docs(spec): 详情面板7问题修复设计文档
Some checks failed
CI / Lint & TypeCheck (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Build Frontend (push) Has been cancelled
CI / Rust Check (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
Some checks failed
CI / Lint & TypeCheck (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Build Frontend (push) Has been cancelled
CI / Rust Check (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
7个问题根因分析+修复方案: P0: 聊天路由竞态/记忆查询缺陷/hand_trigger硬编码 P1: Agent画像断链/反思持久化多重缺陷 P2: 演化差异视图/管家Tab上下文混淆 路径B: 系统桥接修复,扩展已有命令而非新增
This commit is contained in:
285
docs/superpowers/specs/2026-04-11-detail-panel-7-fixes-design.md
Normal file
285
docs/superpowers/specs/2026-04-11-detail-panel-7-fixes-design.md
Normal file
@@ -0,0 +1,285 @@
|
|||||||
|
# Detail Panel 7-Issue Fix Design
|
||||||
|
|
||||||
|
> Date: 2026-04-11
|
||||||
|
> Status: Draft
|
||||||
|
> Approach: Path B — System Bridge (fix bugs + bridge disconnected systems)
|
||||||
|
> Constraint: No new SaaS endpoints, no new stores, no new middleware (stabilization compliance)
|
||||||
|
|
||||||
|
## Background
|
||||||
|
|
||||||
|
User discovered 7 issues during real usage of ZCLAW desktop app's detail panel (RightPanel). Root cause analysis revealed a pattern: 5 of 7 issues stem from "backend has capability but frontend doesn't consume it" — disconnected systems that need bridging.
|
||||||
|
|
||||||
|
## Issue Summary
|
||||||
|
|
||||||
|
| # | Problem | Severity | Root Cause | Category |
|
||||||
|
|---|---------|----------|------------|----------|
|
||||||
|
| 1 | Can't chat after restart | P0 | Heartbeat degrades to 'tauri' in localStorage, restoreSession blindly trusts it | Route race |
|
||||||
|
| 2 | "About me / How I see you" never updates | P1 | Panel reads agentStore.Clone, memory extractor writes identity files — no bridge | Data disconnect |
|
||||||
|
| 3 | Memory 311 count but empty list/graph | P0 | memory_search empty query + min_similarity=None → FTS5 returns 0 | Query logic bug |
|
||||||
|
| 4 | Only 1 reflection, effects unknowable | P1 | History only persists latest (not array); pattern thresholds too high; state restore race | Multi-factor |
|
||||||
|
| 5 | hand_trigger always rejected | P0 | autonomy-manager.ts:268 maps hand_trigger to null — hardcoded bug | Hardcoded bug |
|
||||||
|
| 6 | Evolution has data but effects unknowable | P2 | HistoryItem only shows truncated reason, no expandable diff | UX gap |
|
||||||
|
| 7 | Butler "6 messages" incorrect + empty below | P2 | Shared chat stats header confused for butler data; no pain points detected | UX confusion |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Section 1: P0 Fixes
|
||||||
|
|
||||||
|
### Fix 1 — Chat Routing After Restart
|
||||||
|
|
||||||
|
**Root Cause**: `saasStore.restoreSession()` blindly trusts localStorage connectionMode written by heartbeat degrade. After restart, SaaS auth may still be valid but the persisted 'tauri' mode prevents SaaS relay usage.
|
||||||
|
|
||||||
|
**Solution**:
|
||||||
|
1. In `restoreSession()` (saasStore.ts ~line 750): Before accepting persisted mode, validate SaaS token:
|
||||||
|
- Attempt refresh token → access token exchange
|
||||||
|
- If successful, restore to 'saas' mode regardless of persisted value
|
||||||
|
- Only accept 'tauri' mode if token refresh fails
|
||||||
|
2. In `loadConnectionMode()` (saas-session.ts ~line 170): Add staleness check:
|
||||||
|
- Persist timestamp alongside mode in localStorage
|
||||||
|
- On load, if persisted mode is 'tauri' and timestamp > 5 minutes old, re-validate
|
||||||
|
3. Heartbeat degrade writes timestamp for staleness tracking
|
||||||
|
|
||||||
|
**Files**:
|
||||||
|
- `desktop/src/store/saasStore.ts` — restoreSession() ~line 750 (verify exact location before implementation)
|
||||||
|
- `desktop/src/lib/saas-session.ts` — loadConnectionMode() ~line 170
|
||||||
|
- `desktop/src/store/connectionStore.ts` — connect() routing ~line 428
|
||||||
|
|
||||||
|
**Risk**: Low. Only changes startup validation logic, doesn't affect active session behavior.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Fix 3 — Memory List/Graph Empty Despite 311 Entries
|
||||||
|
|
||||||
|
**Root Cause**: `memory_search` Tauri command calls `VikingStorage::find()` with empty query string and `min_similarity: None`. FTS5 requires non-empty query to match anything. `memory_stats` works because it sets `min_similarity: Some(0.0)` which triggers table scan path.
|
||||||
|
|
||||||
|
**Solution**:
|
||||||
|
1. In `memory_commands.rs` `memory_search` (line 164): When query is empty or whitespace-only, set `min_similarity: Some(0.0)` as default fallback
|
||||||
|
2. This makes the empty-query behavior consistent with `memory_stats` — table scan returns all entries
|
||||||
|
3. No changes to VikingStorage internals — only the calling parameter
|
||||||
|
|
||||||
|
**Files**:
|
||||||
|
- `desktop/src-tauri/src/memory_commands.rs` — memory_search at line 148-184
|
||||||
|
|
||||||
|
**Risk**: Minimal. Single-line parameter change. Performance concern mitigated by existing `limit: 50` default.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Fix 5 — hand_trigger Always Rejected
|
||||||
|
|
||||||
|
**Root Cause**: `autonomy-manager.ts:268` maps `hand_trigger: null` in the actionMap. When configKey is null, `isActionAllowed()` immediately returns false. No `handAutoTrigger` field exists in AutonomyConfig.allowedActions.
|
||||||
|
|
||||||
|
**Solution**:
|
||||||
|
1. Add `handAutoTrigger: boolean` to `AutonomyConfig.allowedActions` type (autonomy-manager.ts)
|
||||||
|
2. Change actionMap: `hand_trigger: null` → `hand_trigger: 'handAutoTrigger'`
|
||||||
|
3. Update default configs:
|
||||||
|
- `autonomous` level: `handAutoTrigger: true`
|
||||||
|
- `supervised` level: `handAutoTrigger: false`
|
||||||
|
- `guided` level: `handAutoTrigger: false`
|
||||||
|
4. Add toggle control in `AutonomyConfig.tsx` UI
|
||||||
|
|
||||||
|
**Files**:
|
||||||
|
- `desktop/src/lib/autonomy-manager.ts` — line 268 actionMap + allowedActions type + default configs
|
||||||
|
- `desktop/src/components/AutonomyConfig.tsx` — add handAutoTrigger toggle
|
||||||
|
|
||||||
|
**Risk**: Low. Pure frontend change. Backend already supports autonomous mode bypassing approval gate.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Section 2: P1 Fixes
|
||||||
|
|
||||||
|
### Fix 2 — "About Me / How I See You" Auto-Update
|
||||||
|
|
||||||
|
**Root Cause**: Panel reads from `agentStore.Clone` static fields. Memory extractor writes to identity files (soul/instructions/user_profile). Backend `UserProfileStore` (Rust) has structured user modeling (industry/role/expertise/communication_style/recent_topics) but no Tauri command exposes it to frontend.
|
||||||
|
|
||||||
|
**Solution (System Bridge — Alternative Approach: Extend Existing Command)**:
|
||||||
|
|
||||||
|
To comply with stabilization constraint "no new Tauri commands" (CLAUDE.md §1.3: "已有 189 个,70 个无前端调用"), we use Option (b): extend an existing command's return payload.
|
||||||
|
|
||||||
|
1. **Extend `agent_get` command**: The existing `agent_get(agent_id)` command returns `AgentConfig`. Extend its return to include an optional `user_profile` field from `UserProfileStore::get()`.
|
||||||
|
- This avoids creating a new command while exposing existing backend data.
|
||||||
|
- File: `desktop/src-tauri/src/kernel_commands/agents.rs` — extend return type
|
||||||
|
|
||||||
|
2. **Frontend bridge**:
|
||||||
|
- Use component-local state (`useState`) in RightPanel for UserProfile data (not Store — per CLAUDE.md §4.2 layered responsibilities)
|
||||||
|
- On mount and after conversation complete, re-fetch agent data which now includes profile
|
||||||
|
- "How I See You" section renders from two sources:
|
||||||
|
- Static: `Clone` fields (userName, workspaceDir, privacyOptIn)
|
||||||
|
- Dynamic: `UserProfile` fields (industry, role, expertise, communication_style, recent_topics)
|
||||||
|
- Fallback: if UserProfile empty, show static Clone data only
|
||||||
|
|
||||||
|
3. **Refresh trigger**: In `streamStore` flow completion callback (~line 446), after memory extraction, trigger RightPanel's profile refresh via a lightweight event or re-fetch
|
||||||
|
|
||||||
|
**Files**:
|
||||||
|
- `desktop/src-tauri/src/kernel_commands/agents.rs` — extend agent_get return type
|
||||||
|
- `desktop/src-tauri/src/lib.rs` — no change needed (command already registered)
|
||||||
|
- `desktop/src/components/RightPanel.tsx` — dual-source rendering (lines 523-557)
|
||||||
|
- `desktop/src/store/chat/streamStore.ts` — add profile refresh trigger ~line 446
|
||||||
|
|
||||||
|
**Risk**: Medium. Extends existing command rather than creating new one. UserProfileStore already exists. No new SaaS endpoints. No new stores.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Fix 4 — Reflection Only 1 Entry, Effects Unknowable
|
||||||
|
|
||||||
|
**Root Cause**: Multi-factor — history only persists `reflection:latest` (single entry) not `reflection:history` (array); pattern detection thresholds too high (5+ same type); state restore race condition in post_conversation_hook.
|
||||||
|
|
||||||
|
**Solution**:
|
||||||
|
1. **Fix history persistence** (reflection.rs):
|
||||||
|
- Ensure `reflect()` appends to `reflection:history:{agent_id}` array (not overwrite)
|
||||||
|
- Maintain cap at 20 entries (FIFO)
|
||||||
|
- Always write both `reflection:latest` AND `reflection:history`
|
||||||
|
|
||||||
|
2. **Lower pattern detection thresholds** (reflection.rs `analyze_patterns()`):
|
||||||
|
- Pattern "many tasks": 5+ → 3+ (rationale: early pattern detection provides timely insights)
|
||||||
|
- Pattern "many preferences": 5+ → 3+ (rationale: user preferences accumulate quickly in normal use)
|
||||||
|
- Pattern "many experiences": 5+ → 3+ (rationale: experiences are high-value patterns worth detecting early)
|
||||||
|
- Pattern "high-visit memories": 3+ → 2+ (rationale: even 2 high-visit memories indicate recurring interest)
|
||||||
|
- Pattern "low-importance memories": 20+ → 15+ (rationale: conservative reduction from 20 — avoids noisy cleanup suggestions at 10)
|
||||||
|
|
||||||
|
3. **Fix state restore race** (intelligence_hooks.rs):
|
||||||
|
- Change `post_conversation_hook` state restore from "pop (read + delete)" to "read only"
|
||||||
|
- Cleanup mechanism: old state is cleaned up on next conversation hook invocation (FIFO with max 1 entry per agent), preventing indefinite accumulation
|
||||||
|
- Let `getHistory` always find complete data in VikingStorage
|
||||||
|
|
||||||
|
4. **Enhance reflection effect visibility** (ReflectionLog.tsx):
|
||||||
|
- Each history entry shows: pattern count + summary, improvement suggestion count, identity proposal count
|
||||||
|
- Timeline indicator: "Triggered after N conversations"
|
||||||
|
- Expandable detail view for patterns and suggestions
|
||||||
|
|
||||||
|
**Files**:
|
||||||
|
- `desktop/src-tauri/src/intelligence/reflection.rs` — history persistence + threshold adjustment
|
||||||
|
- `desktop/src-tauri/src/intelligence_hooks.rs` — state restore race fix
|
||||||
|
- `desktop/src/components/ReflectionLog.tsx` — enhanced visibility UI
|
||||||
|
|
||||||
|
**Risk**: Low-Medium. Reflection persistence fix is the most impactful change. Threshold changes are conservative (3+ is still meaningful).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Section 3: P2 Fixes
|
||||||
|
|
||||||
|
### Fix 6 — Evolution Effects Unknowable
|
||||||
|
|
||||||
|
**Root Cause**: `HistoryItem` component only renders truncated `reason` text and timestamp. Snapshot data contains full `files` object but UI never renders diff content. `ProposalCard` already has a `DiffView` component that could be reused.
|
||||||
|
|
||||||
|
**Solution**:
|
||||||
|
1. **Expandable HistoryItem** (IdentityChangeProposal.tsx):
|
||||||
|
- Add click-to-expand behavior on history items
|
||||||
|
- When expanded, show which files changed (as tags)
|
||||||
|
- Show full reason text (no truncation)
|
||||||
|
- Reuse existing `DiffView` component for before/after comparison
|
||||||
|
|
||||||
|
2. **Evolution summary metadata**:
|
||||||
|
- Each history item header shows file change count badge
|
||||||
|
- Color coding: soul changes = purple, instructions = blue, user_profile = green
|
||||||
|
|
||||||
|
**Files**:
|
||||||
|
- `desktop/src/components/IdentityChangeProposal.tsx` — HistoryItem component (lines 269-304)
|
||||||
|
|
||||||
|
**Risk**: Low. Pure UI enhancement. Reuses existing DiffView component.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Fix 7 — Butler Tab Confusion + Empty Data
|
||||||
|
|
||||||
|
**Root Cause**: "6 messages" is shared chat statistics header displayed across ALL tabs (not butler-specific). Butler tab below is empty because: (a) no pain points detected in short conversations, (b) no proposals generated, (c) Viking memory path empty.
|
||||||
|
|
||||||
|
**Solution**:
|
||||||
|
1. **Contextual header** (RightPanel.tsx):
|
||||||
|
- When butler tab is active, replace chat statistics with butler-specific summary
|
||||||
|
- Show: Butler name + pain points count + proposals count + memory count
|
||||||
|
- Other tabs keep existing chat statistics
|
||||||
|
|
||||||
|
2. **Enhanced empty state** (ButlerPanel/index.tsx):
|
||||||
|
- When all sections empty, show friendly onboarding message
|
||||||
|
- "管家正在了解您,多轮对话后会自动发现您的需求"
|
||||||
|
- Add manual "analyze now" button that calls `analyze_for_pain_signals` with current conversation history
|
||||||
|
- Button disabled when conversation < 2 messages (prevents meaningless analysis)
|
||||||
|
- Show loading state during analysis
|
||||||
|
- Display "未发现痛点" message when analysis completes with no results
|
||||||
|
|
||||||
|
3. **Lower pain point detection threshold** (pain_aggregator.rs):
|
||||||
|
- Relax keyword matching patterns
|
||||||
|
- Allow pain point extraction from shorter conversations (minimum 2 messages instead of 3+)
|
||||||
|
|
||||||
|
**Files**:
|
||||||
|
- `desktop/src/components/RightPanel.tsx` — conditional header rendering (lines 374-386)
|
||||||
|
- `desktop/src/components/ButlerPanel/index.tsx` — empty state enhancement
|
||||||
|
- `desktop/src-tauri/src/intelligence/pain_aggregator.rs` — threshold adjustment
|
||||||
|
|
||||||
|
**Risk**: Low. UX improvements. Pain point threshold change is conservative.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Implementation Priority
|
||||||
|
|
||||||
|
| Batch | Issues | Severity | Est. Complexity |
|
||||||
|
|-------|--------|----------|-----------------|
|
||||||
|
| Batch 1 | 1, 3, 5 | P0 | Low (small targeted fixes) |
|
||||||
|
| Batch 2 | 2, 4 | P1 | Medium (system bridge + persistence fix) |
|
||||||
|
| Batch 3 | 6, 7 | P2 | Low (UX enhancements) |
|
||||||
|
|
||||||
|
## Testing Plan
|
||||||
|
|
||||||
|
### Automated Tests (after each batch)
|
||||||
|
- `tsc --noEmit` — TypeScript type check
|
||||||
|
- `cargo check` — Rust compilation
|
||||||
|
- `vitest run` — Frontend unit tests
|
||||||
|
|
||||||
|
### Rust Unit Tests (Batch 2)
|
||||||
|
- `memory_search` empty query behavior: verify `min_similarity` defaults to `Some(0.0)` when query is empty
|
||||||
|
- Reflection history persistence: verify `reflect()` appends to history array, not overwrites
|
||||||
|
|
||||||
|
### Manual Test Matrix
|
||||||
|
|
||||||
|
**Fix 1 — Chat Routing (must test both modes)**:
|
||||||
|
- [ ] SaaS mode: restart app with valid SaaS session → should restore to 'saas' mode
|
||||||
|
- [ ] SaaS mode: restart app with expired SaaS session → should degrade to 'tauri' mode
|
||||||
|
- [ ] Tauri mode: restart app → should stay in 'tauri' mode
|
||||||
|
- [ ] Fresh install → should prompt login or default to local mode
|
||||||
|
|
||||||
|
**Fix 2 — Agent Profile**:
|
||||||
|
- [ ] UserProfile empty → fallback to static Clone data
|
||||||
|
- [ ] UserProfile populated → dynamic fields shown alongside static
|
||||||
|
- [ ] After multi-turn conversation → profile fields refresh
|
||||||
|
|
||||||
|
**Fix 3 — Memory Display**:
|
||||||
|
- [ ] Memory panel loads with entries visible (not just count)
|
||||||
|
- [ ] Memory graph renders with nodes and edges
|
||||||
|
- [ ] Pagination works for > 50 entries
|
||||||
|
|
||||||
|
**Fix 5 — Hand Trigger**:
|
||||||
|
- [ ] Existing autonomy configs migrate correctly (no handAutoTrigger → default false)
|
||||||
|
- [ ] Autonomous level → hand_trigger auto-allowed
|
||||||
|
- [ ] Supervised/guided → hand_trigger requires approval
|
||||||
|
|
||||||
|
**Fix 4 — Reflection**:
|
||||||
|
- [ ] After 3+ conversations → reflection triggers automatically
|
||||||
|
- [ ] History shows multiple entries (not just 1)
|
||||||
|
- [ ] Each entry shows pattern/improvement/proposal counts
|
||||||
|
|
||||||
|
**Fix 6 — Evolution**:
|
||||||
|
- [ ] Click history item → expandable diff view appears
|
||||||
|
- [ ] Diff view shows before/after for changed files
|
||||||
|
|
||||||
|
**Fix 7 — Butler**:
|
||||||
|
- [ ] Butler tab shows contextual header (not chat stats)
|
||||||
|
- [ ] "Analyze now" button disabled when < 2 messages
|
||||||
|
- [ ] Empty state shows onboarding message
|
||||||
|
|
||||||
|
### Documentation Updates (per CLAUDE.md §8.3)
|
||||||
|
- Update `docs/TRUTH.md` if command count changes
|
||||||
|
- Update `wiki/log.md` with change record
|
||||||
|
- Update `wiki/module-status.md` if module status affected
|
||||||
|
|
||||||
|
## Constraint Compliance
|
||||||
|
|
||||||
|
| Constraint | Status |
|
||||||
|
|------------|--------|
|
||||||
|
| No new SaaS API endpoints | OK |
|
||||||
|
| No new SKILL.md files | OK |
|
||||||
|
| No new middleware | OK |
|
||||||
|
| No new stores | OK — using component-local state |
|
||||||
|
| No new admin pages | OK |
|
||||||
|
| No new Tauri commands | OK — Fix 2 extends existing `agent_get` command instead |
|
||||||
|
|
||||||
|
Note: Fix 2 originally proposed a new Tauri command but was revised to extend existing `agent_get` to comply with stabilization constraint.
|
||||||
Reference in New Issue
Block a user