From b90306ea4bd49bca1068a7bfc8f93c53cd773798 Mon Sep 17 00:00:00 2001 From: iven Date: Sat, 11 Apr 2026 12:24:38 +0800 Subject: [PATCH] =?UTF-8?q?docs(plan):=20=E8=AF=A6=E6=83=85=E9=9D=A2?= =?UTF-8?q?=E6=9D=BF7=E9=97=AE=E9=A2=98=E4=BF=AE=E5=A4=8D=E5=AE=9E?= =?UTF-8?q?=E6=96=BD=E8=AE=A1=E5=88=92?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 15个Task分3个Batch: Batch 1 (P0): memory_search空查询/hand_trigger映射/聊天路由竞态 Batch 2 (P1): 反思阈值+持久化/Agent画像桥接 Batch 3 (P2): 演化差异视图/管家Tab上下文 --- .../plans/2026-04-11-detail-panel-7-fixes.md | 1018 +++++++++++++++++ 1 file changed, 1018 insertions(+) create mode 100644 docs/superpowers/plans/2026-04-11-detail-panel-7-fixes.md diff --git a/docs/superpowers/plans/2026-04-11-detail-panel-7-fixes.md b/docs/superpowers/plans/2026-04-11-detail-panel-7-fixes.md new file mode 100644 index 0000000..4ab5f38 --- /dev/null +++ b/docs/superpowers/plans/2026-04-11-detail-panel-7-fixes.md @@ -0,0 +1,1018 @@ +# Detail Panel 7-Issue Fix Implementation Plan + +> **For agentic workers:** REQUIRED: Use superpowers:subagent-driven-development (if subagents available) or superpowers:executing-plans to implement this plan. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Fix 7 issues in ZCLAW desktop detail panel — P0 broken functionality, P1 data disconnects, P2 UX gaps. + +**Architecture:** Path B — System Bridge. Fix bugs directly; bridge disconnected backend systems to frontend without adding new SaaS endpoints, stores, or middleware. + +**Tech Stack:** Rust (Tauri commands, VikingStorage), TypeScript (React, Zustand), Tailwind CSS. + +**Spec:** `docs/superpowers/specs/2026-04-11-detail-panel-7-fixes-design.md` + +--- + +## File Map + +| Action | File | Responsibility | +|--------|------|----------------| +| Modify | `desktop/src-tauri/src/memory_commands.rs:159-164` | Fix empty-query memory search (Issue 3) | +| Modify | `desktop/src/lib/autonomy-manager.ts:258-276` | Fix hand_trigger null mapping (Issue 5) | +| Modify | `desktop/src/lib/autonomy-manager.ts:105-157` | Add handAutoTrigger to default configs (Issue 5) | +| Modify | `desktop/src/lib/saas-session.ts:162-172` | Add timestamp to connection mode (Issue 1) | +| Modify | `desktop/src/store/saasStore.ts:690-696` | Write timestamp on degrade (Issue 1) | +| Modify | `desktop/src/store/saasStore.ts:750-798` | Validate token before accepting persisted mode (Issue 1) | +| Modify | `desktop/src-tauri/src/intelligence/reflection.rs:393-463` | Lower pattern thresholds (Issue 4) | +| Modify | `desktop/src-tauri/src/intelligence_hooks.rs:116-121` | Fix pop → peek (Issue 4) | +| Modify | `desktop/src-tauri/src/kernel_commands/agent.rs:169-184` | Extend agent_get with UserProfile (Issue 2) | +| Modify | `desktop/src/components/RightPanel.tsx:374-386` | Contextual header for butler tab (Issue 7) | +| Modify | `desktop/src/components/RightPanel.tsx:523-557` | Dual-source "How I See You" rendering (Issue 2) | +| Modify | `desktop/src/components/IdentityChangeProposal.tsx:269-304` | Expandable HistoryItem (Issue 6) | +| Modify | `desktop/src/components/ButlerPanel/index.tsx` | Enhanced empty state + analyze button (Issue 7) | +| Modify | `desktop/src/components/ReflectionLog.tsx` | Enhanced reflection visibility (Issue 4) | + +--- + +## Chunk 1: Batch 1 — P0 Fixes (Issues 3, 5, 1) + +### Task 1: Fix memory_search empty query (Issue 3) + +**Files:** +- Modify: `desktop/src-tauri/src/memory_commands.rs:159-164` + +- [ ] **Step 1: Write the fix** + +In `memory_commands.rs`, after line 159 (`let query = ...`) and before line 161 (`let find_options = ...`), add empty-query fallback logic: + +```rust +// Build search query +let query = options.query.unwrap_or_default(); + +// When query is empty, use min_similarity=0.0 to trigger table scan +// (FTS5 requires non-empty query; without this, empty query returns 0 results) +let min_similarity = if query.trim().is_empty() { + Some(0.0) +} else { + options.min_importance.map(|i| (i as f32) / 10.0) +}; + +let find_options = zclaw_growth::FindOptions { + scope, + limit: options.limit.or(Some(50)), + min_similarity, +}; +``` + +This replaces lines 159-165 with the above logic. + +- [ ] **Step 2: Verify Rust compilation** + +Run: `cd g:/ZClaw_openfang && cargo check -p zclaw-kernel 2>&1 | tail -5` +Expected: `Finished` without errors + +- [ ] **Step 3: Commit** + +```bash +cd g:/ZClaw_openfang +git add desktop/src-tauri/src/memory_commands.rs +git commit -m "fix(memory): memory_search 空查询时默认 min_similarity=0.0 触发表扫描 + +根因: FTS5 空查询返回 0 条,而 memory_stats 因设 min_similarity=Some(0.0) +走表扫描才正确计数。统一空查询行为。 +Closes #3" +``` + +--- + +### Task 2: Fix hand_trigger null mapping (Issue 5) + +**Files:** +- Modify: `desktop/src/lib/autonomy-manager.ts:258-276` (actionMap) +- Modify: `desktop/src/lib/autonomy-manager.ts:105-157` (default configs) + +- [ ] **Step 1: Add handAutoTrigger to allowedActions type** + +Find the `allowedActions` type definition (search for `interface` or type that defines `memoryAutoSave`, `identityAutoUpdate`, etc.). Add: + +```typescript +handAutoTrigger: boolean; +``` + +to the allowedActions shape. It's defined inline in `AutonomyConfig` interface — find `autoReflection: boolean;` and add `handAutoTrigger: boolean;` after it. + +- [ ] **Step 2: Update actionMap at line 268** + +Change line 268 from: +```typescript +hand_trigger: null, +``` +to: +```typescript +hand_trigger: 'handAutoTrigger', +``` + +- [ ] **Step 3: Add handAutoTrigger to all three default configs** + +In `DEFAULT_AUTONOMY_CONFIGS` (lines 105-157), add `handAutoTrigger` to each level: + +- `supervised` (line 114): add `handAutoTrigger: false,` after `autoReflection: false,` +- `assisted` (line 131): add `handAutoTrigger: false,` after `autoReflection: true,` +- `autonomous` (line 148): add `handAutoTrigger: true,` after `autoReflection: true,` + +- [ ] **Step 4: Verify TypeScript compilation** + +Run: `cd g:/ZClaw_openfang/desktop && npx tsc --noEmit 2>&1 | tail -10` +Expected: No errors related to `handAutoTrigger` + +- [ ] **Step 5: Commit** + +```bash +cd g:/ZClaw_openfang +git add desktop/src/lib/autonomy-manager.ts +git commit -m "fix(autonomy): hand_trigger 从 null 映射改为 handAutoTrigger 字段 + +根因: autonomy-manager.ts:268 将 hand_trigger 硬编码为 null, +导致任何自主权级别都无法自动触发 Hand。 +新增 handAutoTrigger 字段,autonomous 级别默认 true。 +Closes #5" +``` + +--- + +### Task 3: Add handAutoTrigger toggle to AutonomyConfig UI + +**Files:** +- Modify: `desktop/src/components/AutonomyConfig.tsx` + +- [ ] **Step 1: Find the toggle section** + +Search `AutonomyConfig.tsx` for existing toggle patterns (e.g., `memoryAutoSave`, `autoReflection`). Find where toggles are rendered. + +- [ ] **Step 2: Add handAutoTrigger toggle** + +Add a toggle row following the existing pattern, with label: +- Chinese: "自动触发 Hand 能力" +- Description: "允许自动执行已启用的自主能力包" +- Map to `allowedActions.handAutoTrigger` + +- [ ] **Step 3: Verify TypeScript compilation** + +Run: `cd g:/ZClaw_openfang/desktop && npx tsc --noEmit 2>&1 | tail -10` +Expected: No errors + +- [ ] **Step 4: Commit** + +```bash +cd g:/ZClaw_openfang +git add desktop/src/components/AutonomyConfig.tsx +git commit -m "feat(ui): 自主配置增加 Hand 自动触发开关" +``` + +--- + +### Task 4: Fix chat routing after restart (Issue 1) + +**Files:** +- Modify: `desktop/src/lib/saas-session.ts:162-172` +- Modify: `desktop/src/store/saasStore.ts:690-696, 750-798` + +- [ ] **Step 1: Add timestamp to saveConnectionMode** + +In `saas-session.ts`, change `saveConnectionMode` (line 162-164) to: + +```typescript +export function saveConnectionMode(mode: string): void { + const data = JSON.stringify({ mode, timestamp: Date.now() }); + localStorage.setItem(SAASMODE_KEY, data); +} +``` + +And change `loadConnectionMode` (line 170-172) to: + +```typescript +export function loadConnectionMode(): string | null { + const raw = localStorage.getItem(SAASMODE_KEY); + if (!raw) return null; + try { + const parsed = JSON.parse(raw); + if (typeof parsed === 'string') return parsed; // legacy format + return parsed.mode ?? null; + } catch { + return raw; // legacy format (plain string) + } +} + +export function loadConnectionModeTimestamp(): number | null { + const raw = localStorage.getItem(SAASMODE_KEY); + if (!raw) return null; + try { + const parsed = JSON.parse(raw); + return parsed.timestamp ?? null; + } catch { + return null; + } +} +``` + +- [ ] **Step 2: Update restoreSession to re-validate degraded mode** + +In `saasStore.ts` `restoreSession()` (line 750), after `const restored = await loadSaaSSession();` and before accepting the persisted connection mode, add staleness validation. + +The key change: after the existing token refresh logic (around line 798), before the function decides the final connection mode, check: + +```typescript +import { loadConnectionModeTimestamp } from '../lib/saas-session'; + +// After token validation logic completes... +// If account was successfully retrieved via refresh, force 'saas' mode +if (account) { + set({ + isAuthenticated: true, + account, + token: newToken, + connectionMode: 'saas', + saasReachable: true, + }); + saveConnectionMode('saas'); + return; +} +``` + +This is the critical fix: if token refresh succeeds, always restore to 'saas' mode regardless of what was persisted. The existing code already does this implicitly if `account` is set, but the issue is that the persisted `'tauri'` mode in localStorage overrides this elsewhere. Verify the full restoreSession flow sets `connectionMode` correctly after account retrieval. + +- [ ] **Step 3: Verify the degrade path writes timestamp correctly** + +Verify `saveConnectionMode('tauri')` call at line 696 now writes timestamp via the updated function. + +- [ ] **Step 4: Verify TypeScript compilation** + +Run: `cd g:/ZClaw_openfang/desktop && npx tsc --noEmit 2>&1 | tail -10` +Expected: No errors + +- [ ] **Step 5: Commit** + +```bash +cd g:/ZClaw_openfang +git add desktop/src/lib/saas-session.ts desktop/src/store/saasStore.ts +git commit -m "fix(auth): 修复重启后无法对话 — restoreSession 优先验证 SaaS token + +根因: 心跳降级将 'tauri' 持久化到 localStorage,重启后盲信该值。 +修复: token refresh 成功时强制恢复 'saas' 模式;connectionMode 携带时间戳。 +Closes #1" +``` + +--- + +### Task 5: Batch 1 verification + +- [ ] **Step 1: Full TypeScript check** + +Run: `cd g:/ZClaw_openfang/desktop && npx tsc --noEmit 2>&1 | tail -10` +Expected: 0 errors + +- [ ] **Step 2: Full Rust check** + +Run: `cd g:/ZClaw_openfang && cargo check 2>&1 | tail -5` +Expected: `Finished` without errors + +- [ ] **Step 3: Frontend tests** + +Run: `cd g:/ZClaw_openfang/desktop && npx vitest run 2>&1 | tail -10` +Expected: All tests pass + +- [ ] **Step 4: Push** + +```bash +cd g:/ZClaw_openfang && git push +``` + +--- + +## Chunk 2: Batch 2 — P1 Fixes (Issues 4, 2) + +### Task 6: Lower reflection pattern thresholds (Issue 4 — part 1) + +**Files:** +- Modify: `desktop/src-tauri/src/intelligence/reflection.rs:393-463` + +- [ ] **Step 1: Lower thresholds in analyze_patterns()** + +Change 5 threshold values in `analyze_patterns()`: + +| Pattern | Old | New | Line | +|---------|-----|-----|------| +| task accumulation | `>= 5` | `>= 3` | 393 | +| preference accumulation | `>= 5` | `>= 3` | 411 | +| lessons learned | `>= 5` | `>= 3` | 429 | +| high-access memories | `>= 3` | `>= 2` | 450 | +| low-importance memories | `> 20` | `> 15` | 463 | + +Also update observation text for low-importance: +- Line 465: `"有 {} 条低重要性记忆,建议清理"` → `"有 {} 条低重要性记忆,可考虑清理"` + +- [ ] **Step 2: Verify Rust compilation** + +Run: `cd g:/ZClaw_openfang && cargo check 2>&1 | tail -5` +Expected: `Finished` without errors + +- [ ] **Step 3: Commit** + +```bash +cd g:/ZClaw_openfang +git add desktop/src-tauri/src/intelligence/reflection.rs +git commit -m "fix(reflection): 降低模式检测阈值 5→3/20→15 以产生更多有意义反思" +``` + +--- + +### Task 7: Fix reflection state restore race (Issue 4 — part 2) + +**Files:** +- Modify: `desktop/src-tauri/src/intelligence/reflection.rs:705-722` (pop → peek) +- Modify: `desktop/src-tauri/src/intelligence_hooks.rs:116-121` (consume pattern) + +- [ ] **Step 1: Add peek functions alongside pop** + +In `reflection.rs`, after `pop_restored_result()` (line 722), add peek variants: + +```rust +/// Peek restored state from cache (non-destructive read) +pub fn peek_restored_state(agent_id: &str) -> Option { + let cache = get_state_cache(); + cache.read().ok()?.get(agent_id).cloned() +} + +/// Peek restored result from cache (non-destructive read) +pub fn peek_restored_result(agent_id: &str) -> Option { + let cache = get_result_cache(); + cache.read().ok()?.get(agent_id).cloned() +} +``` + +Note: `ReflectionState` and `ReflectionResult` must derive `Clone`. Verify they do; if not, add `#[derive(Clone)]`. + +- [ ] **Step 2: Update intelligence_hooks to use peek + cleanup-on-next** + +In `intelligence_hooks.rs`, change lines 116-121 from: + +```rust +if let Some(restored_state) = crate::intelligence::reflection::pop_restored_state(agent_id) { + engine.apply_restored_state(restored_state); +} +if let Some(restored_result) = crate::intelligence::reflection::pop_restored_result(agent_id) { + engine.apply_restored_result(restored_result); +} +``` + +to: + +```rust +if let Some(restored_state) = crate::intelligence::reflection::peek_restored_state(agent_id) { + engine.apply_restored_state(restored_state); + // Pop after successful apply to prevent re-processing + crate::intelligence::reflection::pop_restored_state(agent_id); +} +if let Some(restored_result) = crate::intelligence::reflection::peek_restored_result(agent_id) { + engine.apply_restored_result(restored_result); + crate::intelligence::reflection::pop_restored_result(agent_id); +} +``` + +This ensures `getHistory` can find the data in VikingStorage before it's consumed. The pop happens after apply, not before read. + +- [ ] **Step 3: Verify Rust compilation** + +Run: `cd g:/ZClaw_openfang && cargo check 2>&1 | tail -5` +Expected: `Finished` without errors + +- [ ] **Step 4: Commit** + +```bash +cd g:/ZClaw_openfang +git add desktop/src-tauri/src/intelligence/reflection.rs desktop/src-tauri/src/intelligence_hooks.rs +git commit -m "fix(reflection): 修复 state restore 竞态 — peek+pop 替代直接 pop + +根因: pop_restored_state 在 getHistory 读取前删除数据。 +修复: 先 peek 非破坏性读取,apply 后再 pop,确保数据可被多次读取。" +``` + +--- + +### Task 8: Enhance reflection visibility UI (Issue 4 — part 3) + +**Files:** +- Modify: `desktop/src/components/ReflectionLog.tsx` + +- [ ] **Step 1: Find the history item rendering** + +Search for the component that renders reflection history entries. Find where each entry displays its content. + +- [ ] **Step 2: Add pattern/improvement/proposal summary to each entry** + +For each reflection history entry, add summary badges: +- Pattern count badge: `发现 {n} 个模式` +- Improvement count badge: `{n} 条建议` +- Identity proposal count badge: `{n} 项身份变更` + +Also add timeline text: "第 {conversations} 次对话后触发" + +- [ ] **Step 3: Add expandable detail section** + +When a history entry is clicked/expanded, show: +- Pattern observations (observation text, sentiment, frequency) +- Improvement suggestions (area, suggestion, priority) +- Identity proposals count + +- [ ] **Step 4: Verify TypeScript compilation** + +Run: `cd g:/ZClaw_openfang/desktop && npx tsc --noEmit 2>&1 | tail -10` +Expected: No errors + +- [ ] **Step 5: Commit** + +```bash +cd g:/ZClaw_openfang +git add desktop/src/components/ReflectionLog.tsx +git commit -m "feat(ui): 反思历史增加模式/建议/提案摘要 + 可展开详情" +``` + +--- + +### Task 9: Extend agent_get with UserProfile (Issue 2 — part 1) + +**Files:** +- Modify: `desktop/src-tauri/src/kernel_commands/agent.rs:169-184` + +- [ ] **Step 1: Check AgentInfo type** + +Search for `AgentInfo` struct definition (likely in `crates/zclaw-kernel/src/kernel/agents.rs` or `crates/zclaw-types/src/agent.rs`). Understand its current fields. + +- [ ] **Step 2: Add optional user_profile field to AgentInfo** + +Add to `AgentInfo`: +```rust +pub user_profile: Option, +``` + +If `UserProfile` doesn't implement `Serialize`/`Clone`, add the derives. + +- [ ] **Step 3: Populate user_profile in agent_get command** + +In `agent.rs` `agent_get()`, after getting the agent info, fetch UserProfile: + +```rust +pub async fn agent_get( + state: State<'_, KernelState>, + agent_id: String, +) -> Result, String> { + let agent_id = validate_agent_id(&agent_id)?; + let kernel_lock = state.lock().await; + let kernel = kernel_lock.as_ref() + .ok_or_else(|| "Kernel not initialized".to_string())?; + let id: AgentId = agent_id.parse() + .map_err(|_| "Invalid agent ID format".to_string())?; + + let mut info = kernel.get_agent(&id); + + // Extend with UserProfile if available + if let Some(ref mut agent_info) = info { + if let Ok(storage) = crate::viking_commands::get_storage().await { + let profile_store = zclaw_memory::UserProfileStore::new(storage); + agent_info.user_profile = profile_store.get(&agent_id).await.ok(); + } + } + + Ok(info) +} +``` + +Note: Adjust the exact API based on how `UserProfileStore` is constructed and its `get` method signature. Verify in `crates/zclaw-memory/src/user_profile_store.rs`. + +- [ ] **Step 4: Verify Rust compilation** + +Run: `cd g:/ZClaw_openfang && cargo check 2>&1 | tail -5` +Expected: `Finished` without errors + +- [ ] **Step 5: Commit** + +```bash +cd g:/ZClaw_openfang +git add desktop/src-tauri/src/kernel_commands/agent.rs +git commit -m "feat(kernel): agent_get 返回值扩展 UserProfile 字段 + +复用已有 UserProfileStore,不新增 Tauri 命令。 +前端可从 agent 数据直接读取用户画像。" +``` + +--- + +### Task 10: RightPanel dual-source "How I See You" (Issue 2 — part 2) + +**Files:** +- Modify: `desktop/src/components/RightPanel.tsx:523-557` +- Modify: `desktop/src/store/chat/streamStore.ts` (add profile refresh trigger) + +- [ ] **Step 1: Add userProfile state to RightPanel** + +Near the top of the `RightPanel` component function, add: + +```typescript +const [userProfile, setUserProfile] = useState(null); +``` + +- [ ] **Step 2: Fetch UserProfile on mount and agent change** + +Add a `useEffect` that fetches agent data (which now includes user_profile) when `currentAgent?.id` changes: + +```typescript +useEffect(() => { + if (!currentAgent?.id) return; + // Fetch full agent data including UserProfile + intelligenceClient.agents.getAgent(currentAgent.id) + .then(data => setUserProfile(data?.user_profile ?? null)) + .catch(() => setUserProfile(null)); +}, [currentAgent?.id]); +``` + +Adjust the API call based on how the frontend fetches agent data. Check `desktop/src/lib/kernel-agent.ts` or `gateway-api.ts` for the `getAgent` method. + +- [ ] **Step 3: Render dual-source "How I See You"** + +In the "我眼中的你" section (lines 523-557), after the existing static Clone fields, add dynamic UserProfile fields: + +```tsx +{/* Dynamic: UserProfile data (from conversation learning) */} +{userProfile && ( +
+
对话中了解到的
+ {userProfile.industry && ( +
+ 行业: {userProfile.industry} +
+ )} + {userProfile.role && ( +
+ 角色: {userProfile.role} +
+ )} + {userProfile.communication_style && ( +
+ 沟通偏好: {userProfile.communication_style} +
+ )} + {userProfile.recent_topics?.length > 0 && ( +
+ 近期话题: {userProfile.recent_topics.slice(0, 5).join(', ')} +
+ )} +
+)} +``` + +- [ ] **Step 4: Add refresh trigger after stream completion** + +In `streamStore.ts`, find the flow completion callback (~line 446 where `getMemoryExtractor().extractFromConversation()` is called). After it, add a trigger to refresh the agent's UserProfile. Use a custom event or direct re-fetch: + +```typescript +// After memory extraction completes, notify RightPanel to refresh UserProfile +window.dispatchEvent(new CustomEvent('zclaw:agent-profile-updated', { + detail: { agentId } +})); +``` + +In RightPanel, listen for this event: + +```typescript +useEffect(() => { + const handler = (e: CustomEvent) => { + if (e.detail.agentId === currentAgent?.id) { + // Re-fetch agent data including updated UserProfile + intelligenceClient.agents.getAgent(currentAgent.id) + .then(data => setUserProfile(data?.user_profile ?? null)) + .catch(() => {}); + } + }; + window.addEventListener('zclaw:agent-profile-updated', handler as EventListener); + return () => window.removeEventListener('zclaw:agent-profile-updated', handler as EventListener); +}, [currentAgent?.id]); +``` + +- [ ] **Step 5: Verify TypeScript compilation** + +Run: `cd g:/ZClaw_openfang/desktop && npx tsc --noEmit 2>&1 | tail -10` +Expected: No errors + +- [ ] **Step 6: Commit** + +```bash +cd g:/ZClaw_openfang +git add desktop/src/components/RightPanel.tsx desktop/src/store/chat/streamStore.ts +git commit -m "feat(ui): '我眼中的你' 双源渲染 — 静态Clone + 动态UserProfile + +桥接 identity 系统 → 前端面板,对话结束后自动刷新画像。 +Closes #2" +``` + +--- + +### Task 11: Batch 2 verification + +- [ ] **Step 1: Full TypeScript check** + +Run: `cd g:/ZClaw_openfang/desktop && npx tsc --noEmit 2>&1 | tail -10` +Expected: 0 errors + +- [ ] **Step 2: Full Rust check** + +Run: `cd g:/ZClaw_openfang && cargo check 2>&1 | tail -5` +Expected: `Finished` without errors + +- [ ] **Step 3: Frontend tests** + +Run: `cd g:/ZClaw_openfang/desktop && npx vitest run 2>&1 | tail -10` +Expected: All tests pass + +- [ ] **Step 4: Push** + +```bash +cd g:/ZClaw_openfang && git push +``` + +--- + +## Chunk 3: Batch 3 — P2 Fixes (Issues 6, 7) + +### Task 12: Add expandable diff to HistoryItem (Issue 6) + +**Files:** +- Modify: `desktop/src/components/IdentityChangeProposal.tsx:269-304` + +- [ ] **Step 1: Add expand state to HistoryItem** + +Modify `HistoryItem` component to add expandable state and file change display: + +```tsx +function HistoryItem({ + snapshot, + onRestore, + isRestoring, +}: { + snapshot: IdentitySnapshot; + onRestore: () => void; + isRestoring: boolean; +}) { + const [expanded, setExpanded] = useState(false); + const timeAgo = getTimeAgo(snapshot.timestamp); + + // Determine which files changed + const fileNames: string[] = []; + if (snapshot.files) { + if (snapshot.files.soul) fileNames.push('soul'); + if (snapshot.files.instructions) fileNames.push('instructions'); + if (snapshot.files.user_profile) fileNames.push('user_profile'); + } + + const fileColorMap: Record = { + soul: 'bg-purple-100 text-purple-700 dark:bg-purple-900/30 dark:text-purple-300', + instructions: 'bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-300', + user_profile: 'bg-emerald-100 text-emerald-700 dark:bg-emerald-900/30 dark:text-emerald-300', + }; + + return ( +
+
setExpanded(!expanded)} + > +
+ +
+
+
+ {timeAgo} +
+ {fileNames.length > 0 && ( + + {fileNames.length} 个文件 + + )} + +
+
+

