Files
zclaw_openfang/docs/superpowers/plans/2026-04-11-detail-panel-7-fixes.md
iven b90306ea4b
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
docs(plan): 详情面板7问题修复实施计划
15个Task分3个Batch:
Batch 1 (P0): memory_search空查询/hand_trigger映射/聊天路由竞态
Batch 2 (P1): 反思阈值+持久化/Agent画像桥接
Batch 3 (P2): 演化差异视图/管家Tab上下文
2026-04-11 12:24:38 +08:00

34 KiB
Raw Permalink Blame History

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:

// 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
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:

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:

hand_trigger: null,

to:

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
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
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:

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:

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:

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
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
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
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:

/// Peek restored state from cache (non-destructive read)
pub fn peek_restored_state(agent_id: &str) -> Option<ReflectionState> {
    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<ReflectionResult> {
    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:

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:

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
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
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:

pub user_profile: Option<zclaw_memory::UserProfile>,

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:

pub async fn agent_get(
    state: State<'_, KernelState>,
    agent_id: String,
) -> Result<Option<AgentInfo>, 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
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:

const [userProfile, setUserProfile] = useState<any>(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:

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:

{/* Dynamic: UserProfile data (from conversation learning) */}
{userProfile && (
  <div className="mt-3 pt-3 border-t border-gray-100 dark:border-gray-800">
    <div className="text-xs text-gray-400 mb-2">对话中了解到的</div>
    {userProfile.industry && (
      <div className="text-xs text-gray-600 dark:text-gray-300">
        行业: {userProfile.industry}
      </div>
    )}
    {userProfile.role && (
      <div className="text-xs text-gray-600 dark:text-gray-300">
        角色: {userProfile.role}
      </div>
    )}
    {userProfile.communication_style && (
      <div className="text-xs text-gray-600 dark:text-gray-300">
        沟通偏好: {userProfile.communication_style}
      </div>
    )}
    {userProfile.recent_topics?.length > 0 && (
      <div className="text-xs text-gray-600 dark:text-gray-300">
        近期话题: {userProfile.recent_topics.slice(0, 5).join(', ')}
      </div>
    )}
  </div>
)}
  • 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:

// 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:

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
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
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:

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<string, string> = {
    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 (
    <div className="rounded-lg bg-gray-50 dark:bg-gray-800/50 border border-gray-100 dark:border-gray-700">
      <div
        className="flex items-start gap-3 p-3 cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-800/70"
        onClick={() => setExpanded(!expanded)}
      >
        <div className="w-8 h-8 rounded-lg bg-gray-200 dark:bg-gray-700 flex items-center justify-center flex-shrink-0">
          <History className="w-4 h-4 text-gray-500 dark:text-gray-400" />
        </div>
        <div className="flex-1 min-w-0">
          <div className="flex items-center justify-between gap-2">
            <span className="text-xs text-gray-500 dark:text-gray-400">{timeAgo}</span>
            <div className="flex items-center gap-2">
              {fileNames.length > 0 && (
                <span className="text-xs text-gray-400">
                  {fileNames.length} 个文件
                </span>
              )}
              <Button
                variant="ghost"
                size="sm"
                onClick={(e) => { e.stopPropagation(); onRestore(); }}
                disabled={isRestoring}
                className="text-xs text-gray-500 hover:text-orange-600"
              >
                恢复
              </Button>
            </div>
          </div>
          <p className="text-sm text-gray-700 dark:text-gray-300 mt-1">
            {snapshot.reason || '自动快照'}
          </p>
          {fileNames.length > 0 && (
            <div className="flex gap-1 mt-1.5">
              {fileNames.map(name => (
                <span
                  key={name}
                  className={`text-[10px] px-1.5 py-0.5 rounded ${fileColorMap[name] || 'bg-gray-100 text-gray-600'}`}
                >
                  {name}
                </span>
              ))}
            </div>
          )}
        </div>
      </div>

      {expanded && snapshot.files && (
        <div className="px-3 pb-3 pt-1 border-t border-gray-200 dark:border-gray-700">
          {snapshot.files.soul && (
            <div className="mt-2">
              <div className="text-xs font-medium text-purple-600 dark:text-purple-400 mb-1">Soul</div>
              <pre className="text-xs text-gray-600 dark:text-gray-400 whitespace-pre-wrap max-h-32 overflow-y-auto bg-white dark:bg-gray-900 p-2 rounded">
                {typeof snapshot.files.soul === 'string'
                  ? snapshot.files.soul.slice(0, 500)
                  : JSON.stringify(snapshot.files.soul, null, 2).slice(0, 500)}
              </pre>
            </div>
          )}
          {snapshot.files.instructions && (
            <div className="mt-2">
              <div className="text-xs font-medium text-blue-600 dark:text-blue-400 mb-1">Instructions</div>
              <pre className="text-xs text-gray-600 dark:text-gray-400 whitespace-pre-wrap max-h-32 overflow-y-auto bg-white dark:bg-gray-900 p-2 rounded">
                {typeof snapshot.files.instructions === 'string'
                  ? snapshot.files.instructions.slice(0, 500)
                  : JSON.stringify(snapshot.files.instructions, null, 2).slice(0, 500)}
              </pre>
            </div>
          )}
        </div>
      )}
    </div>
  );
}

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
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:

{/* 消息统计 — 但ler tab 时显示管家专用摘要 */}
{activeTab === 'butler' ? (
  <div className="px-4 py-2 border-b border-gray-100 dark:border-gray-800 flex items-center justify-between text-xs">
    <div className="flex items-center gap-2 text-gray-500 dark:text-gray-400">
      <span className="font-medium">管家模式</span>
    </div>
    <div className={`flex items-center gap-1 ${connected ? 'text-emerald-500' : 'text-gray-400'}`}>
      {connected ? <Wifi className="w-3.5 h-3.5" /> : <WifiOff className="w-3.5 h-3.5" />}
      <span>{runtimeSummary}</span>
    </div>
  </div>
) : (
  <div className="px-4 py-2 border-b border-gray-100 dark:border-gray-800 flex items-center justify-between text-xs">
    <div className="flex items-center gap-2 text-gray-500 dark:text-gray-400">
      <BarChart3 className="w-3.5 h-3.5" />
      <span>{messageCount} 条消息</span>
      <span className="text-gray-300 dark:text-gray-600">|</span>
      <span>{userMsgCount} 用户 / {assistantMsgCount} 助手</span>
    </div>
    <div className={`flex items-center gap-1 ${connected ? 'text-emerald-500' : 'text-gray-400'}`}>
      {connected ? <Wifi className="w-3.5 h-3.5" /> : <WifiOff className="w-3.5 h-3.5" />}
      <span>{runtimeSummary}</span>
    </div>
  </div>
)}
  • Step 2: Verify TypeScript compilation

Run: cd g:/ZClaw_openfang/desktop && npx tsc --noEmit 2>&1 | tail -10 Expected: No errors

  • Step 3: Commit
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:

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 (
      <div className="flex items-center justify-center h-full">
        <p className="text-sm text-gray-500 dark:text-gray-400">请先选择一个 Agent</p>
      </div>
    );
  }

  return (
    <div className="space-y-6">
      {error && (
        <div className="rounded-lg bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 px-3 py-2 text-xs text-red-700 dark:text-red-300">
          {error}
        </div>
      )}

      {loading && (
        <div className="flex items-center justify-center py-4">
          <div className="w-5 h-5 border-2 border-gray-300 dark:border-gray-600 border-t-blue-500 rounded-full animate-spin" />
        </div>
      )}

      {!loading && !hasData && (
        <div className="flex flex-col items-center justify-center py-8 text-center">
          <div className="text-gray-400 dark:text-gray-500 mb-3">
            <svg className="w-12 h-12 mx-auto" fill="none" viewBox="0 0 24 24" stroke="currentColor">
              <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M9.813 15.904L9 18.75l-.813-2.846a4.5 4.5 0 00-3.09-3.09L2.25 12l2.846-.813a4.5 4.5 0 003.09-3.09L9 5.25l.813 2.846a4.5 4.5 0 003.09 3.09L15.75 12l-2.846.813a4.5 4.5 0 00-3.09 3.09z" />
            </svg>
          </div>
          <p className="text-sm text-gray-500 dark:text-gray-400 mb-3">
            管家正在了解您,多轮对话后会自动发现您的需求
          </p>
          <button
            onClick={handleAnalyze}
            disabled={!canAnalyze || analyzing}
            className={`text-xs px-3 py-1.5 rounded-lg transition-colors ${
              canAnalyze && !analyzing
                ? 'bg-blue-50 dark:bg-blue-900/20 text-blue-600 dark:text-blue-400 hover:bg-blue-100 dark:hover:bg-blue-900/30'
                : 'bg-gray-100 dark:bg-gray-800 text-gray-400 dark:text-gray-500 cursor-not-allowed'
            }`}
          >
            {analyzing ? '分析中...' : canAnalyze ? '立即分析对话' : '至少需要 2 条对话才能分析'}
          </button>
        </div>
      )}

      {(hasData || loading) && (
        <>
          <div>
            <h3 className="text-sm font-semibold text-gray-900 dark:text-gray-100 mb-2">
              我最近在关注
            </h3>
            <InsightsSection painPoints={painPoints} onGenerate={refresh} />
          </div>

          <div>
            <h3 className="text-sm font-semibold text-gray-900 dark:text-gray-100 mb-2">
              我提出的方案
            </h3>
            <ProposalsSection proposals={proposals} onStatusChange={refresh} />
          </div>
        </>
      )}

      <div>
        <h3 className="text-sm font-semibold text-gray-900 dark:text-gray-100 mb-2">
          我记得关于您
        </h3>
        <MemorySection agentId={agentId} />
      </div>
    </div>
  );
}

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
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
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
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