15个Task分3个Batch: Batch 1 (P0): memory_search空查询/hand_trigger映射/聊天路由竞态 Batch 2 (P1): 反思阈值+持久化/Agent画像桥接 Batch 3 (P2): 演化差异视图/管家Tab上下文
34 KiB
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): addhandAutoTrigger: false,afterautoReflection: false, -
assisted(line 131): addhandAutoTrigger: false,afterautoReflection: true, -
autonomous(line 148): addhandAutoTrigger: true,afterautoReflection: 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