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
15个Task分3个Batch: Batch 1 (P0): memory_search空查询/hand_trigger映射/聊天路由竞态 Batch 2 (P1): 反思阈值+持久化/Agent画像桥接 Batch 3 (P2): 演化差异视图/管家Tab上下文
1019 lines
34 KiB
Markdown
1019 lines
34 KiB
Markdown
# 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<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:
|
||
|
||
```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<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:
|
||
|
||
```rust
|
||
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**
|
||
|
||
```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<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:
|
||
|
||
```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 && (
|
||
<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:
|
||
|
||
```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<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**
|
||
|
||
```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' ? (
|
||
<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**
|
||
|
||
```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 (
|
||
<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**
|
||
|
||
```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**
|