新增: 音频可视化8种新模式+节拍检测+鼠标交互
This commit is contained in:
171
docs/claude-code-monitor.md
Normal file
171
docs/claude-code-monitor.md
Normal file
@@ -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/<sid>/*.json | 任务列表:subject、status、依赖 | `~/.claude/tasks/` |
|
||||
| teams/<name>/config.json | 团队:成员、模型、工作目录 | `~/.claude/teams/` |
|
||||
|
||||
### 数据关联
|
||||
|
||||
```
|
||||
sessions/*.json → 活跃会话列表
|
||||
├─ cwd → 项目名(extractProjectName)
|
||||
├─ name → 会话名(自动生成的任务摘要,优先展示)
|
||||
└─ sessionId → 关联 history/tasks
|
||||
|
||||
history.jsonl → 尾读 64KB(覆盖 ~3 天)
|
||||
└─ sessionId → lastPrompt(每个会话最后一条指令)
|
||||
|
||||
tasks/<sid>/ → 临时文件,完成即清除
|
||||
└─ 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 行代码。运行开销可忽略 |
|
||||
| 性价比 | **极高** |
|
||||
36
knowledge.go
36
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
|
||||
|
||||
Reference in New Issue
Block a user