diff --git a/agents/haonan/sessions/sessions.json b/agents/haonan/sessions/sessions.json
index d1dfcbe..11de632 100644
--- a/agents/haonan/sessions/sessions.json
+++ b/agents/haonan/sessions/sessions.json
@@ -715,8 +715,8 @@
"memoryFlushCompactionCount": 0
},
"agent:haonan:cron:41396aeb-6e3a-47b2-9bd3-9156a348c1dc": {
- "sessionId": "13cf89c7-ae35-4b61-935d-4b76542f1c28",
- "updatedAt": 1773856800006,
+ "sessionId": "971b07ad-12bb-4041-879c-97906de4bf18",
+ "updatedAt": 1773943200005,
"systemSent": true,
"model": "glm-5",
"contextTokens": 200000,
@@ -757,11 +757,11 @@
"version": 0
},
"modelProvider": "zhipu",
- "inputTokens": 10815,
- "outputTokens": 343,
- "totalTokens": 16891,
+ "inputTokens": 13549,
+ "outputTokens": 371,
+ "totalTokens": 16884,
"totalTokensFresh": true,
- "cacheRead": 39360,
+ "cacheRead": 19840,
"cacheWrite": 0
},
"agent:haonan:cron:f03b184f-5f1d-4093-ae1b-fc9fe0b59eef": {
@@ -807,9 +807,9 @@
"version": 0
},
"modelProvider": "zhipu",
- "inputTokens": 42108,
- "outputTokens": 2001,
- "totalTokens": 24584,
+ "inputTokens": 55959,
+ "outputTokens": 3146,
+ "totalTokens": 156912,
"deliveryContext": {
"channel": "webchat"
},
@@ -818,7 +818,7 @@
"sessionFile": "/home/starmen1265/.openclaw/agents/haonan/sessions/ccc40ca7-7876-4d82-ab84-7b790feddc19.jsonl",
"abortedLastRun": false,
"totalTokensFresh": true,
- "cacheRead": 39613,
+ "cacheRead": 100953,
"cacheWrite": 0
},
"agent:haonan:cron:3245fca7-045b-4977-8fab-020f6df2e453": {
@@ -861,8 +861,8 @@
"cacheWrite": 0
},
"agent:haonan:cron:aa4d2de9-79be-4262-83ee-af8824793bfc": {
- "sessionId": "26e8d6da-1d38-4fc7-9ddb-a1c14c23fde4",
- "updatedAt": 1773857400007,
+ "sessionId": "3659e428-8612-437c-9c2b-1ce2f4a0b33d",
+ "updatedAt": 1773943800004,
"systemSent": true,
"label": "Cron: openclaw-git-sync",
"skillsSnapshot": {
@@ -910,113 +910,6 @@
"cacheRead": 32768,
"cacheWrite": 0
},
- "agent:haonan:cron:f03b184f-5f1d-4093-ae1b-fc9fe0b59eef:run:742f547d-e84f-486c-99fe-66589180c4a7": {
- "sessionId": "742f547d-e84f-486c-99fe-66589180c4a7",
- "updatedAt": 1773771600017,
- "systemSent": true,
- "model": "glm-4.7-FlashX",
- "contextTokens": 200000,
- "label": "Cron: capability-evolver-daily",
- "skillsSnapshot": {
- "prompt": "\n\nThe following skills provide specialized instructions for specific tasks.\nUse the read tool to load a skill's file when the task matches its description.\nWhen a skill file references a relative path, resolve it against the skill directory (parent of SKILL.md / dirname of the path) and use that absolute path in tool commands.\n\n\n \n capability-evolver\n A self-evolution engine for AI agents. Analyzes runtime history to identify improvements and applies protocol-constrained evolution.\n ~/.openclaw/skills/capability-evolver/SKILL.md\n \n \n clawlist\n MUST use for any multi-step project, long-running task, or infinite monitoring workflow. Plan, execute, track, and verify tasks with checkpoint validation. For projects, automation, and ongoing operations.\n ~/.openclaw/skills/clawlist/SKILL.md\n \n",
- "skills": [
- {
- "name": "capability-evolver"
- },
- {
- "name": "clawlist"
- }
- ],
- "skillFilter": [
- "clawlist",
- "capability-evolver",
- "self-improving-agent"
- ],
- "resolvedSkills": [
- {
- "name": "capability-evolver",
- "description": "A self-evolution engine for AI agents. Analyzes runtime history to identify improvements and applies protocol-constrained evolution.",
- "filePath": "/home/starmen1265/.openclaw/skills/capability-evolver/SKILL.md",
- "baseDir": "/home/starmen1265/.openclaw/skills/capability-evolver",
- "source": "openclaw-managed",
- "disableModelInvocation": false
- },
- {
- "name": "clawlist",
- "description": "MUST use for any multi-step project, long-running task, or infinite monitoring workflow. Plan, execute, track, and verify tasks with checkpoint validation. For projects, automation, and ongoing operations.",
- "filePath": "/home/starmen1265/.openclaw/skills/clawlist/SKILL.md",
- "baseDir": "/home/starmen1265/.openclaw/skills/clawlist",
- "source": "openclaw-managed",
- "disableModelInvocation": false
- }
- ],
- "version": 0
- },
- "modelProvider": "zhipu",
- "inputTokens": 42108,
- "outputTokens": 2001,
- "totalTokens": 24584,
- "deliveryContext": {
- "channel": "webchat"
- },
- "lastChannel": "webchat",
- "channel": "webchat",
- "sessionFile": "/home/starmen1265/.openclaw/agents/haonan/sessions/ccc40ca7-7876-4d82-ab84-7b790feddc19.jsonl",
- "abortedLastRun": false,
- "totalTokensFresh": true,
- "cacheRead": 39613,
- "cacheWrite": 0
- },
- "agent:haonan:cron:41396aeb-6e3a-47b2-9bd3-9156a348c1dc:run:13cf89c7-ae35-4b61-935d-4b76542f1c28": {
- "sessionId": "13cf89c7-ae35-4b61-935d-4b76542f1c28",
- "updatedAt": 1773856800006,
- "systemSent": true,
- "model": "glm-5",
- "contextTokens": 200000,
- "label": "Cron: backup-openclaw-json",
- "skillsSnapshot": {
- "prompt": "\n\nThe following skills provide specialized instructions for specific tasks.\nUse the read tool to load a skill's file when the task matches its description.\nWhen a skill file references a relative path, resolve it against the skill directory (parent of SKILL.md / dirname of the path) and use that absolute path in tool commands.\n\n\n \n capability-evolver\n A self-evolution engine for AI agents. Analyzes runtime history to identify improvements and applies protocol-constrained evolution.\n ~/.openclaw/skills/capability-evolver/SKILL.md\n \n \n clawlist\n MUST use for any multi-step project, long-running task, or infinite monitoring workflow. Plan, execute, track, and verify tasks with checkpoint validation. For projects, automation, and ongoing operations.\n ~/.openclaw/skills/clawlist/SKILL.md\n \n",
- "skills": [
- {
- "name": "capability-evolver"
- },
- {
- "name": "clawlist"
- }
- ],
- "skillFilter": [
- "clawlist",
- "capability-evolver",
- "self-improving-agent"
- ],
- "resolvedSkills": [
- {
- "name": "capability-evolver",
- "description": "A self-evolution engine for AI agents. Analyzes runtime history to identify improvements and applies protocol-constrained evolution.",
- "filePath": "/home/starmen1265/.openclaw/skills/capability-evolver/SKILL.md",
- "baseDir": "/home/starmen1265/.openclaw/skills/capability-evolver",
- "source": "openclaw-managed",
- "disableModelInvocation": false
- },
- {
- "name": "clawlist",
- "description": "MUST use for any multi-step project, long-running task, or infinite monitoring workflow. Plan, execute, track, and verify tasks with checkpoint validation. For projects, automation, and ongoing operations.",
- "filePath": "/home/starmen1265/.openclaw/skills/clawlist/SKILL.md",
- "baseDir": "/home/starmen1265/.openclaw/skills/clawlist",
- "source": "openclaw-managed",
- "disableModelInvocation": false
- }
- ],
- "version": 0
- },
- "modelProvider": "zhipu",
- "inputTokens": 10815,
- "outputTokens": 343,
- "totalTokens": 16891,
- "totalTokensFresh": true,
- "cacheRead": 39360,
- "cacheWrite": 0
- },
"agent:haonan:cron:aa4d2de9-79be-4262-83ee-af8824793bfc:run:26e8d6da-1d38-4fc7-9ddb-a1c14c23fde4": {
"sessionId": "26e8d6da-1d38-4fc7-9ddb-a1c14c23fde4",
"updatedAt": 1773857400007,
@@ -1110,9 +1003,9 @@
"version": 0
},
"modelProvider": "zhipu",
- "inputTokens": 42108,
- "outputTokens": 2001,
- "totalTokens": 24584,
+ "inputTokens": 55959,
+ "outputTokens": 3146,
+ "totalTokens": 156912,
"deliveryContext": {
"channel": "webchat"
},
@@ -1121,7 +1014,107 @@
"sessionFile": "/home/starmen1265/.openclaw/agents/haonan/sessions/ccc40ca7-7876-4d82-ab84-7b790feddc19.jsonl",
"abortedLastRun": false,
"totalTokensFresh": true,
- "cacheRead": 39613,
+ "cacheRead": 100953,
+ "cacheWrite": 0
+ },
+ "agent:haonan:cron:41396aeb-6e3a-47b2-9bd3-9156a348c1dc:run:971b07ad-12bb-4041-879c-97906de4bf18": {
+ "sessionId": "971b07ad-12bb-4041-879c-97906de4bf18",
+ "updatedAt": 1773943200005,
+ "systemSent": true,
+ "model": "glm-5",
+ "contextTokens": 200000,
+ "label": "Cron: backup-openclaw-json",
+ "skillsSnapshot": {
+ "prompt": "\n\nThe following skills provide specialized instructions for specific tasks.\nUse the read tool to load a skill's file when the task matches its description.\nWhen a skill file references a relative path, resolve it against the skill directory (parent of SKILL.md / dirname of the path) and use that absolute path in tool commands.\n\n\n \n capability-evolver\n A self-evolution engine for AI agents. Analyzes runtime history to identify improvements and applies protocol-constrained evolution.\n ~/.openclaw/skills/capability-evolver/SKILL.md\n \n \n clawlist\n MUST use for any multi-step project, long-running task, or infinite monitoring workflow. Plan, execute, track, and verify tasks with checkpoint validation. For projects, automation, and ongoing operations.\n ~/.openclaw/skills/clawlist/SKILL.md\n \n",
+ "skills": [
+ {
+ "name": "capability-evolver"
+ },
+ {
+ "name": "clawlist"
+ }
+ ],
+ "skillFilter": [
+ "clawlist",
+ "capability-evolver",
+ "self-improving-agent"
+ ],
+ "resolvedSkills": [
+ {
+ "name": "capability-evolver",
+ "description": "A self-evolution engine for AI agents. Analyzes runtime history to identify improvements and applies protocol-constrained evolution.",
+ "filePath": "/home/starmen1265/.openclaw/skills/capability-evolver/SKILL.md",
+ "baseDir": "/home/starmen1265/.openclaw/skills/capability-evolver",
+ "source": "openclaw-managed",
+ "disableModelInvocation": false
+ },
+ {
+ "name": "clawlist",
+ "description": "MUST use for any multi-step project, long-running task, or infinite monitoring workflow. Plan, execute, track, and verify tasks with checkpoint validation. For projects, automation, and ongoing operations.",
+ "filePath": "/home/starmen1265/.openclaw/skills/clawlist/SKILL.md",
+ "baseDir": "/home/starmen1265/.openclaw/skills/clawlist",
+ "source": "openclaw-managed",
+ "disableModelInvocation": false
+ }
+ ],
+ "version": 0
+ },
+ "modelProvider": "zhipu",
+ "inputTokens": 13549,
+ "outputTokens": 371,
+ "totalTokens": 16884,
+ "totalTokensFresh": true,
+ "cacheRead": 19840,
+ "cacheWrite": 0
+ },
+ "agent:haonan:cron:aa4d2de9-79be-4262-83ee-af8824793bfc:run:3659e428-8612-437c-9c2b-1ce2f4a0b33d": {
+ "sessionId": "3659e428-8612-437c-9c2b-1ce2f4a0b33d",
+ "updatedAt": 1773943800004,
+ "systemSent": true,
+ "label": "Cron: openclaw-git-sync",
+ "skillsSnapshot": {
+ "prompt": "\n\nThe following skills provide specialized instructions for specific tasks.\nUse the read tool to load a skill's file when the task matches its description.\nWhen a skill file references a relative path, resolve it against the skill directory (parent of SKILL.md / dirname of the path) and use that absolute path in tool commands.\n\n\n \n capability-evolver\n A self-evolution engine for AI agents. Analyzes runtime history to identify improvements and applies protocol-constrained evolution.\n ~/.openclaw/skills/capability-evolver/SKILL.md\n \n \n clawlist\n MUST use for any multi-step project, long-running task, or infinite monitoring workflow. Plan, execute, track, and verify tasks with checkpoint validation. For projects, automation, and ongoing operations.\n ~/.openclaw/skills/clawlist/SKILL.md\n \n",
+ "skills": [
+ {
+ "name": "capability-evolver"
+ },
+ {
+ "name": "clawlist"
+ }
+ ],
+ "skillFilter": [
+ "clawlist",
+ "capability-evolver",
+ "self-improving-agent"
+ ],
+ "resolvedSkills": [
+ {
+ "name": "capability-evolver",
+ "description": "A self-evolution engine for AI agents. Analyzes runtime history to identify improvements and applies protocol-constrained evolution.",
+ "filePath": "/home/starmen1265/.openclaw/skills/capability-evolver/SKILL.md",
+ "baseDir": "/home/starmen1265/.openclaw/skills/capability-evolver",
+ "source": "openclaw-managed",
+ "disableModelInvocation": false
+ },
+ {
+ "name": "clawlist",
+ "description": "MUST use for any multi-step project, long-running task, or infinite monitoring workflow. Plan, execute, track, and verify tasks with checkpoint validation. For projects, automation, and ongoing operations.",
+ "filePath": "/home/starmen1265/.openclaw/skills/clawlist/SKILL.md",
+ "baseDir": "/home/starmen1265/.openclaw/skills/clawlist",
+ "source": "openclaw-managed",
+ "disableModelInvocation": false
+ }
+ ],
+ "version": 0
+ },
+ "modelProvider": "zhipu",
+ "model": "glm-5",
+ "contextTokens": 200000,
+ "inputTokens": 1734,
+ "outputTokens": 413,
+ "totalTokens": 18003,
+ "totalTokensFresh": true,
+ "cacheRead": 32768,
"cacheWrite": 0
}
}
\ No newline at end of file
diff --git a/agents/knowledge-base/workspace/memory/2026-03-19-1500.md b/agents/knowledge-base/workspace/memory/2026-03-19-1500.md
new file mode 100644
index 0000000..6f778cb
--- /dev/null
+++ b/agents/knowledge-base/workspace/memory/2026-03-19-1500.md
@@ -0,0 +1,164 @@
+# Session: 2026-03-19 15:00:36 UTC
+
+- **Session Key**: agent:knowledge-base:main
+- **Session ID**: 731aff7c-67da-45ff-957f-7940849d0aae
+- **Source**: feishu
+
+## Conversation Summary
+
+user: Conversation info (untrusted metadata):
+```json
+{
+ "message_id": "om_x100b548be4a02ca4b33d43c7a3208be",
+ "sender_id": "ou_9351c287f42069fa30366ac641d24df7",
+ "sender": "张青",
+ "timestamp": "Thu 2026-03-19 21:31 GMT+8"
+}
+```
+
+Sender (untrusted metadata):
+```json
+{
+ "label": "张青 (ou_9351c287f42069fa30366ac641d24df7)",
+ "id": "ou_9351c287f42069fa30366ac641d24df7",
+ "name": "张青"
+}
+```
+
+[message_id: om_x100b548be4a02ca4b33d43c7a3208be]
+张青: 记一下,可可想要一个里面有小人的公交车
+assistant: 已记录到速记本:**可可想要一个里面有小人的公交车** (2026-03-19 21:31)
+user: Conversation info (untrusted metadata):
+```json
+{
+ "message_id": "om_x100b548be25df0b0b22cdf708250b00",
+ "sender_id": "ou_9351c287f42069fa30366ac641d24df7",
+ "sender": "张青",
+ "timestamp": "Thu 2026-03-19 21:31 GMT+8"
+}
+```
+
+Sender (untrusted metadata):
+```json
+{
+ "label": "张青 (ou_9351c287f42069fa30366ac641d24df7)",
+ "id": "ou_9351c287f42069fa30366ac641d24df7",
+ "name": "张青"
+}
+```
+
+[message_id: om_x100b548be25df0b0b22cdf708250b00]
+张青: 还要带语音控制的
+assistant: 已更新:**可可想要一个里面有小人的公交车(带语音控制)** (2026-03-19 21:31)
+user: Conversation info (untrusted metadata):
+```json
+{
+ "message_id": "om_x100b548be03aaca4b393638a734446f",
+ "sender_id": "ou_9351c287f42069fa30366ac641d24df7",
+ "sender": "张青",
+ "timestamp": "Thu 2026-03-19 21:32 GMT+8"
+}
+```
+
+Sender (untrusted metadata):
+```json
+{
+ "label": "张青 (ou_9351c287f42069fa30366ac641d24df7)",
+ "id": "ou_9351c287f42069fa30366ac641d24df7",
+ "name": "张青"
+}
+```
+
+[message_id: om_x100b548be03aaca4b393638a734446f]
+张青: 还要能放音乐的
+assistant: 已更新:**可可想要一个里面有小人的公交车(带语音控制、能放音乐)** (2026-03-19 21:31)
+user: Conversation info (untrusted metadata):
+```json
+{
+ "message_id": "om_x100b548bfc0824a4b364c8c0c8977d1",
+ "sender_id": "ou_9351c287f42069fa30366ac641d24df7",
+ "sender": "张青",
+ "timestamp": "Thu 2026-03-19 21:33 GMT+8"
+}
+```
+
+Sender (untrusted metadata):
+```json
+{
+ "label": "张青 (ou_9351c287f42069fa30366ac641d24df7)",
+ "id": "ou_9351c287f42069fa30366ac641d24df7",
+ "name": "张青"
+}
+```
+
+[message_id: om_x100b548bfc0824a4b364c8c0c8977d1]
+张青: 乐乐想要一个库洛米城堡,里面有很多摆件,这些摆件呢,可以动
+assistant: 已记录:**乐乐想要一个库洛米城堡,里面有很多摆件,这些摆件可以动** (2026-03-19 21:33)
+user: Conversation info (untrusted metadata):
+```json
+{
+ "message_id": "om_x100b548bfd70e48cb22d0cbc00050f8",
+ "sender_id": "ou_9351c287f42069fa30366ac641d24df7",
+ "sender": "张青",
+ "timestamp": "Thu 2026-03-19 21:33 GMT+8"
+}
+```
+
+Sender (untrusted metadata):
+```json
+{
+ "label": "张青 (ou_9351c287f42069fa30366ac641d24df7)",
+ "id": "ou_9351c287f42069fa30366ac641d24df7",
+ "name": "张青"
+}
+```
+
+[message_id: om_x100b548bfd70e48cb22d0cbc00050f8]
+张青: 通过按按钮来控制里面的家具或者家电呃运动
+assistant: 已更新:**乐乐想要一个库洛米城堡,里面有很多摆件,通过按按钮控制家具或家电运动** (2026-03-19 21:33)
+user: Conversation info (untrusted metadata):
+```json
+{
+ "message_id": "om_x100b548bf82dbca0b2c751a1b709843",
+ "sender_id": "ou_9351c287f42069fa30366ac641d24df7",
+ "sender": "张青",
+ "timestamp": "Thu 2026-03-19 21:34 GMT+8"
+}
+```
+
+Sender (untrusted metadata):
+```json
+{
+ "label": "张青 (ou_9351c287f42069fa30366ac641d24df7)",
+ "id": "ou_9351c287f42069fa30366ac641d24df7",
+ "name": "张青"
+}
+```
+
+[message_id: om_x100b548bf82dbca0b2c751a1b709843]
+张青: 可可还想要一个可以涂颜色的石膏娃娃
+assistant: 已记录:**可可想要一个可以涂颜色的石膏娃娃** (2026-03-19 21:34)
+user: Conversation info (untrusted metadata):
+```json
+{
+ "message_id": "om_x100b548b8e22dcacb2d8d587bb7b14e",
+ "sender_id": "ou_9351c287f42069fa30366ac641d24df7",
+ "sender": "张青",
+ "timestamp": "Thu 2026-03-19 21:37 GMT+8"
+}
+```
+
+Sender (untrusted metadata):
+```json
+{
+ "label": "张青 (ou_9351c287f42069fa30366ac641d24df7)",
+ "id": "ou_9351c287f42069fa30366ac641d24df7",
+ "name": "张青"
+}
+```
+
+[message_id: om_x100b548b8e22dcacb2d8d587bb7b14e]
+张青: 嗯,可可想要一个空的城堡,里面有个绿色按钮。
+
+绿色按钮按一下,城堡的顶上就会出现一个美乐蒂,再按一下按钮美乐蒂就会变成一朵花
+assistant: 已记录:**可可想要一个空的城堡,里面有个绿色按钮:按一下城堡顶上出现美乐蒂,再按一下美乐蒂变成一朵花** (2026-03-19 21:37)
diff --git a/agents/knowledge-base/workspace/memory/2026-03-19-1612.md b/agents/knowledge-base/workspace/memory/2026-03-19-1612.md
new file mode 100644
index 0000000..b7439a1
--- /dev/null
+++ b/agents/knowledge-base/workspace/memory/2026-03-19-1612.md
@@ -0,0 +1,67 @@
+# Session: 2026-03-19 16:12:12 UTC
+
+- **Session Key**: agent:knowledge-base:main
+- **Session ID**: b11228d2-b3ca-444f-837c-c0a787d3a0cb
+- **Source**: feishu
+
+## Conversation Summary
+
+assistant: ✅ New session started · model: zhipu/glm-5
+user: A new session was started via /new or /reset. Execute your Session Startup sequence now - read the required files before responding to the user. Then greet the user in your configured persona, if one is provided. Be yourself - use your defined voice, mannerisms, and mood. Keep it to 1-3 sentences and ask what they want to do. If the runtime model differs from default_model in the system prompt, mention the default model. Do not mention internal steps, files, tools, or reasoning.
+assistant: 📚 蒋先生您好,我是达康书记。已为您准备好知识库与工作区维护服务。
+
+今天有什么需要我帮您处理的?
+user: Conversation info (untrusted metadata):
+```json
+{
+ "message_id": "om_x100b54f5454c14a4c42e9b6c6ea2cd3",
+ "sender_id": "ou_9351c287f42069fa30366ac641d24df7",
+ "sender": "张青",
+ "timestamp": "Thu 2026-03-19 23:05 GMT+8"
+}
+```
+
+Sender (untrusted metadata):
+```json
+{
+ "label": "张青 (ou_9351c287f42069fa30366ac641d24df7)",
+ "id": "ou_9351c287f42069fa30366ac641d24df7",
+ "name": "张青"
+}
+```
+
+[message_id: om_x100b54f5454c14a4c42e9b6c6ea2cd3]
+张青: https://www.bilibili.com/video/BV1NwwqzeE55/?t=6&spm_id_from=333.1007.tianma.1-1-1.click&vd_source=1deaab00c5de8885422a7a7690170eba 添加知识库
+assistant: 收到,蒋先生。我立即按照知识库工作流技能处理这个B站视频链接。
+assistant: 好的,蒋先生。我按照知识库工作流技能处理这个B站视频链接。
+
+**步骤 1:检查环境变量**
+assistant: 好的,蒋先生。工作流已经开始处理这个B站视频。我看到它正在:
+
+1. ✅ 解析 URL
+2. ⚠️ 字幕获取失败(可能视频没有字幕)
+3. 🔄 正在进行 AI 深度分析...
+
+让我继续监控进度:
+assistant: ✅ **处理完成!**
+
+**视频信息:**
+- **标题:** 爱因斯坦又对了!科学家首次观测到物质从真空中诞生,【科学快报】265期
+- **作者:** 科学透镜
+- **BV号:** BV1NwwqzeE55
+
+**处理流程:**
+- ✅ Parse - URL 解析成功
+- ✅ Download - 下载音频和元数据
+- ✅ ASR - 语音识别完成(Whisper)
+- ✅ Analyze - AI 深度分析完成(耗时 110.78秒)
+- ✅ Archive - 归档成功
+
+**核心内容摘要:**
+本视频报道了三项颠覆性科学发现:
+1. **物质从真空中诞生** - 布鲁克海文国家实验室首次观测到Lambda超子与反粒子的自旋纠缠,证实虚粒子可转化为真实物质
+2. **电子运动延迟** - ETH测得电子响应原子核运动存在30飞秒延迟,打破波恩-奥本海默近似
+3. **半莫比乌斯分子** - 人类首次合成并精准操控具有拓扑结构的分子
+
+**归档位置:**
+`~/work/zq/30.资源/AI分析/bilibili/20260319_BV1NwwqzeE55_analysis.md`
diff --git a/agents/knowledge-base/workspace/memory/2026-03-19.md b/agents/knowledge-base/workspace/memory/2026-03-19.md
new file mode 100644
index 0000000..af51648
--- /dev/null
+++ b/agents/knowledge-base/workspace/memory/2026-03-19.md
@@ -0,0 +1,8 @@
+# 2026-03-19 每日记录
+
+## 定时任务执行
+
+### 02:30 - 每日回顾整理(daily-review-cleanup)
+- 执行脚本:`~/.openclaw/cron/scripts/daily-review.sh`
+- 处理日期:2026-03-18
+- 结果:从 NAS 拉取更新完成,无已完成任务需要归档
diff --git a/agents/ui-designer/sessions/sessions.json b/agents/ui-designer/sessions/sessions.json
index 6f5d6f0..5ea9fca 100644
--- a/agents/ui-designer/sessions/sessions.json
+++ b/agents/ui-designer/sessions/sessions.json
@@ -1,7 +1,7 @@
{
"agent:ui-designer:main": {
- "sessionId": "4caf7900-b5bc-4f76-a39d-80e506bcea84",
- "updatedAt": 1773758631721,
+ "sessionId": "60827bbf-d985-4a29-bd62-1783159bb38e",
+ "updatedAt": 1773937164358,
"systemSent": true,
"abortedLastRun": false,
"chatType": "direct",
@@ -22,7 +22,7 @@
"to": "user:ou_9847ce84b960736c14554730b41e4957",
"accountId": "feishu-art"
},
- "sessionFile": "/home/starmen1265/.openclaw/agents/ui-designer/sessions/4caf7900-b5bc-4f76-a39d-80e506bcea84.jsonl",
+ "sessionFile": "/home/starmen1265/.openclaw/agents/ui-designer/sessions/60827bbf-d985-4a29-bd62-1783159bb38e.jsonl",
"compactionCount": 0,
"skillsSnapshot": {
"prompt": "\n\nThe following skills provide specialized instructions for specific tasks.\nUse the read tool to load a skill's file when the task matches its description.\nWhen a skill file references a relative path, resolve it against the skill directory (parent of SKILL.md / dirname of the path) and use that absolute path in tool commands.\n\n\n \n capability-evolver\n A self-evolution engine for AI agents. Analyzes runtime history to identify improvements and applies protocol-constrained evolution.\n ~/.openclaw/skills/capability-evolver/SKILL.md\n \n",
@@ -51,8 +51,8 @@
"model": "glm-5",
"systemPromptReport": {
"source": "run",
- "generatedAt": 1773758614151,
- "sessionId": "4caf7900-b5bc-4f76-a39d-80e506bcea84",
+ "generatedAt": 1773937122951,
+ "sessionId": "60827bbf-d985-4a29-bd62-1783159bb38e",
"sessionKey": "agent:ui-designer:main",
"provider": "zhipu",
"model": "glm-5",
@@ -64,8 +64,8 @@
"sandboxed": false
},
"systemPrompt": {
- "chars": 24499,
- "projectContextChars": 12014,
+ "chars": 36876,
+ "projectContextChars": 24391,
"nonProjectContextChars": 12485
},
"injectedWorkspaceFiles": [
@@ -73,8 +73,8 @@
"name": "AGENTS.md",
"path": "/home/starmen1265/.openclaw/agents/ui-designer/workspace/AGENTS.md",
"missing": false,
- "rawChars": 2617,
- "injectedChars": 2617,
+ "rawChars": 2663,
+ "injectedChars": 2663,
"truncated": false
},
{
@@ -187,6 +187,120 @@
"injectedChars": 616,
"truncated": false
},
+ {
+ "path": "SELF_IMPROVEMENT_REMINDER.md",
+ "rawChars": 616,
+ "injectedChars": 616,
+ "truncated": false
+ },
+ {
+ "path": "SELF_IMPROVEMENT_REMINDER.md",
+ "rawChars": 616,
+ "injectedChars": 616,
+ "truncated": false
+ },
+ {
+ "path": "SELF_IMPROVEMENT_REMINDER.md",
+ "rawChars": 616,
+ "injectedChars": 616,
+ "truncated": false
+ },
+ {
+ "path": "SELF_IMPROVEMENT_REMINDER.md",
+ "rawChars": 616,
+ "injectedChars": 616,
+ "truncated": false
+ },
+ {
+ "path": "SELF_IMPROVEMENT_REMINDER.md",
+ "rawChars": 616,
+ "injectedChars": 616,
+ "truncated": false
+ },
+ {
+ "path": "SELF_IMPROVEMENT_REMINDER.md",
+ "rawChars": 616,
+ "injectedChars": 616,
+ "truncated": false
+ },
+ {
+ "path": "SELF_IMPROVEMENT_REMINDER.md",
+ "rawChars": 616,
+ "injectedChars": 616,
+ "truncated": false
+ },
+ {
+ "path": "SELF_IMPROVEMENT_REMINDER.md",
+ "rawChars": 616,
+ "injectedChars": 616,
+ "truncated": false
+ },
+ {
+ "path": "SELF_IMPROVEMENT_REMINDER.md",
+ "rawChars": 616,
+ "injectedChars": 616,
+ "truncated": false
+ },
+ {
+ "path": "SELF_IMPROVEMENT_REMINDER.md",
+ "rawChars": 616,
+ "injectedChars": 616,
+ "truncated": false
+ },
+ {
+ "path": "SELF_IMPROVEMENT_REMINDER.md",
+ "rawChars": 616,
+ "injectedChars": 616,
+ "truncated": false
+ },
+ {
+ "path": "SELF_IMPROVEMENT_REMINDER.md",
+ "rawChars": 616,
+ "injectedChars": 616,
+ "truncated": false
+ },
+ {
+ "path": "SELF_IMPROVEMENT_REMINDER.md",
+ "rawChars": 616,
+ "injectedChars": 616,
+ "truncated": false
+ },
+ {
+ "path": "SELF_IMPROVEMENT_REMINDER.md",
+ "rawChars": 616,
+ "injectedChars": 616,
+ "truncated": false
+ },
+ {
+ "path": "SELF_IMPROVEMENT_REMINDER.md",
+ "rawChars": 616,
+ "injectedChars": 616,
+ "truncated": false
+ },
+ {
+ "path": "SELF_IMPROVEMENT_REMINDER.md",
+ "rawChars": 616,
+ "injectedChars": 616,
+ "truncated": false
+ },
+ {
+ "path": "SELF_IMPROVEMENT_REMINDER.md",
+ "rawChars": 616,
+ "injectedChars": 616,
+ "truncated": false
+ },
+ {
+ "path": "SELF_IMPROVEMENT_REMINDER.md",
+ "rawChars": 616,
+ "injectedChars": 616,
+ "truncated": false
+ },
+ {
+ "path": "SELF_IMPROVEMENT_REMINDER.md",
+ "rawChars": 616,
+ "injectedChars": 616,
+ "truncated": false
+ },
{
"path": "SELF_IMPROVEMENT_REMINDER.md",
"rawChars": 616,
@@ -414,13 +528,13 @@
]
}
},
- "cacheRead": 9408,
+ "cacheRead": 23872,
"cacheWrite": 0,
"totalTokensFresh": true,
"contextTokens": 200000,
- "inputTokens": 7026,
- "outputTokens": 464,
- "totalTokens": 16434
+ "inputTokens": 311770,
+ "outputTokens": 332,
+ "totalTokens": 170338
},
"agent:ui-designer:cron:78b64c2f-7f91-47ff-9250-d13f546eb21f": {
"sessionId": "03e44b46-c707-4a9f-bf3d-1818aafdcde2",
diff --git a/cron/jobs.json b/cron/jobs.json
index 09b9348..08a3464 100644
--- a/cron/jobs.json
+++ b/cron/jobs.json
@@ -7,7 +7,7 @@
"name": "backup-openclaw-json",
"enabled": true,
"createdAtMs": 1772261581796,
- "updatedAtMs": 1773856817101,
+ "updatedAtMs": 1773943214562,
"schedule": {
"kind": "cron",
"expr": "0 2 * * *",
@@ -23,10 +23,10 @@
"mode": "announce"
},
"state": {
- "nextRunAtMs": 1773943200000,
- "lastRunAtMs": 1773856800004,
+ "nextRunAtMs": 1774029600000,
+ "lastRunAtMs": 1773943200004,
"lastStatus": "ok",
- "lastDurationMs": 17097,
+ "lastDurationMs": 14558,
"consecutiveErrors": 0,
"lastRunStatus": "ok",
"lastDelivered": true,
@@ -59,7 +59,8 @@
"consecutiveErrors": 0,
"lastRunStatus": "ok",
"lastDeliveryStatus": "delivered",
- "lastDelivered": true
+ "lastDelivered": true,
+ "runningAtMs": 1773943800002
},
"delivery": {
"mode": "announce"
@@ -71,7 +72,7 @@
"name": "capability-evolver-daily",
"enabled": true,
"createdAtMs": 1772550743528,
- "updatedAtMs": 1773771643135,
+ "updatedAtMs": 1773858232095,
"schedule": {
"kind": "cron",
"expr": "20 2 * * *",
@@ -92,16 +93,16 @@
"to": "feishu-pm"
},
"state": {
- "nextRunAtMs": 1773858000000,
- "lastRunAtMs": 1773771600014,
+ "nextRunAtMs": 1773944400000,
+ "lastRunAtMs": 1773858000004,
"lastStatus": "error",
- "lastDurationMs": 43121,
- "consecutiveErrors": 5,
+ "lastDurationMs": 232091,
+ "consecutiveErrors": 6,
"lastRunStatus": "error",
"lastDeliveryStatus": "not-delivered",
"lastDelivered": false,
- "lastDeliveryError": "⚠️ ✉️ Message: 蒋先生 failed",
- "runningAtMs": 1773858000003
+ "lastDeliveryError": "⚠️ ✉️ Message failed",
+ "lastError": "⚠️ ✉️ Message failed"
}
},
{
@@ -111,7 +112,7 @@
"description": "每天凌晨2:30整理昨天的已完成任务到每日回顾",
"enabled": true,
"createdAtMs": 1741484880000,
- "updatedAtMs": 1773772248827,
+ "updatedAtMs": 1773858626341,
"schedule": {
"kind": "cron",
"expr": "30 2 * * *",
@@ -129,11 +130,11 @@
"channel": "last"
},
"state": {
- "nextRunAtMs": 1773858600000,
- "lastRunAtMs": 1773772200010,
+ "nextRunAtMs": 1773945000000,
+ "lastRunAtMs": 1773858600004,
"lastRunStatus": "ok",
"lastStatus": "ok",
- "lastDurationMs": 48817,
+ "lastDurationMs": 26337,
"lastDelivered": true,
"lastDeliveryStatus": "delivered",
"consecutiveErrors": 0
@@ -145,7 +146,7 @@
"name": "zq_workspace_sync",
"enabled": true,
"createdAtMs": 1772551807455,
- "updatedAtMs": 1773772800011,
+ "updatedAtMs": 1773859200006,
"schedule": {
"expr": "40 2 * * *",
"kind": "cron",
@@ -158,8 +159,8 @@
"text": "执行 zq 工作区同步:bash ~/.openclaw/cron/scripts/zq_workspace_sync.sh"
},
"state": {
- "nextRunAtMs": 1773859200000,
- "lastRunAtMs": 1773772800010,
+ "nextRunAtMs": 1773945600000,
+ "lastRunAtMs": 1773859200005,
"lastStatus": "skipped",
"lastDurationMs": 1,
"consecutiveErrors": 0,
@@ -175,7 +176,7 @@
"description": "每天早上8点整理与各个 AI 代理的对话记录",
"enabled": true,
"createdAtMs": 1772776369645,
- "updatedAtMs": 1773792029087,
+ "updatedAtMs": 1773878422623,
"schedule": {
"kind": "cron",
"expr": "0 8 * * *",
@@ -193,11 +194,11 @@
"channel": "last"
},
"state": {
- "nextRunAtMs": 1773878400000,
- "lastRunAtMs": 1773792000007,
+ "nextRunAtMs": 1773964800000,
+ "lastRunAtMs": 1773878400004,
"lastRunStatus": "ok",
"lastStatus": "ok",
- "lastDurationMs": 29080,
+ "lastDurationMs": 22619,
"lastDelivered": true,
"lastDeliveryStatus": "delivered",
"consecutiveErrors": 0
@@ -210,7 +211,7 @@
"description": "每天7点同步提醒到CalDAV日历",
"enabled": true,
"createdAtMs": 1772791528528,
- "updatedAtMs": 1773788430000,
+ "updatedAtMs": 1773874820139,
"schedule": {
"kind": "cron",
"expr": "0 7 * * *",
@@ -228,11 +229,11 @@
"channel": "last"
},
"state": {
- "nextRunAtMs": 1773874800000,
- "lastRunAtMs": 1773788400005,
+ "nextRunAtMs": 1773961200000,
+ "lastRunAtMs": 1773874800006,
"lastRunStatus": "ok",
"lastStatus": "ok",
- "lastDurationMs": 29995,
+ "lastDurationMs": 20133,
"lastDeliveryStatus": "delivered",
"consecutiveErrors": 0,
"lastDelivered": true
diff --git a/cron/jobs.json.bak b/cron/jobs.json.bak
index 753feea..2d8376d 100644
--- a/cron/jobs.json.bak
+++ b/cron/jobs.json.bak
@@ -7,7 +7,7 @@
"name": "backup-openclaw-json",
"enabled": true,
"createdAtMs": 1772261581796,
- "updatedAtMs": 1773856817101,
+ "updatedAtMs": 1773943214562,
"schedule": {
"kind": "cron",
"expr": "0 2 * * *",
@@ -23,10 +23,10 @@
"mode": "announce"
},
"state": {
- "nextRunAtMs": 1773943200000,
- "lastRunAtMs": 1773856800004,
+ "nextRunAtMs": 1774029600000,
+ "lastRunAtMs": 1773943200004,
"lastStatus": "ok",
- "lastDurationMs": 17097,
+ "lastDurationMs": 14558,
"consecutiveErrors": 0,
"lastRunStatus": "ok",
"lastDelivered": true,
@@ -71,7 +71,7 @@
"name": "capability-evolver-daily",
"enabled": true,
"createdAtMs": 1772550743528,
- "updatedAtMs": 1773771643135,
+ "updatedAtMs": 1773858232095,
"schedule": {
"kind": "cron",
"expr": "20 2 * * *",
@@ -92,16 +92,16 @@
"to": "feishu-pm"
},
"state": {
- "nextRunAtMs": 1773858000000,
- "lastRunAtMs": 1773771600014,
+ "nextRunAtMs": 1773944400000,
+ "lastRunAtMs": 1773858000004,
"lastStatus": "error",
- "lastDurationMs": 43121,
- "consecutiveErrors": 5,
+ "lastDurationMs": 232091,
+ "consecutiveErrors": 6,
"lastRunStatus": "error",
"lastDeliveryStatus": "not-delivered",
"lastDelivered": false,
- "lastDeliveryError": "⚠️ ✉️ Message: 蒋先生 failed",
- "lastError": "⚠️ ✉️ Message: 蒋先生 failed"
+ "lastDeliveryError": "⚠️ ✉️ Message failed",
+ "lastError": "⚠️ ✉️ Message failed"
}
},
{
@@ -111,7 +111,7 @@
"description": "每天凌晨2:30整理昨天的已完成任务到每日回顾",
"enabled": true,
"createdAtMs": 1741484880000,
- "updatedAtMs": 1773772248827,
+ "updatedAtMs": 1773858626341,
"schedule": {
"kind": "cron",
"expr": "30 2 * * *",
@@ -129,11 +129,11 @@
"channel": "last"
},
"state": {
- "nextRunAtMs": 1773858600000,
- "lastRunAtMs": 1773772200010,
+ "nextRunAtMs": 1773945000000,
+ "lastRunAtMs": 1773858600004,
"lastRunStatus": "ok",
"lastStatus": "ok",
- "lastDurationMs": 48817,
+ "lastDurationMs": 26337,
"lastDelivered": true,
"lastDeliveryStatus": "delivered",
"consecutiveErrors": 0
@@ -145,7 +145,7 @@
"name": "zq_workspace_sync",
"enabled": true,
"createdAtMs": 1772551807455,
- "updatedAtMs": 1773772800011,
+ "updatedAtMs": 1773859200006,
"schedule": {
"expr": "40 2 * * *",
"kind": "cron",
@@ -158,8 +158,8 @@
"text": "执行 zq 工作区同步:bash ~/.openclaw/cron/scripts/zq_workspace_sync.sh"
},
"state": {
- "nextRunAtMs": 1773859200000,
- "lastRunAtMs": 1773772800010,
+ "nextRunAtMs": 1773945600000,
+ "lastRunAtMs": 1773859200005,
"lastStatus": "skipped",
"lastDurationMs": 1,
"consecutiveErrors": 0,
@@ -175,7 +175,7 @@
"description": "每天早上8点整理与各个 AI 代理的对话记录",
"enabled": true,
"createdAtMs": 1772776369645,
- "updatedAtMs": 1773792029087,
+ "updatedAtMs": 1773878422623,
"schedule": {
"kind": "cron",
"expr": "0 8 * * *",
@@ -193,11 +193,11 @@
"channel": "last"
},
"state": {
- "nextRunAtMs": 1773878400000,
- "lastRunAtMs": 1773792000007,
+ "nextRunAtMs": 1773964800000,
+ "lastRunAtMs": 1773878400004,
"lastRunStatus": "ok",
"lastStatus": "ok",
- "lastDurationMs": 29080,
+ "lastDurationMs": 22619,
"lastDelivered": true,
"lastDeliveryStatus": "delivered",
"consecutiveErrors": 0
@@ -210,7 +210,7 @@
"description": "每天7点同步提醒到CalDAV日历",
"enabled": true,
"createdAtMs": 1772791528528,
- "updatedAtMs": 1773788430000,
+ "updatedAtMs": 1773874820139,
"schedule": {
"kind": "cron",
"expr": "0 7 * * *",
@@ -228,11 +228,11 @@
"channel": "last"
},
"state": {
- "nextRunAtMs": 1773874800000,
- "lastRunAtMs": 1773788400005,
+ "nextRunAtMs": 1773961200000,
+ "lastRunAtMs": 1773874800006,
"lastRunStatus": "ok",
"lastStatus": "ok",
- "lastDurationMs": 29995,
+ "lastDurationMs": 20133,
"lastDeliveryStatus": "delivered",
"consecutiveErrors": 0,
"lastDelivered": true
diff --git a/delivery-queue/bc3f3807-8735-4bef-bb42-3efd115c3f7e.json b/delivery-queue/bc3f3807-8735-4bef-bb42-3efd115c3f7e.json
new file mode 100644
index 0000000..9a0c273
--- /dev/null
+++ b/delivery-queue/bc3f3807-8735-4bef-bb42-3efd115c3f7e.json
@@ -0,0 +1,21 @@
+{
+ "id": "bc3f3807-8735-4bef-bb42-3efd115c3f7e",
+ "enqueuedAt": 1773858231892,
+ "channel": "feishu",
+ "to": "feishu-pm",
+ "accountId": "feishu-pm",
+ "payloads": [
+ {
+ "text": "我遇到了 Feishu 目标格式的问题。让我直接在当前会话中向您汇报:"
+ }
+ ],
+ "threadId": null,
+ "mirror": {
+ "sessionKey": "agent:haonan:main",
+ "agentId": "haonan",
+ "text": "我遇到了 Feishu 目标格式的问题。让我直接在当前会话中向您汇报:"
+ },
+ "retryCount": 1,
+ "lastAttemptAt": 1773858232086,
+ "lastError": "Request failed with status code 400"
+}
\ No newline at end of file
diff --git a/feishu/dedup/feishu-art.json b/feishu/dedup/feishu-art.json
index 3b55670..c9f86f8 100644
--- a/feishu/dedup/feishu-art.json
+++ b/feishu/dedup/feishu-art.json
@@ -1,5 +1,31 @@
{
- "om_x100b54b68a9658a8c3597587eaea165": 1773677638200,
- "om_x100b54b69ae988acc4e308675a1599b": 1773677890863,
- "om_x100b54a2c799a0b0c2ea37cee3ecec2": 1773758613782
+ "om_x100b5480d5bba4b4b2c9a4e8bf5b189": 1773881784103,
+ "om_x100b54808ff17c90b374531d1cf7751": 1773882388481,
+ "om_x100b5481441a38a4b20013813040b59": 1773883566093,
+ "om_x100b54816cbffcb8b36dfe46fd84a26": 1773883944086,
+ "om_x100b54817240a0a0b49ad1ecc4230d1": 1773884361569,
+ "om_x100b548114fc64a4b34dda488c6c4cf": 1773884835671,
+ "om_x100b548129129490b4b1770d7f3efcc": 1773885054622,
+ "om_x100b5481243380b0b10b5bef1b92ca3": 1773885104561,
+ "om_x100b54813d7144bcb2da847227b06a8": 1773885244462,
+ "om_x100b548139e42d10b3ffcf9447ed48c": 1773885299355,
+ "om_x100b548133fa3ca4b219d22fcf41e18": 1773885396226,
+ "om_x100b5481ce0418a0b26dc9b2f68f007": 1773885453209,
+ "om_x100b548c9b6dcc78b49c9ec428e44f3": 1773899098605,
+ "om_x100b548ca39e00acc3362a9bf3873de": 1773899477853,
+ "om_x100b548d47a538b0c2c282968797cea": 1773899927180,
+ "om_x100b5489ba663ca0c4c8bc1d9b97937": 1773920075261,
+ "om_x100b548a50d788b8b2c1f705241dbc5": 1773920738205,
+ "om_x100b548a72e7f4a8b286f0d7e9aea31": 1773921219213,
+ "om_x100b548a83211ca8c44bcbf00eb049e": 1773923551363,
+ "om_x100b548a95d45530c29580a9d1bad9a": 1773923762295,
+ "om_x100b548b1fa8c8a4c42e95eb381a75c": 1773925655047,
+ "om_x100b54f49f9f20b0b2d58df353d8a5b": 1773931798109,
+ "om_x100b54f51931bca0c361d933da09d2b": 1773933952255,
+ "om_x100b54f52b61f0a4c3650d280bf0dad": 1773934171242,
+ "om_x100b54f53b8678b0c3188a42d675405": 1773934421286,
+ "om_x100b54f533291ca4c2cf74f54da005d": 1773934558803,
+ "om_x100b54f5b9ebf8a0c33674c32a8da6c": 1773936499279,
+ "om_x100b54f64c3844acc14333ab25a59e5": 1773936688149,
+ "om_x100b54f650ef80a0c2d3c76a3fbe9be": 1773937122637
}
diff --git a/feishu/dedup/feishu-worker.json b/feishu/dedup/feishu-worker.json
index e1d7b1d..dec6427 100644
--- a/feishu/dedup/feishu-worker.json
+++ b/feishu/dedup/feishu-worker.json
@@ -1,10 +1,18 @@
{
- "om_x100b54a3ae6304a0b28ab7519a1acf1": 1773764107460,
- "om_x100b54a3a3782938b379c9a1da0010b": 1773764315828,
- "om_x100b54a3b60634a0b2910ed3595f0aa": 1773764493305,
- "om_x100b54955fb54964c4e180ded6a68b3": 1773801752225,
- "om_x100b54955a75ccb4c11c3b4070aa4fd": 1773801804425,
- "om_x100b54951cb128bcc361c4fb7da8c71": 1773802792386,
- "om_x100b5498aaee84a0c451ce70c732daf": 1773850178765,
- "om_x100b5498a8e104a8c37682c5c9f2e15": 1773850211313
+ "om_x100b548be4a02ca4b33d43c7a3208be": 1773927079628,
+ "om_x100b548be25df0b0b22cdf708250b00": 1773927113615,
+ "om_x100b548be03aaca4b393638a734446f": 1773927152061,
+ "om_x100b548bfc0824a4b364c8c0c8977d1": 1773927212950,
+ "om_x100b548bfd70e48cb22d0cbc00050f8": 1773927228439,
+ "om_x100b548bf82dbca0b2c751a1b709843": 1773927278588,
+ "om_x100b548b8e22dcacb2d8d587bb7b14e": 1773927439644,
+ "om_x100b54f4b7f294a0c45989f17b70fb7": 1773932436549,
+ "om_x100b54f5454c14a4c42e9b6c6ea2cd3": 1773932728615,
+ "om_x100b54f64b72d4b4c44dfcb4a5b55ab": 1773936732479,
+ "om_x100b54f644fc18a4c3e0e0a4d345c50": 1773936803575,
+ "om_x100b54f654df8534c4ee3346acd1bcd": 1773937057577,
+ "om_x100b54f6091f78a4c4d71668a805f7d": 1773937789758,
+ "om_x100b54f6cf31b4a0c453538202af189": 1773938720210,
+ "om_x100b54f6d65a48a4c4e7a9045781192": 1773939082098,
+ "om_x100b54f6d48980a0c2cc7e00d22cd8e": 1773939108667
}
diff --git a/identity/device-auth.json b/identity/device-auth.json
index 713caf9..32eb46b 100644
--- a/identity/device-auth.json
+++ b/identity/device-auth.json
@@ -10,7 +10,7 @@
"operator.approvals",
"operator.pairing"
],
- "updatedAtMs": 1773857416774
+ "updatedAtMs": 1773943213908
}
}
}
diff --git a/logs/commands.log b/logs/commands.log
index 75fe861..f5a563a 100644
--- a/logs/commands.log
+++ b/logs/commands.log
@@ -60,3 +60,5 @@
{"timestamp":"2026-03-17T16:06:09.290Z","action":"new","sessionKey":"agent:knowledge-base:main","senderId":"ou_9351c287f42069fa30366ac641d24df7","source":"feishu"}
{"timestamp":"2026-03-18T02:42:32.742Z","action":"new","sessionKey":"agent:knowledge-base:main","senderId":"ou_9351c287f42069fa30366ac641d24df7","source":"feishu"}
{"timestamp":"2026-03-18T16:09:39.151Z","action":"new","sessionKey":"agent:knowledge-base:main","senderId":"ou_9351c287f42069fa30366ac641d24df7","source":"feishu"}
+{"timestamp":"2026-03-19T15:00:36.886Z","action":"new","sessionKey":"agent:knowledge-base:main","senderId":"ou_9351c287f42069fa30366ac641d24df7","source":"feishu"}
+{"timestamp":"2026-03-19T16:12:12.817Z","action":"new","sessionKey":"agent:knowledge-base:main","senderId":"ou_9351c287f42069fa30366ac641d24df7","source":"feishu"}
diff --git a/skills/kb_workflow/.env.example b/skills/kb_workflow/.env.example
index dc8e79b..283b878 100644
--- a/skills/kb_workflow/.env.example
+++ b/skills/kb_workflow/.env.example
@@ -12,3 +12,6 @@ export KB_BASE_PATH="~/work/knowledge-base"
# 可选: Git 配置
# export KB_GIT_USER="Your Name"
# export KB_GIT_EMAIL="your@email.com"
+
+# 可选: 工作流行为配置
+# export KB_OVERWRITE_ARCHIVES="false" # 非交互模式下是否覆盖已存在的归档文件(true/false)
diff --git a/skills/kb_workflow/__pycache__/__init__.cpython-312.pyc b/skills/kb_workflow/__pycache__/__init__.cpython-312.pyc
index 135bf9e..fcfe64b 100644
Binary files a/skills/kb_workflow/__pycache__/__init__.cpython-312.pyc and b/skills/kb_workflow/__pycache__/__init__.cpython-312.pyc differ
diff --git a/skills/kb_workflow/__pycache__/__main__.cpython-312.pyc b/skills/kb_workflow/__pycache__/__main__.cpython-312.pyc
index 43a557e..a2df455 100644
Binary files a/skills/kb_workflow/__pycache__/__main__.cpython-312.pyc and b/skills/kb_workflow/__pycache__/__main__.cpython-312.pyc differ
diff --git a/skills/kb_workflow/__pycache__/cli.cpython-312.pyc b/skills/kb_workflow/__pycache__/cli.cpython-312.pyc
index 9333d1c..93f868e 100644
Binary files a/skills/kb_workflow/__pycache__/cli.cpython-312.pyc and b/skills/kb_workflow/__pycache__/cli.cpython-312.pyc differ
diff --git a/skills/kb_workflow/auth/__pycache__/__init__.cpython-312.pyc b/skills/kb_workflow/auth/__pycache__/__init__.cpython-312.pyc
index 9586cdd..abed6ec 100644
Binary files a/skills/kb_workflow/auth/__pycache__/__init__.cpython-312.pyc and b/skills/kb_workflow/auth/__pycache__/__init__.cpython-312.pyc differ
diff --git a/skills/kb_workflow/auth/__pycache__/zhihu_login.cpython-312.pyc b/skills/kb_workflow/auth/__pycache__/zhihu_login.cpython-312.pyc
index 9143470..55283bd 100644
Binary files a/skills/kb_workflow/auth/__pycache__/zhihu_login.cpython-312.pyc and b/skills/kb_workflow/auth/__pycache__/zhihu_login.cpython-312.pyc differ
diff --git a/skills/kb_workflow/cli.py b/skills/kb_workflow/cli.py
index ca2fe0f..d8440a1 100644
--- a/skills/kb_workflow/cli.py
+++ b/skills/kb_workflow/cli.py
@@ -49,7 +49,7 @@ def process_command(
from .pipeline.pipeline import Pipeline
from .pipeline.processors import (
ASRProcessor, AnalyzeProcessor, ArchiveProcessor,
- DownloadProcessor, ParseProcessor
+ DownloadProcessor, ParseProcessor, SRTProcessor
)
from .pipeline.schemas import PipelineConfig
@@ -63,13 +63,14 @@ def process_command(
ParseProcessor(),
DownloadProcessor(),
ASRProcessor(),
- AnalyzeProcessor(),
+ SRTProcessor(), # B站视频使用
+ AnalyzeProcessor(), # 知乎/文章使用
ArchiveProcessor(),
]
# Create pipeline config
config = PipelineConfig(
- steps=["parse", "download", "asr", "analyze", "archive"],
+ steps=["parse", "download", "asr", "srt", "analyze", "archive"],
output_dir=out_dir,
skip_completed=skip_completed,
)
@@ -165,7 +166,7 @@ def login_command(
result = login.login_by_qrcode()
if result.success and result.cookie_str:
manager.save_cookie(result.cookie_str)
- return result.model_dump()
+ return result.model_dump(mode='json')
except Exception as e:
return {"success": False, "error": str(e)}
diff --git a/skills/kb_workflow/crawler/__pycache__/__init__.cpython-312.pyc b/skills/kb_workflow/crawler/__pycache__/__init__.cpython-312.pyc
index f098ea1..2e8f7b3 100644
Binary files a/skills/kb_workflow/crawler/__pycache__/__init__.cpython-312.pyc and b/skills/kb_workflow/crawler/__pycache__/__init__.cpython-312.pyc differ
diff --git a/skills/kb_workflow/crawler/__pycache__/config.cpython-312.pyc b/skills/kb_workflow/crawler/__pycache__/config.cpython-312.pyc
index c7577fe..0695e00 100644
Binary files a/skills/kb_workflow/crawler/__pycache__/config.cpython-312.pyc and b/skills/kb_workflow/crawler/__pycache__/config.cpython-312.pyc differ
diff --git a/skills/kb_workflow/crawler/__pycache__/models.cpython-312.pyc b/skills/kb_workflow/crawler/__pycache__/models.cpython-312.pyc
index 7ff5237..daccb74 100644
Binary files a/skills/kb_workflow/crawler/__pycache__/models.cpython-312.pyc and b/skills/kb_workflow/crawler/__pycache__/models.cpython-312.pyc differ
diff --git a/skills/kb_workflow/crawler/__pycache__/proxy_pool.cpython-312.pyc b/skills/kb_workflow/crawler/__pycache__/proxy_pool.cpython-312.pyc
index 4b37a08..28fd0ca 100644
Binary files a/skills/kb_workflow/crawler/__pycache__/proxy_pool.cpython-312.pyc and b/skills/kb_workflow/crawler/__pycache__/proxy_pool.cpython-312.pyc differ
diff --git a/skills/kb_workflow/crawler/__pycache__/zhihu_crawler.cpython-312.pyc b/skills/kb_workflow/crawler/__pycache__/zhihu_crawler.cpython-312.pyc
index 8b45c2f..ab455b7 100644
Binary files a/skills/kb_workflow/crawler/__pycache__/zhihu_crawler.cpython-312.pyc and b/skills/kb_workflow/crawler/__pycache__/zhihu_crawler.cpython-312.pyc differ
diff --git a/skills/kb_workflow/crawler/stealth/__pycache__/__init__.cpython-312.pyc b/skills/kb_workflow/crawler/stealth/__pycache__/__init__.cpython-312.pyc
index 70a8de0..efdc635 100644
Binary files a/skills/kb_workflow/crawler/stealth/__pycache__/__init__.cpython-312.pyc and b/skills/kb_workflow/crawler/stealth/__pycache__/__init__.cpython-312.pyc differ
diff --git a/skills/kb_workflow/crawler/stealth/__pycache__/stealth_config.cpython-312.pyc b/skills/kb_workflow/crawler/stealth/__pycache__/stealth_config.cpython-312.pyc
index c047be4..88d3160 100644
Binary files a/skills/kb_workflow/crawler/stealth/__pycache__/stealth_config.cpython-312.pyc and b/skills/kb_workflow/crawler/stealth/__pycache__/stealth_config.cpython-312.pyc differ
diff --git a/skills/kb_workflow/deep_analyzer/__init__.py b/skills/kb_workflow/deep_analyzer/__init__.py
index 2f947ce..4b06248 100644
--- a/skills/kb_workflow/deep_analyzer/__init__.py
+++ b/skills/kb_workflow/deep_analyzer/__init__.py
@@ -90,22 +90,35 @@ class SimpleAIClient:
def _call_zhipu(self, prompt: str, max_tokens: int, api_key: str, model: str, base_url: str) -> str:
import requests
+ import sys
+
base_url = base_url or "https://open.bigmodel.cn/api/coding/paas/v4"
url = f"{base_url}/chat/completions"
headers = {"Authorization": f"Bearer {api_key}"}
- payload = {"model": model, "messages": [{"role": "user", "content": prompt}], "max_tokens": max_tokens}
+ # GLM-4.5/4.6/4.7/5 默认启用thinking mode,使用 thinking.type 参数禁用
+ payload = {
+ "model": model,
+ "messages": [{"role": "user", "content": prompt}],
+ "max_tokens": max_tokens,
+ "thinking": {
+ "type": "disabled" # 禁用思考模式,获取直接响应
+ }
+ }
response = requests.post(url, headers=headers, json=payload, timeout=600)
response.raise_for_status()
data = response.json()
message = data.get('choices', [{}])[0].get('message', {})
content = message.get('content', '')
- reasoning = message.get('reasoning_content', '')
- if not content and reasoning:
- sentences = reasoning.strip().split('。')
- useful = [s.strip() for s in sentences if s.strip() and len(s.strip()) > 10]
- if useful:
- return '。'.join(useful[-3:]) + '。'
- return reasoning.strip()[:500]
+
+ if not content:
+ # 如果仍然为空,检查是否有reasoning_content(用于调试)
+ reasoning = message.get('reasoning_content', '')
+ if reasoning:
+ print(f"[DEBUG] Zhipu API返回空的content,但有reasoning_content。", file=sys.stderr)
+ print(f"[DEBUG] 这可能表示thinking.type参数未生效或模型不支持。", file=sys.stderr)
+ print(f"[DEBUG] reasoning_content前200字符: {reasoning[:200]}", file=sys.stderr)
+ raise ValueError(f"Zhipu API返回空内容。请检查API配置和模型参数。响应数据: {data}")
+
return content
def _call_minimax(self, prompt: str, max_tokens: int, api_key: str, model: str, base_url: str) -> str:
@@ -129,6 +142,13 @@ class DeepAnalyzer:
def __init__(self, ai_client: SimpleAIClient = None):
self.ai_client = ai_client or SimpleAIClient()
+ # 弃用警告:B站视频应使用 SRTProcessor
+ import logging
+ logger = logging.getLogger(__name__)
+ logger.warning(
+ "DeepAnalyzer is deprecated for Bilibili video processing. "
+ "Use SRTProcessor instead."
+ )
def analyze(self, metadata: Dict[str, Any], content: Dict[str, Any], content_type: str = "video") -> AnalysisResult:
"""一次性调用 AI 生成完整分析报告
diff --git a/skills/kb_workflow/deep_analyzer/__pycache__/__init__.cpython-312.pyc b/skills/kb_workflow/deep_analyzer/__pycache__/__init__.cpython-312.pyc
index d9d9811..b4bd08e 100644
Binary files a/skills/kb_workflow/deep_analyzer/__pycache__/__init__.cpython-312.pyc and b/skills/kb_workflow/deep_analyzer/__pycache__/__init__.cpython-312.pyc differ
diff --git a/skills/kb_workflow/metadata/__pycache__/__init__.cpython-312.pyc b/skills/kb_workflow/metadata/__pycache__/__init__.cpython-312.pyc
index 6c78a3a..ab88287 100644
Binary files a/skills/kb_workflow/metadata/__pycache__/__init__.cpython-312.pyc and b/skills/kb_workflow/metadata/__pycache__/__init__.cpython-312.pyc differ
diff --git a/skills/kb_workflow/metadata/__pycache__/bilibili_extractor.cpython-312.pyc b/skills/kb_workflow/metadata/__pycache__/bilibili_extractor.cpython-312.pyc
index d2f6498..c1b973e 100644
Binary files a/skills/kb_workflow/metadata/__pycache__/bilibili_extractor.cpython-312.pyc and b/skills/kb_workflow/metadata/__pycache__/bilibili_extractor.cpython-312.pyc differ
diff --git a/skills/kb_workflow/metadata/__pycache__/markdown_converter.cpython-312.pyc b/skills/kb_workflow/metadata/__pycache__/markdown_converter.cpython-312.pyc
index e5585ea..a29235d 100644
Binary files a/skills/kb_workflow/metadata/__pycache__/markdown_converter.cpython-312.pyc and b/skills/kb_workflow/metadata/__pycache__/markdown_converter.cpython-312.pyc differ
diff --git a/skills/kb_workflow/metadata/__pycache__/zhihu_extractor.cpython-312.pyc b/skills/kb_workflow/metadata/__pycache__/zhihu_extractor.cpython-312.pyc
index a2e251f..e102397 100644
Binary files a/skills/kb_workflow/metadata/__pycache__/zhihu_extractor.cpython-312.pyc and b/skills/kb_workflow/metadata/__pycache__/zhihu_extractor.cpython-312.pyc differ
diff --git a/skills/kb_workflow/pipeline/__pycache__/__init__.cpython-312.pyc b/skills/kb_workflow/pipeline/__pycache__/__init__.cpython-312.pyc
index 1e74f3c..99e0733 100644
Binary files a/skills/kb_workflow/pipeline/__pycache__/__init__.cpython-312.pyc and b/skills/kb_workflow/pipeline/__pycache__/__init__.cpython-312.pyc differ
diff --git a/skills/kb_workflow/pipeline/__pycache__/config.cpython-312.pyc b/skills/kb_workflow/pipeline/__pycache__/config.cpython-312.pyc
index b10ce9a..5688789 100644
Binary files a/skills/kb_workflow/pipeline/__pycache__/config.cpython-312.pyc and b/skills/kb_workflow/pipeline/__pycache__/config.cpython-312.pyc differ
diff --git a/skills/kb_workflow/pipeline/__pycache__/errors.cpython-312.pyc b/skills/kb_workflow/pipeline/__pycache__/errors.cpython-312.pyc
index 90ff78e..5cd8e69 100644
Binary files a/skills/kb_workflow/pipeline/__pycache__/errors.cpython-312.pyc and b/skills/kb_workflow/pipeline/__pycache__/errors.cpython-312.pyc differ
diff --git a/skills/kb_workflow/pipeline/__pycache__/idempotency.cpython-312.pyc b/skills/kb_workflow/pipeline/__pycache__/idempotency.cpython-312.pyc
index c7fd3e3..a1c1db0 100644
Binary files a/skills/kb_workflow/pipeline/__pycache__/idempotency.cpython-312.pyc and b/skills/kb_workflow/pipeline/__pycache__/idempotency.cpython-312.pyc differ
diff --git a/skills/kb_workflow/pipeline/__pycache__/link_parser.cpython-312.pyc b/skills/kb_workflow/pipeline/__pycache__/link_parser.cpython-312.pyc
index bb7f393..07eceb8 100644
Binary files a/skills/kb_workflow/pipeline/__pycache__/link_parser.cpython-312.pyc and b/skills/kb_workflow/pipeline/__pycache__/link_parser.cpython-312.pyc differ
diff --git a/skills/kb_workflow/pipeline/__pycache__/pipeline.cpython-312.pyc b/skills/kb_workflow/pipeline/__pycache__/pipeline.cpython-312.pyc
index 12dac89..0135d0a 100644
Binary files a/skills/kb_workflow/pipeline/__pycache__/pipeline.cpython-312.pyc and b/skills/kb_workflow/pipeline/__pycache__/pipeline.cpython-312.pyc differ
diff --git a/skills/kb_workflow/pipeline/__pycache__/retry.cpython-312.pyc b/skills/kb_workflow/pipeline/__pycache__/retry.cpython-312.pyc
index 1acd865..21536d2 100644
Binary files a/skills/kb_workflow/pipeline/__pycache__/retry.cpython-312.pyc and b/skills/kb_workflow/pipeline/__pycache__/retry.cpython-312.pyc differ
diff --git a/skills/kb_workflow/pipeline/__pycache__/schemas.cpython-312.pyc b/skills/kb_workflow/pipeline/__pycache__/schemas.cpython-312.pyc
index fdaf977..ceccc30 100644
Binary files a/skills/kb_workflow/pipeline/__pycache__/schemas.cpython-312.pyc and b/skills/kb_workflow/pipeline/__pycache__/schemas.cpython-312.pyc differ
diff --git a/skills/kb_workflow/pipeline/__pycache__/state.cpython-312.pyc b/skills/kb_workflow/pipeline/__pycache__/state.cpython-312.pyc
index 569b93e..90f9f2a 100644
Binary files a/skills/kb_workflow/pipeline/__pycache__/state.cpython-312.pyc and b/skills/kb_workflow/pipeline/__pycache__/state.cpython-312.pyc differ
diff --git a/skills/kb_workflow/pipeline/config.py b/skills/kb_workflow/pipeline/config.py
index 855a113..3422b5b 100644
--- a/skills/kb_workflow/pipeline/config.py
+++ b/skills/kb_workflow/pipeline/config.py
@@ -58,6 +58,11 @@ class KBSettings(BaseSettings):
default=SecretStr(""), description="B站 Cookie (for video download)"
)
+ # 工作流行为配置
+ overwrite_archives: bool = Field(
+ default=False, description="覆盖已存在的归档文件(非交互模式)"
+ )
+
@lru_cache
def get_settings() -> KBSettings:
diff --git a/skills/kb_workflow/pipeline/pipeline.py b/skills/kb_workflow/pipeline/pipeline.py
index 60d57fc..f96f62d 100644
--- a/skills/kb_workflow/pipeline/pipeline.py
+++ b/skills/kb_workflow/pipeline/pipeline.py
@@ -21,7 +21,7 @@ from typing import Dict, List, Optional
from kb_workflow.pipeline.config import get_settings
from kb_workflow.pipeline.link_parser import LinkParser, ParsedLink
from kb_workflow.pipeline.processors.base import BaseProcessor
-from kb_workflow.pipeline.schemas import PipelineConfig, PipelineInput, PipelineOutput
+from kb_workflow.pipeline.schemas import PipelineConfig, PipelineInput, PipelineOutput, SRTInput
logger = logging.getLogger(__name__)
@@ -174,6 +174,20 @@ class Pipeline:
logger.info(f"Skipping ASR for text content type: {parsed.content_type}")
continue
+ # B站视频:跳过 analyze 步骤,使用 srt 步骤替代
+ is_bilibili_video = (
+ parsed.platform == "bilibili" and
+ parsed.content_type == "video"
+ )
+ if step_name == "analyze" and is_bilibili_video:
+ logger.info(f"Skipping analyze for Bilibili video, using SRT processor instead")
+ continue
+
+ # B站文章/知乎等:跳过 srt 步骤
+ if step_name == "srt" and not is_bilibili_video:
+ logger.info(f"Skipping SRT for non-Bilibili-video content: {parsed.platform}/{parsed.content_type}")
+ continue
+
processor = self.processors[step_name]
# Mark step as started
@@ -228,10 +242,26 @@ class Pipeline:
content_type=parsed.content_type,
output_file=str(content_dir / "analysis.md"),
)
+ elif step_name == "srt":
+ # SRT needs srt file from ASR step and metadata
+ download_result = results.get("download", {})
+ asr_result = results.get("asr", {})
+ metadata_file = download_result.get("metadata_file", "")
+ # SRT file from ASR output
+ srt_file = asr_result.get("output_files", {}).get("srt", "")
+
+ step_input = SRTInput(
+ content_id=parsed.content_id,
+ platform=parsed.platform,
+ srt_file=srt_file,
+ metadata_file=metadata_file,
+ output_file=str(content_dir / "analysis.md"),
+ content_type=parsed.content_type,
+ )
elif step_name == "archive":
- # Archive needs analysis file from analyze step
- analyze_result = results.get("analyze", {})
- analysis_file = analyze_result.get("analysis_file", "")
+ # Archive needs analysis file from analyze step (or srt step for Bilibili video)
+ analyze_result = results.get("analyze", {}) or results.get("srt", {})
+ analysis_file = analyze_result.get("analysis_file", "") or analyze_result.get("output_file", "")
step_input = ArchiveInput(
content_id=parsed.content_id,
@@ -261,7 +291,7 @@ class Pipeline:
# Add output fields to results
for field_name in ["metadata_file", "content_file", "title", "author",
"analysis_file", "summary", "output_files",
- "archive_file", "skipped"]:
+ "archive_file", "skipped", "output_file", "paragraphs", "tags", "corrected"]:
if hasattr(step_output, field_name):
results[step_name][field_name] = getattr(step_output, field_name)
@@ -397,3 +427,14 @@ class Pipeline:
"analysis_file": str(analysis_path),
}
logger.info(f"Found existing analysis: {analysis_path}")
+
+ elif step_name == "srt":
+ # Scan for SRT output (same file as analyze - analysis.md)
+ analysis_path = content_dir / "analysis.md"
+ if analysis_path.exists():
+ results["srt"] = {
+ "success": True,
+ "error_message": None,
+ "output_file": str(analysis_path),
+ }
+ logger.info(f"Found existing SRT output: {analysis_path}")
diff --git a/skills/kb_workflow/pipeline/platforms/__pycache__/__init__.cpython-312.pyc b/skills/kb_workflow/pipeline/platforms/__pycache__/__init__.cpython-312.pyc
index cbe3eae..fe3e842 100644
Binary files a/skills/kb_workflow/pipeline/platforms/__pycache__/__init__.cpython-312.pyc and b/skills/kb_workflow/pipeline/platforms/__pycache__/__init__.cpython-312.pyc differ
diff --git a/skills/kb_workflow/pipeline/platforms/__pycache__/base.cpython-312.pyc b/skills/kb_workflow/pipeline/platforms/__pycache__/base.cpython-312.pyc
index 39e5d42..2a395db 100644
Binary files a/skills/kb_workflow/pipeline/platforms/__pycache__/base.cpython-312.pyc and b/skills/kb_workflow/pipeline/platforms/__pycache__/base.cpython-312.pyc differ
diff --git a/skills/kb_workflow/pipeline/platforms/__pycache__/bilibili.cpython-312.pyc b/skills/kb_workflow/pipeline/platforms/__pycache__/bilibili.cpython-312.pyc
index 2c4ec8e..45e8bae 100644
Binary files a/skills/kb_workflow/pipeline/platforms/__pycache__/bilibili.cpython-312.pyc and b/skills/kb_workflow/pipeline/platforms/__pycache__/bilibili.cpython-312.pyc differ
diff --git a/skills/kb_workflow/pipeline/platforms/__pycache__/zhihu.cpython-312.pyc b/skills/kb_workflow/pipeline/platforms/__pycache__/zhihu.cpython-312.pyc
index 06650be..d108772 100644
Binary files a/skills/kb_workflow/pipeline/platforms/__pycache__/zhihu.cpython-312.pyc and b/skills/kb_workflow/pipeline/platforms/__pycache__/zhihu.cpython-312.pyc differ
diff --git a/skills/kb_workflow/pipeline/processors/__init__.py b/skills/kb_workflow/pipeline/processors/__init__.py
index 5354519..b7f91d0 100644
--- a/skills/kb_workflow/pipeline/processors/__init__.py
+++ b/skills/kb_workflow/pipeline/processors/__init__.py
@@ -5,6 +5,7 @@ This module contains processors for various content transformation tasks:
- ParseProcessor: URL parsing for various platforms
- DownloadProcessor: Metadata and content downloading
- ASRProcessor: Audio-to-text conversion (subtitle download + Whisper.cpp)
+- SRTProcessor: SRT subtitle processing (parse, segment, correct, markdown)
- AnalyzeProcessor: AI-powered content analysis using DeepAnalyzer
- ArchiveProcessor: Copy analysis reports to archive directory
"""
@@ -13,6 +14,7 @@ from kb_workflow.pipeline.processors.base import BaseProcessor
from kb_workflow.pipeline.processors.parse import ParseProcessor
from kb_workflow.pipeline.processors.download import DownloadProcessor
from kb_workflow.pipeline.processors.asr import ASRProcessor
+from kb_workflow.pipeline.processors.srt import SRTProcessor
from kb_workflow.pipeline.processors.analyze import AnalyzeProcessor
from kb_workflow.pipeline.processors.archive import ArchiveProcessor
@@ -21,6 +23,7 @@ __all__ = [
"ParseProcessor",
"DownloadProcessor",
"ASRProcessor",
+ "SRTProcessor",
"AnalyzeProcessor",
"ArchiveProcessor",
]
diff --git a/skills/kb_workflow/pipeline/processors/__pycache__/__init__.cpython-312.pyc b/skills/kb_workflow/pipeline/processors/__pycache__/__init__.cpython-312.pyc
index f701381..7684ca7 100644
Binary files a/skills/kb_workflow/pipeline/processors/__pycache__/__init__.cpython-312.pyc and b/skills/kb_workflow/pipeline/processors/__pycache__/__init__.cpython-312.pyc differ
diff --git a/skills/kb_workflow/pipeline/processors/__pycache__/analyze.cpython-312.pyc b/skills/kb_workflow/pipeline/processors/__pycache__/analyze.cpython-312.pyc
index ae02829..7de22a1 100644
Binary files a/skills/kb_workflow/pipeline/processors/__pycache__/analyze.cpython-312.pyc and b/skills/kb_workflow/pipeline/processors/__pycache__/analyze.cpython-312.pyc differ
diff --git a/skills/kb_workflow/pipeline/processors/__pycache__/archive.cpython-312.pyc b/skills/kb_workflow/pipeline/processors/__pycache__/archive.cpython-312.pyc
index 38dd7cd..0195504 100644
Binary files a/skills/kb_workflow/pipeline/processors/__pycache__/archive.cpython-312.pyc and b/skills/kb_workflow/pipeline/processors/__pycache__/archive.cpython-312.pyc differ
diff --git a/skills/kb_workflow/pipeline/processors/__pycache__/asr.cpython-312.pyc b/skills/kb_workflow/pipeline/processors/__pycache__/asr.cpython-312.pyc
index 073a6ae..13283d5 100644
Binary files a/skills/kb_workflow/pipeline/processors/__pycache__/asr.cpython-312.pyc and b/skills/kb_workflow/pipeline/processors/__pycache__/asr.cpython-312.pyc differ
diff --git a/skills/kb_workflow/pipeline/processors/__pycache__/base.cpython-312.pyc b/skills/kb_workflow/pipeline/processors/__pycache__/base.cpython-312.pyc
index 64bd84b..5e229ee 100644
Binary files a/skills/kb_workflow/pipeline/processors/__pycache__/base.cpython-312.pyc and b/skills/kb_workflow/pipeline/processors/__pycache__/base.cpython-312.pyc differ
diff --git a/skills/kb_workflow/pipeline/processors/__pycache__/download.cpython-312.pyc b/skills/kb_workflow/pipeline/processors/__pycache__/download.cpython-312.pyc
index 5000219..65c9098 100644
Binary files a/skills/kb_workflow/pipeline/processors/__pycache__/download.cpython-312.pyc and b/skills/kb_workflow/pipeline/processors/__pycache__/download.cpython-312.pyc differ
diff --git a/skills/kb_workflow/pipeline/processors/__pycache__/parse.cpython-312.pyc b/skills/kb_workflow/pipeline/processors/__pycache__/parse.cpython-312.pyc
index 46fe483..c9795a4 100644
Binary files a/skills/kb_workflow/pipeline/processors/__pycache__/parse.cpython-312.pyc and b/skills/kb_workflow/pipeline/processors/__pycache__/parse.cpython-312.pyc differ
diff --git a/skills/kb_workflow/pipeline/processors/__pycache__/srt.cpython-312.pyc b/skills/kb_workflow/pipeline/processors/__pycache__/srt.cpython-312.pyc
new file mode 100644
index 0000000..a8d8f69
Binary files /dev/null and b/skills/kb_workflow/pipeline/processors/__pycache__/srt.cpython-312.pyc differ
diff --git a/skills/kb_workflow/pipeline/processors/archive.py b/skills/kb_workflow/pipeline/processors/archive.py
index 9866a52..e9c20ac 100644
--- a/skills/kb_workflow/pipeline/processors/archive.py
+++ b/skills/kb_workflow/pipeline/processors/archive.py
@@ -46,6 +46,8 @@ class ArchiveProcessor(BaseProcessor):
Archive 处理结果
"""
try:
+ import sys
+
# Step 1: 检查是否已完成(幂等执行)
if self.state_manager:
state = self.state_manager.load()
@@ -73,7 +75,9 @@ class ArchiveProcessor(BaseProcessor):
platform_dir, input_data.content_id
)
if existing_file:
- should_overwrite = self._ask_overwrite(existing_file)
+ # 检测是否为非交互式环境
+ is_non_interactive = not sys.stdin.isatty()
+ should_overwrite = self._ask_overwrite(existing_file, is_non_interactive)
if not should_overwrite:
logger.info(f"User chose not to overwrite, skipping archive")
return ArchiveOutput(
@@ -143,17 +147,28 @@ class ArchiveProcessor(BaseProcessor):
matches = list(platform_dir.glob(pattern))
return matches[0] if matches else None
- def _ask_overwrite(self, existing_file: Path) -> bool:
+ def _ask_overwrite(self, existing_file: Path, is_non_interactive: bool = False) -> bool:
"""询问用户是否覆盖已存在的文件
使用 rich.prompt.Confirm 模式(与 Pipeline._ask_rerun_step 相同)
Args:
existing_file: 已存在的文件路径
+ is_non_interactive: 是否为非交互式环境(stdin不是tty)
Returns:
True 如果用户选择覆盖
"""
+ # 非交互式模式:使用配置决定
+ if is_non_interactive:
+ if self.settings.overwrite_archives:
+ logger.info(f"Non-interactive mode: Overwriting existing archive (KB_OVERWRITE_ARCHIVES=true): {existing_file.name}")
+ return True
+ else:
+ logger.info(f"Non-interactive mode: Skipping existing archive (KB_OVERWRITE_ARCHIVES=false): {existing_file.name}")
+ return False
+
+ # 交互式模式:询问用户
try:
from rich.console import Console
from rich.prompt import Confirm
diff --git a/skills/kb_workflow/pipeline/processors/srt.py b/skills/kb_workflow/pipeline/processors/srt.py
new file mode 100644
index 0000000..2723e38
--- /dev/null
+++ b/skills/kb_workflow/pipeline/processors/srt.py
@@ -0,0 +1,333 @@
+"""SRTProcessor - B站字幕处理处理器
+
+将 B站 SRT 字幕文件转换为简化的 Markdown 文档。
+
+处理流程:
+1. 解析 SRT 文件(调用 parser.parse_srt_file)
+2. 语义分段(调用 segmenter.segment)
+3. ASR 纠错(调用 corrector.correct,可选)
+4. 生成 Markdown 文件
+
+简化版:不做深度观点分析,只保留字幕内容和元数据。
+"""
+
+from __future__ import annotations
+
+import json
+import logging
+from datetime import datetime
+from pathlib import Path
+from typing import Dict, Any, List, Optional
+
+from kb_workflow.pipeline.config import get_settings
+from kb_workflow.pipeline.processors.base import BaseProcessor
+from kb_workflow.pipeline.schemas import SRTInput, SRTOutput
+from kb_workflow.pipeline.state import WorkflowStateManager
+from kb_workflow.srt.parser import parse_srt_file
+from kb_workflow.srt.segmenter import SemanticSegmenter
+
+logger = logging.getLogger(__name__)
+
+
+class SRTProcessor(BaseProcessor):
+ """SRT 字幕处理器
+
+ 将 SRT 字幕文件转换为简化的 Markdown 文档。
+ 继承 BaseProcessor,遵循 Pipeline 处理器模式。
+
+ Attributes:
+ STEP_NAME: 步骤名称 "srt"
+ settings: KB 配置
+ state_manager: 状态管理器
+ segmenter: 语义分段器
+ corrector: ASR 纠错器(可选)
+ """
+
+ STEP_NAME = "srt"
+
+ def __init__(
+ self,
+ state_manager: Optional[WorkflowStateManager] = None,
+ enable_correction: bool = True
+ ):
+ """初始化 SRT 处理器
+
+ Args:
+ state_manager: 状态管理器
+ enable_correction: 是否启用 ASR 纠错(默认 True)
+ """
+ super().__init__(state_manager)
+ self.enable_correction = enable_correction
+ self.segmenter = SemanticSegmenter()
+ self.corrector = None
+
+ # 延迟初始化纠错器
+ if enable_correction:
+ try:
+ from kb_workflow.srt.corrector import ASRCorrector
+ from kb_workflow.deep_analyzer import SimpleAIClient
+
+ # 尝试创建 AI 客户端
+ ai_client = SimpleAIClient()
+ if ai_client.api_key:
+ self.corrector = ASRCorrector(ai_client=ai_client)
+ logger.info("ASR corrector initialized")
+ else:
+ logger.info("AI API key not configured, ASR correction disabled")
+ except Exception as e:
+ logger.warning(f"Failed to initialize ASR corrector: {e}")
+
+ async def process(self, input_data: SRTInput) -> SRTOutput:
+ """处理 SRT 文件
+
+ Args:
+ input_data: SRT 输入数据
+
+ Returns:
+ SRT 处理结果
+
+ Raises:
+ Exception: 处理失败时抛出异常
+ """
+ try:
+ # 记录开始状态
+ if self.state_manager:
+ state = self.state_manager.load()
+ if state:
+ state.mark_step_started(self.STEP_NAME)
+ self.state_manager.save(state)
+
+ # Step 1: 解析 SRT 文件
+ logger.info(f"Parsing SRT file: {input_data.srt_file}")
+ segments = parse_srt_file(Path(input_data.srt_file))
+ logger.info(f"Parsed {len(segments)} segments")
+
+ # Step 2: 语义分段
+ logger.info("Segmenting subtitles into paragraphs")
+ paragraphs = self.segmenter.segment(segments)
+ logger.info(f"Generated {len(paragraphs)} paragraphs")
+
+ # Step 3: ASR 纠错(可选)
+ corrected = False
+ if self.corrector and paragraphs:
+ logger.info("Applying ASR correction")
+ corrected_paragraphs = []
+ for para in paragraphs:
+ corrected_para = self.corrector.correct(para)
+ corrected_paragraphs.append(corrected_para)
+ if corrected_para != para:
+ corrected = True
+ paragraphs = corrected_paragraphs
+ logger.info(f"ASR correction applied: {corrected}")
+
+ # Step 4: 加载元数据
+ metadata = self._load_metadata(input_data.metadata_file)
+
+ # Step 5: 生成 Markdown
+ logger.info(f"Generating Markdown: {input_data.output_file}")
+ tags = self._extract_tags(paragraphs)
+ self._generate_markdown(
+ output_file=Path(input_data.output_file),
+ metadata=metadata,
+ paragraphs=paragraphs,
+ tags=tags
+ )
+
+ # 记录完成状态
+ if self.state_manager:
+ state = self.state_manager.load()
+ if state:
+ summary = f"paragraphs={len(paragraphs)}, corrected={corrected}"
+ state.mark_step_completed(self.STEP_NAME, summary)
+ self.state_manager.save(state)
+
+ return SRTOutput(
+ success=True,
+ output_file=str(input_data.output_file),
+ paragraphs=paragraphs,
+ tags=tags,
+ corrected=corrected
+ )
+
+ except Exception as e:
+ logger.error(f"SRT processing failed: {e}")
+ # 记录失败状态
+ if self.state_manager:
+ state = self.state_manager.load()
+ if state:
+ state.mark_step_failed(self.STEP_NAME, str(e))
+ self.state_manager.save(state)
+ raise
+
+ def _load_metadata(self, metadata_file: str) -> Dict[str, Any]:
+ """加载元数据文件
+
+ Args:
+ metadata_file: 元数据文件路径
+
+ Returns:
+ 元数据字典
+ """
+ path = Path(metadata_file)
+ if not path.exists():
+ logger.warning(f"Metadata file not found: {metadata_file}")
+ return {}
+
+ with open(path, 'r', encoding='utf-8') as f:
+ return json.load(f)
+
+ def _extract_tags(self, paragraphs: List[str]) -> List[str]:
+ """从段落中提取标签
+
+ 简单实现:不使用 LLM,只返回默认标签。
+ 未来可以增强为 LLM 提取。
+
+ Args:
+ paragraphs: 段落列表
+
+ Returns:
+ 标签列表
+ """
+ # 简单实现:返回空列表
+ # TODO: 可以使用 LLM 提取标签
+ return []
+
+ def _generate_markdown(
+ self,
+ output_file: Path,
+ metadata: Dict[str, Any],
+ paragraphs: List[str],
+ tags: List[str]
+ ) -> None:
+ """生成 Markdown 文件
+
+ Args:
+ output_file: 输出文件路径
+ metadata: 元数据
+ paragraphs: 段落列表
+ tags: 标签列表
+ """
+ # 确保输出目录存在
+ output_file.parent.mkdir(parents=True, exist_ok=True)
+
+ # 生成 frontmatter
+ frontmatter = self._generate_frontmatter(metadata, tags)
+
+ # 构建内容
+ content_parts = [frontmatter, ""]
+
+ # 添加摘要(可选)
+ if paragraphs:
+ content_parts.append("## 摘要")
+ content_parts.append("")
+ # 使用第一段作为简单摘要
+ first_para = paragraphs[0][:200] if len(paragraphs[0]) > 200 else paragraphs[0]
+ content_parts.append(first_para + ("..." if len(paragraphs[0]) > 200 else ""))
+ content_parts.append("")
+
+ # 添加内容
+ content_parts.append("## 内容")
+ content_parts.append("")
+ for para in paragraphs:
+ content_parts.append(para)
+ content_parts.append("")
+
+ # 写入文件
+ output_file.write_text('\n'.join(content_parts), encoding='utf-8')
+ logger.info(f"Markdown file generated: {output_file}")
+
+ def _generate_frontmatter(self, metadata: Dict[str, Any], tags: List[str]) -> str:
+ """生成 YAML frontmatter
+
+ Args:
+ metadata: 元数据
+ tags: 标签列表
+
+ Returns:
+ YAML frontmatter 字符串
+ """
+ lines = ["---"]
+
+ # 标题
+ title = metadata.get('title', '未命名')
+ lines.append(f'title: "{self._escape_yaml(title)}"')
+
+ # 日期
+ pubtime = metadata.get('pubtime', '') or metadata.get('published_at', '')
+ if pubtime:
+ date_str = str(pubtime)[:10]
+ else:
+ date_str = datetime.now().strftime('%Y-%m-%d')
+ lines.append(f'date: {date_str}')
+
+ # 作者
+ author = metadata.get('author', '未知')
+ lines.append(f'author: {self._escape_yaml(author)}')
+
+ # 平台
+ platform = metadata.get('platform', 'bilibili')
+ lines.append(f'platform: {platform}')
+
+ # 时长(视频)
+ duration = metadata.get('duration', 0)
+ if duration:
+ duration_str = self._format_duration(duration)
+ lines.append(f'duration: {duration_str}')
+
+ # BVID
+ bvid = metadata.get('bvid', '')
+ if bvid:
+ lines.append(f'bvid: {bvid}')
+
+ # URL
+ url = metadata.get('url', '')
+ if url:
+ lines.append(f'url: {url}')
+
+ # 标签
+ if tags:
+ tags_str = ', '.join(f'"{t}"' for t in tags)
+ lines.append(f'tags: [{tags_str}]')
+
+ lines.append("---")
+ return '\n'.join(lines)
+
+ def _escape_yaml(self, text: str) -> str:
+ """转义 YAML 特殊字符
+
+ Args:
+ text: 原始文本
+
+ Returns:
+ 转义后的文本
+ """
+ # 移除零宽度字符
+ invisible_chars = ['\u200b', '\u200c', '\u200d', '\ufeff', '\u00ad']
+ for char in invisible_chars:
+ text = text.replace(char, '')
+
+ # 移除换行符
+ text = text.replace('\n', ' ').strip()
+
+ # 如果包含特殊字符,用引号包裹
+ if any(c in text for c in [':', '"', "'", '\n', '#']):
+ text = text.replace('"', '\\"')
+ return text
+
+ return text
+
+ def _format_duration(self, seconds: int) -> str:
+ """格式化时长
+
+ Args:
+ seconds: 秒数
+
+ Returns:
+ 格式化的时长字符串 (HH:MM:SS 或 MM:SS)
+ """
+ h = seconds // 3600
+ m = (seconds % 3600) // 60
+ s = seconds % 60
+ if h > 0:
+ return f"{h}:{m:02d}:{s:02d}"
+ return f"{m}:{s:02d}"
diff --git a/skills/kb_workflow/pipeline/schemas.py b/skills/kb_workflow/pipeline/schemas.py
index 83a3e02..1f5922a 100644
--- a/skills/kb_workflow/pipeline/schemas.py
+++ b/skills/kb_workflow/pipeline/schemas.py
@@ -444,3 +444,42 @@ class ArchiveOutput(StepOutput):
archive_file: str = Field(default="", description="Path to archived file")
skipped: bool = Field(default=False, description="Whether archiving was skipped")
platform: str = Field(default="", description="Platform name")
+
+
+# =============================================================================
+# SRT Schemas (Phase 12)
+# =============================================================================
+
+
+class SRTInput(StepInput):
+ """SRT 步骤输入
+
+ 用于 SRTProcessor 处理 B站字幕。
+
+ Attributes:
+ srt_file: SRT 字幕文件路径
+ metadata_file: 元数据 JSON 文件路径
+ output_file: 输出 Markdown 文件路径
+ content_type: 内容类型(默认 video)
+ """
+ srt_file: str = Field(..., min_length=1, description="Path to SRT subtitle file")
+ metadata_file: str = Field(..., min_length=1, description="Path to metadata JSON file")
+ output_file: str = Field(..., min_length=1, description="Path to output Markdown file")
+ content_type: str = Field(default="video", description="Content type")
+
+
+class SRTOutput(StepOutput):
+ """SRT 步骤输出
+
+ 继承 StepOutput 的基础字段,添加 SRT 处理特定字段。
+
+ Attributes:
+ output_file: 生成的 Markdown 文件路径
+ paragraphs: 生成的段落列表
+ tags: 提取的标签列表
+ corrected: 是否进行了 ASR 纠错
+ """
+ output_file: str = Field(..., description="Path to generated Markdown file")
+ paragraphs: List[str] = Field(default_factory=list, description="Generated paragraphs")
+ tags: List[str] = Field(default_factory=list, description="Extracted tags")
+ corrected: bool = Field(default=False, description="Whether ASR correction was applied")
diff --git a/skills/kb_workflow/srt/__init__.py b/skills/kb_workflow/srt/__init__.py
new file mode 100644
index 0000000..581c0aa
--- /dev/null
+++ b/skills/kb_workflow/srt/__init__.py
@@ -0,0 +1,27 @@
+"""SRT Processing Module
+
+B站字幕处理模块,包含:
+- SRT 解析器 (parser)
+- 语义分段器 (segmenter)
+- ASR 纠错器 (corrector)
+"""
+
+from kb_workflow.srt.parser import parse_srt, parse_srt_file, Segment
+
+__all__ = [
+ "parse_srt",
+ "parse_srt_file",
+ "Segment",
+ "SemanticSegmenter",
+ "ASRCorrector",
+]
+
+# 延迟导入,避免循环依赖
+def __getattr__(name: str):
+ if name == "SemanticSegmenter":
+ from kb_workflow.srt.segmenter import SemanticSegmenter
+ return SemanticSegmenter
+ if name == "ASRCorrector":
+ from kb_workflow.srt.corrector import ASRCorrector
+ return ASRCorrector
+ raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
diff --git a/skills/kb_workflow/srt/__pycache__/__init__.cpython-312.pyc b/skills/kb_workflow/srt/__pycache__/__init__.cpython-312.pyc
new file mode 100644
index 0000000..c873d85
Binary files /dev/null and b/skills/kb_workflow/srt/__pycache__/__init__.cpython-312.pyc differ
diff --git a/skills/kb_workflow/srt/__pycache__/corrector.cpython-312.pyc b/skills/kb_workflow/srt/__pycache__/corrector.cpython-312.pyc
new file mode 100644
index 0000000..9d3e087
Binary files /dev/null and b/skills/kb_workflow/srt/__pycache__/corrector.cpython-312.pyc differ
diff --git a/skills/kb_workflow/srt/__pycache__/parser.cpython-312.pyc b/skills/kb_workflow/srt/__pycache__/parser.cpython-312.pyc
new file mode 100644
index 0000000..0c1b175
Binary files /dev/null and b/skills/kb_workflow/srt/__pycache__/parser.cpython-312.pyc differ
diff --git a/skills/kb_workflow/srt/__pycache__/segmenter.cpython-312.pyc b/skills/kb_workflow/srt/__pycache__/segmenter.cpython-312.pyc
new file mode 100644
index 0000000..00d6540
Binary files /dev/null and b/skills/kb_workflow/srt/__pycache__/segmenter.cpython-312.pyc differ
diff --git a/skills/kb_workflow/srt/corrector.py b/skills/kb_workflow/srt/corrector.py
new file mode 100644
index 0000000..5609578
--- /dev/null
+++ b/skills/kb_workflow/srt/corrector.py
@@ -0,0 +1,157 @@
+"""ASR Corrector Module
+
+使用 LLM 纠正语音识别错误。复用 SimpleAIClient。
+
+功能:
+- 纠正明显的语音识别错误(同音字、漏字、错字)
+- 分批处理长文本
+- AI 不可用时返回原文
+"""
+
+from __future__ import annotations
+
+import logging
+from typing import Optional, Protocol
+
+logger = logging.getLogger(__name__)
+
+
+class AIClientProtocol(Protocol):
+ """AI 客户端协议"""
+
+ def generate(self, prompt: str, max_tokens: int = 16000) -> str:
+ """生成文本"""
+ ...
+
+
+# 纠错 Prompt 模板
+CORRECTION_PROMPT = """你是一个文本校对专家。以下是语音识别生成的字幕,可能包含识别错误。
+请只纠正明显的错误(如同音字、漏字、错字),不要改变原意和风格。
+
+原文:
+{text}
+
+纠正后的文本(只输出纠正后的文本,不要解释):"""
+
+
+class ASRCorrector:
+ """ASR 纠错器
+
+ 使用 LLM 纠正语音识别中的明显错误。
+
+ Attributes:
+ ai_client: AI 客户端(必须实现 generate 方法)
+ batch_size: 分批处理的字符数,默认 5000
+ """
+
+ def __init__(
+ self,
+ ai_client: Optional[AIClientProtocol] = None,
+ batch_size: int = 5000
+ ):
+ """初始化纠错器
+
+ Args:
+ ai_client: AI 客户端实例,如果为 None 则不进行纠错
+ batch_size: 分批处理的字符数
+ """
+ self.ai_client = ai_client
+ self.batch_size = batch_size
+
+ def correct(self, text: str) -> str:
+ """纠正文本中的语音识别错误
+
+ Args:
+ text: 原始文本
+
+ Returns:
+ 纠正后的文本。如果 AI 不可用或文本为空,返回原文。
+
+ Example:
+ >>> corrector = ASRCorrector(ai_client=my_client)
+ >>> result = corrector.correct("什么是机器学习")
+ >>> print(result)
+ 什么是机器学习
+ """
+ # 空文本直接返回
+ if not text or not text.strip():
+ return text or ""
+
+ # AI 不可用,返回原文
+ if self.ai_client is None:
+ logger.info("AI client not available, returning original text")
+ return text
+
+ # 短文本直接处理
+ if len(text) <= self.batch_size:
+ return self._correct_batch(text)
+
+ # 长文本分批处理
+ return self._correct_long_text(text)
+
+ def _correct_batch(self, text: str) -> str:
+ """处理单个批次的文本
+
+ Args:
+ text: 文本内容
+
+ Returns:
+ 纠正后的文本
+ """
+ try:
+ prompt = CORRECTION_PROMPT.format(text=text)
+ result = self.ai_client.generate(prompt, max_tokens=len(text) + 500)
+ return result.strip()
+ except Exception as e:
+ logger.warning(f"ASR correction failed: {e}, returning original text")
+ return text
+
+ def _correct_long_text(self, text: str) -> str:
+ """分批处理长文本
+
+ 按句子分割,每批不超过 batch_size 字符。
+
+ Args:
+ text: 长文本内容
+
+ Returns:
+ 纠正后的文本
+ """
+ # 按句子分割(简单分割,基于句号、问号、感叹号)
+ sentences = []
+ current = ""
+ for char in text:
+ current += char
+ if char in '。!?\n':
+ sentences.append(current)
+ current = ""
+
+ # 添加最后一个不完整的句子
+ if current:
+ sentences.append(current)
+
+ # 分批处理
+ batches = []
+ current_batch = []
+ current_length = 0
+
+ for sentence in sentences:
+ if current_length + len(sentence) > self.batch_size:
+ if current_batch:
+ batches.append(''.join(current_batch))
+ current_batch = [sentence]
+ current_length = len(sentence)
+ else:
+ current_batch.append(sentence)
+ current_length += len(sentence)
+
+ if current_batch:
+ batches.append(''.join(current_batch))
+
+ # 处理每个批次
+ corrected_batches = []
+ for batch in batches:
+ corrected = self._correct_batch(batch)
+ corrected_batches.append(corrected)
+
+ return ''.join(corrected_batches)
diff --git a/skills/kb_workflow/srt/parser.py b/skills/kb_workflow/srt/parser.py
new file mode 100644
index 0000000..6565792
--- /dev/null
+++ b/skills/kb_workflow/srt/parser.py
@@ -0,0 +1,161 @@
+"""SRT Parser Module
+
+解析 SRT 字幕文件格式,将字幕转换为结构化的 Segment 对象。
+
+SRT 格式示例:
+ 1
+ 00:00:00,000 --> 00:00:05,000
+ 第一句字幕内容
+
+ 2
+ 00:00:05,000 --> 00:00:10,000
+ 第二句字幕内容
+"""
+
+from __future__ import annotations
+
+import re
+from dataclasses import dataclass
+from pathlib import Path
+from typing import List
+
+
+@dataclass
+class Segment:
+ """字幕分段数据结构
+
+ Attributes:
+ id: 字幕序号
+ start_time: 开始时间(秒)
+ end_time: 结束时间(秒)
+ text: 字幕文本
+ """
+ id: int
+ start_time: float
+ end_time: float
+ text: str
+
+
+# SRT 块正则表达式
+# 格式: 序号\n时间码\n文本(可能多行)\n\n
+SRT_BLOCK_PATTERN = re.compile(
+ r'(\d+)\s*\n'
+ r'(\d{2}:\d{2}:\d{2},\d{3})\s*-->\s*(\d{2}:\d{2}:\d{2},\d{3})\s*\n'
+ r'(.*?)(?=\n\n|\Z)',
+ re.DOTALL
+)
+
+
+def parse_srt_time(time_str: str) -> float:
+ """解析 SRT 时间格式 (HH:MM:SS,mmm) 为秒数
+
+ Args:
+ time_str: SRT 时间字符串,如 "00:05:30,500"
+
+ Returns:
+ 秒数,如 330.5
+
+ Example:
+ >>> parse_srt_time("00:05:30,500")
+ 330.5
+ >>> parse_srt_time("01:02:03,004")
+ 3723.004
+ """
+ # 分割时间和毫秒
+ main_part, millis = time_str.rsplit(',', 1)
+ hours, minutes, seconds = main_part.split(':')
+
+ total_seconds = (
+ int(hours) * 3600 +
+ int(minutes) * 60 +
+ int(seconds) +
+ int(millis) / 1000
+ )
+ return total_seconds
+
+
+def parse_srt(content: str) -> List[Segment]:
+ """解析 SRT 格式字符串
+
+ 将 SRT 格式的字幕内容解析为 Segment 对象列表。
+
+ Args:
+ content: SRT 格式的字符串内容
+
+ Returns:
+ Segment 对象列表
+
+ Raises:
+ ValueError: 内容为空或格式无效
+
+ Example:
+ >>> content = "1\\n00:00:00,000 --> 00:00:05,000\\n第一句\\n\\n"
+ >>> segments = parse_srt(content)
+ >>> len(segments)
+ 1
+ >>> segments[0].text
+ '第一句'
+ """
+ # 检查空内容
+ if not content or not content.strip():
+ raise ValueError("Empty SRT content")
+
+ segments = []
+
+ # 查找所有 SRT 块
+ matches = SRT_BLOCK_PATTERN.findall(content)
+
+ if not matches:
+ raise ValueError("Invalid SRT format: no valid blocks found")
+
+ for match in matches:
+ try:
+ segment_id = int(match[0])
+ start_time = parse_srt_time(match[1])
+ end_time = parse_srt_time(match[2])
+ # 清理文本:移除前后空白,合并多行为单行
+ text = ' '.join(match[3].strip().split())
+
+ segment = Segment(
+ id=segment_id,
+ start_time=start_time,
+ end_time=end_time,
+ text=text
+ )
+ segments.append(segment)
+ except (ValueError, IndexError) as e:
+ # 跳过解析失败的块
+ continue
+
+ if not segments:
+ raise ValueError("Invalid SRT format: no valid segments parsed")
+
+ return segments
+
+
+def parse_srt_file(file_path: Path) -> List[Segment]:
+ """从文件解析 SRT 格式
+
+ 读取文件内容并解析为 Segment 对象列表。
+
+ Args:
+ file_path: SRT 文件路径
+
+ Returns:
+ Segment 对象列表
+
+ Raises:
+ FileNotFoundError: 文件不存在
+ ValueError: 内容格式无效
+
+ Example:
+ >>> from pathlib import Path
+ >>> segments = parse_srt_file(Path("subtitles.srt"))
+ >>> len(segments)
+ 10
+ """
+ if not file_path.exists():
+ raise FileNotFoundError(f"SRT file not found: {file_path}")
+
+ content = file_path.read_text(encoding='utf-8')
+ return parse_srt(content)
diff --git a/skills/kb_workflow/srt/segmenter.py b/skills/kb_workflow/srt/segmenter.py
new file mode 100644
index 0000000..7af8d12
--- /dev/null
+++ b/skills/kb_workflow/srt/segmenter.py
@@ -0,0 +1,97 @@
+"""Semantic Segmenter Module
+
+将字幕分段按语义合并为段落。基于简单启发式规则:
+1. 时间间隔超过阈值时分段
+2. 段落长度限制
+
+不使用 LLM 进行分段判断,保持简单和快速。
+"""
+
+from __future__ import annotations
+
+from typing import List
+
+from kb_workflow.srt.parser import Segment
+
+
+class SemanticSegmenter:
+ """语义分段器
+
+ 将字幕分段转换为语义段落。基于时间和长度规则:
+ - 长时间停顿(>gap_threshold 秒)作为分段点
+ - 段落长度限制(max_paragraph_chars 字符)
+
+ Attributes:
+ gap_threshold: 时间间隔阈值(秒),默认 3.0
+ max_paragraph_chars: 单段落最大字符数,默认 500
+ """
+
+ def __init__(
+ self,
+ gap_threshold: float = 3.0,
+ max_paragraph_chars: int = 500
+ ):
+ """初始化分段器
+
+ Args:
+ gap_threshold: 时间间隔阈值(秒),超过此值时分段
+ max_paragraph_chars: 单段落最大字符数
+ """
+ self.gap_threshold = gap_threshold
+ self.max_paragraph_chars = max_paragraph_chars
+
+ def segment(self, segments: List[Segment]) -> List[str]:
+ """将字幕分段转换为语义段落
+
+ Args:
+ segments: 字幕分段列表
+
+ Returns:
+ 段落文本列表(已去除时间戳)
+
+ Example:
+ >>> segmenter = SemanticSegmenter()
+ >>> segments = [Segment(1, 0, 5, "第一句"), Segment(2, 5, 10, "第二句")]
+ >>> paragraphs = segmenter.segment(segments)
+ >>> len(paragraphs)
+ 1
+ """
+ if not segments:
+ return []
+
+ paragraphs = []
+ current_paragraph = []
+ current_length = 0
+ last_end_time = None
+
+ for seg in segments:
+ # 检查是否需要分段
+ should_start_new = False
+
+ # 规则1: 时间间隔超过阈值
+ if last_end_time is not None:
+ gap = seg.start_time - last_end_time
+ if gap > self.gap_threshold:
+ should_start_new = True
+
+ # 规则2: 段落长度超过限制
+ if current_length + len(seg.text) > self.max_paragraph_chars:
+ if current_paragraph: # 只有当前段落有内容时才分段
+ should_start_new = True
+
+ # 执行分段
+ if should_start_new and current_paragraph:
+ paragraphs.append(' '.join(current_paragraph))
+ current_paragraph = []
+ current_length = 0
+
+ # 添加当前分段
+ current_paragraph.append(seg.text)
+ current_length += len(seg.text)
+ last_end_time = seg.end_time
+
+ # 添加最后一个段落
+ if current_paragraph:
+ paragraphs.append(' '.join(current_paragraph))
+
+ return paragraphs