+ {snapshot.reason || '自动快照'} +

+ {fileNames.length > 0 && ( +
+ {fileNames.map(name => ( + + {name} + + ))} +
+ )} +
+
+ + {expanded && snapshot.files && ( +
+ {snapshot.files.soul && ( +
+
Soul
+
+                {typeof snapshot.files.soul === 'string'
+                  ? snapshot.files.soul.slice(0, 500)
+                  : JSON.stringify(snapshot.files.soul, null, 2).slice(0, 500)}
+              
+
+ )} + {snapshot.files.instructions && ( +
+
Instructions
+
+                {typeof snapshot.files.instructions === 'string'
+                  ? snapshot.files.instructions.slice(0, 500)
+                  : JSON.stringify(snapshot.files.instructions, null, 2).slice(0, 500)}
+              
+
+ )} +
+ )} +
+ ); +} +``` + +Note: Adjust `snapshot.files` field names based on actual `IdentitySnapshot` type definition. Check `desktop/src/lib/intelligence-client/types.ts` for the exact shape. + +- [ ] **Step 2: Verify TypeScript compilation** + +Run: `cd g:/ZClaw_openfang/desktop && npx tsc --noEmit 2>&1 | tail -10` +Expected: No errors + +- [ ] **Step 3: Commit** + +```bash +cd g:/ZClaw_openfang +git add desktop/src/components/IdentityChangeProposal.tsx +git commit -m "feat(ui): 演化历史条目增加可展开差异视图 + 文件变更标签 + +点击展开显示 soul/instructions 变更内容,不再截断原因文本。 +Closes #6" +``` + +--- + +### Task 13: Butler contextual header (Issue 7 — part 1) + +**Files:** +- Modify: `desktop/src/components/RightPanel.tsx:374-386` + +- [ ] **Step 1: Add conditional rendering for stats bar** + +Wrap the existing message stats bar (lines 374-386) with a condition: + +```tsx +{/* 消息统计 — 但ler tab 时显示管家专用摘要 */} +{activeTab === 'butler' ? ( +
+
+ 管家模式 +
+
+ {connected ? : } + {runtimeSummary} +
+
+) : ( +
+
+ + {messageCount} 条消息 + | + {userMsgCount} 用户 / {assistantMsgCount} 助手 +
+
+ {connected ? : } + {runtimeSummary} +
+
+)} +``` + +- [ ] **Step 2: Verify TypeScript compilation** + +Run: `cd g:/ZClaw_openfang/desktop && npx tsc --noEmit 2>&1 | tail -10` +Expected: No errors + +- [ ] **Step 3: Commit** + +```bash +cd g:/ZClaw_openfang +git add desktop/src/components/RightPanel.tsx +git commit -m "fix(ui): 管家 Tab 统计栏显示管家专属摘要,不再显示聊天统计" +``` + +--- + +### Task 14: Butler empty state enhancement (Issue 7 — part 2) + +**Files:** +- Modify: `desktop/src/components/ButlerPanel/index.tsx` + +- [ ] **Step 1: Add empty state and analyze button** + +Modify `ButlerPanel` to show enhanced empty state when all data is empty: + +```tsx +import { useButlerInsights } from '../../hooks/useButlerInsights'; +import { useConversationStore } from '../../store/chat/conversationStore'; +import { InsightsSection } from './InsightsSection'; +import { ProposalsSection } from './ProposalsSection'; +import { MemorySection } from './MemorySection'; + +interface ButlerPanelProps { + agentId: string | undefined; +} + +export function ButlerPanel({ agentId }: ButlerPanelProps) { + const { painPoints, proposals, loading, error, refresh } = useButlerInsights(agentId); + const messageCount = useConversationStore((s) => s.messages.length); + const [analyzing, setAnalyzing] = useState(false); + + const hasData = (painPoints?.length ?? 0) > 0 || (proposals?.length ?? 0) > 0; + const canAnalyze = messageCount >= 2; + + const handleAnalyze = async () => { + if (!canAnalyze || analyzing) return; + setAnalyzing(true); + try { + await refresh(); + } finally { + setAnalyzing(false); + } + }; + + if (!agentId) { + return ( +
+

请先选择一个 Agent

+
+ ); + } + + return ( +
+ {error && ( +
+ {error} +
+ )} + + {loading && ( +
+
+
+ )} + + {!loading && !hasData && ( +
+
+ + + +
+

+ 管家正在了解您,多轮对话后会自动发现您的需求 +

+ +
+ )} + + {(hasData || loading) && ( + <> +
+

+ 我最近在关注 +

+ +
+ +
+

+ 我提出的方案 +

+ +
+ + )} + +
+

+ 我记得关于您 +

+ +
+
+ ); +} +``` + +Note: Add `import { useState } from 'react';` if not already imported. + +- [ ] **Step 2: Verify TypeScript compilation** + +Run: `cd g:/ZClaw_openfang/desktop && npx tsc --noEmit 2>&1 | tail -10` +Expected: No errors + +- [ ] **Step 3: Commit** + +```bash +cd g:/ZClaw_openfang +git add desktop/src/components/ButlerPanel/index.tsx +git commit -m "feat(ui): 管家 Tab 空状态增加引导文案 + 立即分析按钮 + +- 无数据时显示友好引导文案 +- 分析按钮要求至少 2 条对话 +- 分析中显示加载状态 +Closes #7" +``` + +--- + +### Task 15: Batch 3 verification + final push + +- [ ] **Step 1: Full TypeScript check** + +Run: `cd g:/ZClaw_openfang/desktop && npx tsc --noEmit 2>&1 | tail -10` +Expected: 0 errors + +- [ ] **Step 2: Full Rust check** + +Run: `cd g:/ZClaw_openfang && cargo check 2>&1 | tail -5` +Expected: `Finished` without errors + +- [ ] **Step 3: Frontend tests** + +Run: `cd g:/ZClaw_openfang/desktop && npx vitest run 2>&1 | tail -10` +Expected: All tests pass + +- [ ] **Step 4: Push all batches** + +```bash +cd g:/ZClaw_openfang && git push +``` + +- [ ] **Step 5: Update documentation per CLAUDE.md §8.3** + +Update `docs/TRUTH.md` if Tauri command count changed (only if agent_get signature was modified). +Update `wiki/log.md` with change record. +Append entry: +``` +### [2026-04-11] 详情面板 7 问题修复 +- P0: 聊天路由竞态/记忆查询缺陷/hand_trigger硬编码 +- P1: Agent画像桥接/反思持久化+阈值 +- P2: 演化差异视图/管家Tab上下文 +``` + +- [ ] **Step 6: Commit and push docs** + +```bash +cd g:/ZClaw_openfang +git add docs/TRUTH.md wiki/log.md +git commit -m "docs: 详情面板7问题修复记录" +git push +``` + +--- + +## Summary + +| Batch | Tasks | Issues | Files Modified | +|-------|-------|--------|----------------| +| 1 | 1-5 | 3, 5, 1 | memory_commands.rs, autonomy-manager.ts, AutonomyConfig.tsx, saas-session.ts, saasStore.ts | +| 2 | 6-11 | 4, 2 | reflection.rs, intelligence_hooks.rs, ReflectionLog.tsx, agent.rs, RightPanel.tsx, streamStore.ts | +| 3 | 12-15 | 6, 7 | IdentityChangeProposal.tsx, RightPanel.tsx, ButlerPanel/index.tsx | + +**Total: 15 tasks, 3 batches, ~14 files modified**