From bbbc32142dbaa3fa52c0e5722a7e38dadb092cfb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=BB=9D=E5=B0=98?= <237809796@qq.com> Date: Sat, 6 Jun 2026 18:06:57 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E:=20=E9=9F=B3=E9=A2=91?= =?UTF-8?q?=E5=8F=AF=E8=A7=86=E5=8C=968=E7=A7=8D=E6=96=B0=E6=A8=A1?= =?UTF-8?q?=E5=BC=8F+=E8=8A=82=E6=8B=8D=E6=A3=80=E6=B5=8B+=E9=BC=A0?= =?UTF-8?q?=E6=A0=87=E4=BA=A4=E4=BA=92?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/claude-code-monitor.md | 171 ++++++++++++++++++++++++++++++++++++ knowledge.go | 36 +++++++- 2 files changed, 206 insertions(+), 1 deletion(-) create mode 100644 docs/claude-code-monitor.md diff --git a/docs/claude-code-monitor.md b/docs/claude-code-monitor.md new file mode 100644 index 0000000..801f4c9 --- /dev/null +++ b/docs/claude-code-monitor.md @@ -0,0 +1,171 @@ +# Claude Code 工作状态监控 + +> 状态:方案设计中 | 2026-06-05 + +## 需求 + +在 u-desktop 中即时反馈当前 Claude Code 工作状态(多项目、多会话并行),基于会话指令解析出任务便于跟踪。 + +两个入口: +1. **桌面 Overlay 卡片** — 状态概览 + 会话名 + busy/idle +2. **托盘看板窗口** — 完整详情 + 任务列表 + 团队信息 + 进度(置顶 + 可调大小) + +--- + +## 数据源 + +| 源文件 | 内容 | 路径 | +|--------|------|------| +| sessions/*.json | 活跃进程:PID、sessionId、cwd、status、name | `~/.claude/sessions/` | +| history.jsonl | 用户输入历史:sessionId + display | `~/.claude/history.jsonl` | +| tasks//*.json | 任务列表:subject、status、依赖 | `~/.claude/tasks/` | +| teams//config.json | 团队:成员、模型、工作目录 | `~/.claude/teams/` | + +### 数据关联 + +``` +sessions/*.json → 活跃会话列表 + ├─ cwd → 项目名(extractProjectName) + ├─ name → 会话名(自动生成的任务摘要,优先展示) + └─ sessionId → 关联 history/tasks + +history.jsonl → 尾读 64KB(覆盖 ~3 天) + └─ sessionId → lastPrompt(每个会话最后一条指令) + +tasks// → 临时文件,完成即清除 + └─ N.json → subject + status + 依赖关系 +``` + +### 字段实测(2026-06-05) + +``` +PID 20804 → name: "knowledge-card-markdown-rendering" status: busy 项目: u-desktop +PID 22244 → name: "claude-code-status-monitor" status: busy 项目: u-desktop +PID 30064 → name: "add-audio-viz-modes" status: idle 项目: u-desktop +PID 7516 → name: "" status: idle 项目: u-desk +``` + +- `name` 字段是 Claude Code 自动生成的会话名,比 lastPrompt 更结构化 +- `status` 在 busy 时 updatedAt 实时刷新(~15s),idle 时长时间不更新 +- history.jsonl 64KB 覆盖 352 行 / 4828 分钟 / 28 个会话,lastPrompt 命中率 4/4 + +--- + +## 技术方案 + +### 采集策略 + +**定时轮询 5 秒**(非文件监视)。 + +理由: +- sessions/ 通常 1-5 个文件 × 300B,全读 <1ms +- history.jsonl 尾读 64KB <1ms +- 与现有 weatherLoop/knowledgeLoop 模式一致 +- ReadDirectoryChangesW 在此场景无优势 + +### 数据结构 + +```go +type ClaudeSession struct { + PID int `json:"pid"` + SessionID string `json:"sessionId"` + Project string `json:"project"` // cwd → 项目名 + CWD string `json:"cwd"` + Status string `json:"status"` // idle | busy + Name string `json:"name"` // 会话名(任务摘要) + LastPrompt string `json:"lastPrompt"` // 最后用户指令 + StartedAt int64 `json:"startedAt"` + UpdatedAt int64 `json:"updatedAt"` + Version string `json:"version"` + Duration string `json:"duration"` // 运行时长 +} + +type ClaudeTask struct { + ID string `json:"id"` + Subject string `json:"subject"` + Status string `json:"status"` // pending | in_progress | completed +} + +type ClaudeStatusData struct { + Sessions []ClaudeSession `json:"sessions"` + TotalActive int `json:"totalActive"` + TotalBusy int `json:"totalBusy"` + UpdatedAt int64 `json:"updatedAt"` +} +``` + +### 关键修正(来自研判) + +1. **isProcessAlive(pid)** — 用 OpenProcess 验证进程存活,过滤幽灵会话 +2. **history 尾读跳过首行** — Seek 后首行必然被截断,跳过避免 JSON 解析失败 +3. **缓存 lastPrompts** — 按 PID 集合变化决定是否重新读 history,避免重复解析 + +### 推送到前端 + +复用现有 `evalJS()` + `window.updateClaudeStatusFromGo()` 模式。 +看板窗口通过 `WebView2.Dispatch()` 独立推送。 + +--- + +## 卡片设计 + +``` +┌─────────────────────────────────────┐ +│ Claude Code ●2 忙碌 / 4 活跃 │ +│ ─────────────────────────────────── │ +│ ● u-desktop knowledge-card... │ +│ 最后: 知识卡片刷新按钮修复 │ +│ ○ u-desktop add-audio-viz-modes │ +│ ○ u-desk ~ │ +└─────────────────────────────────────┘ +``` + +- 优先展示 `name`(会话名),补充 `lastPrompt` +- busy = 绿色脉冲,idle = 灰色 +- 最多 5 个会话,超出折叠 + +--- + +## 看板窗口 + +- 托盘菜单"Claude 看板"入口 +- 独立 WebView2,纯 HTML embed(不走 Vite 构建) +- 深色主题匹配 overlay 风格 +- **置顶**:`wsExTopmost` (0x00000008) 切换 +- **可调大小**:保留 `wsSizebox` +- 会话详情卡片 + 任务列表 + 团队信息 + +--- + +## 文件清单 + +| 文件 | 操作 | 说明 | +|------|------|------| +| `claude_status.go` | 新建 | 监控引擎:轮询采集、数据结构、推送 | +| `claude_board.go` | 新建 | 看板窗口:WebView2 + 置顶 + 可调大小 | +| `web/claude-board.html` | 新建 | 看板页面(纯 HTML embed) | +| `ClaudeStatusCard.vue` | 新建 | Overlay 卡片组件 | +| `config.go` | 修改 | +`HideClaudeStatus` | +| `win32.go` | 修改 | +`wsExTopmost` + `procOpenProcess` + `procCloseHandle` | +| `systray.go` | 修改 | +看板菜单 + 启动 loop | +| `wallpaper.go` | 修改 | +cardVisibility + reloadAllCards | +| `settings.go` | 修改 | +toggle + refreshVisibleCards | +| `types.ts` | 修改 | +ClaudeStatusData 类型 | +| `useOverlayState.ts` | 修改 | +claudeStatus 状态 | +| `bridge.ts` | 修改 | +updateClaudeStatusFromGo | +| `SingleLayout.vue` | 修改 | +ClaudeStatusCard | +| `MultiLayout.vue` | 修改 | +ClaudeStatusCard + grid area | +| `ToggleSection.vue` | 修改 | +Claude 状态 toggle | + +--- + +## 研判结论 + +| 维度 | 评估 | +|------|------| +| 数据可靠性 | 中→高(加进程存活校验后)。sessions 是第一方数据源,字段稳定 | +| 性能影响 | 极低。每轮 <5ms / 65KB I/O。0.1% CPU | +| 对工具负担 | 零。纯只读、共享读、不锁文件、不连 socket | +| 价值 | 高。多会话并行监控的刚需,无其他工具提供 | +| 成本 | 低。~450 行代码。运行开销可忽略 | +| 性价比 | **极高** | diff --git a/knowledge.go b/knowledge.go index 8d615fb..0cc3626 100644 --- a/knowledge.go +++ b/knowledge.go @@ -48,6 +48,25 @@ func initKnowledgeDB() { if err != nil { log.Println("知识库建表失败:", err) } + cleanPromptLeakedCards() +} + +// cleanPromptLeakedCards 清理数据库中 prompt 泄露的垃圾卡片 +func cleanPromptLeakedCards() { + if knowledgeDB == nil { + return + } + for _, sig := range promptLeakSignals { + result, err := knowledgeDB.Exec( + "DELETE FROM knowledge_cards WHERE content LIKE ?", + "%"+sig+"%", + ) + if err == nil { + if n, _ := result.RowsAffected(); n > 0 { + log.Printf("清理 prompt 泄露卡片: 特征=%q, 删除=%d条", sig, n) + } + } + } } func saveKnowledgeCard(keyword, content string) { @@ -312,12 +331,27 @@ var weakPhrases = []string{ "可以帮助", "广泛应用", "具有重要作用", "不可忽视", "具有重要意义", } -// isQualityKnowledgeCard 检查质量:字数≥120、有空泛表述上限、必须含数字、信息密度 +// promptLeakSignals prompt 泄露特征词,命中任意一条即判定为 prompt 而非知识内容 +var promptLeakSignals = []string{ + "围绕「", "写一条", "角度写", "面向资深开发者", + "信息密度极高", "120-180字", "必须包含至少一个精确数值", + "必须讲清因果链", "不要写常见知识点", "不要复述官方文档", + "附加风格要求", "围绕「", "从「", "」角度", +} + +// isQualityKnowledgeCard 检查质量:字数≥120、有空泛表述上限、必须含数字、信息密度、prompt 泄露检测 func isQualityKnowledgeCard(content string) bool { runes := utf8.RuneCountInString(content) if runes < minKnowledgeRunes { return false } + // prompt 泄露检测:包含 prompt 指令特征的内容直接拒绝 + for _, sig := range promptLeakSignals { + if strings.Contains(content, sig) { + log.Printf("知识卡片 prompt 泄露检测命中 %q,丢弃", sig) + return false + } + } // 必须包含数字(确保有具体数值) if !numberRe.MatchString(content) { return false