fix(hands): use hand.id instead of hand.name for API calls
- Fix HandTaskPanel to use hand.id when loading runs and triggering - Fix HandsPanel to use hand.id for getHandDetails and triggerHand - Fix WorkflowEditor to use hand.id as option value The API expects hand identifiers, not names. This ensures correct hand execution and run history loading. Also clean up old plan files and add Gateway stability plan. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -57,19 +57,19 @@ export function HandTaskPanel({ handId, onBack }: HandTaskPanelProps) {
|
||||
// Load task history when hand is selected
|
||||
useEffect(() => {
|
||||
if (selectedHand) {
|
||||
loadHandRuns(selectedHand.name, { limit: 50 });
|
||||
loadHandRuns(selectedHand.id, { limit: 50 });
|
||||
}
|
||||
}, [selectedHand, loadHandRuns]);
|
||||
|
||||
// Get runs for this hand from store
|
||||
const tasks: HandRun[] = selectedHand ? (handRuns[selectedHand.name] || []) : [];
|
||||
const tasks: HandRun[] = selectedHand ? (handRuns[selectedHand.id] || []) : [];
|
||||
|
||||
// Refresh task history
|
||||
const handleRefresh = useCallback(async () => {
|
||||
if (!selectedHand) return;
|
||||
setIsRefreshing(true);
|
||||
try {
|
||||
await loadHandRuns(selectedHand.name, { limit: 50 });
|
||||
await loadHandRuns(selectedHand.id, { limit: 50 });
|
||||
} finally {
|
||||
setIsRefreshing(false);
|
||||
}
|
||||
@@ -80,11 +80,11 @@ export function HandTaskPanel({ handId, onBack }: HandTaskPanelProps) {
|
||||
if (!selectedHand) return;
|
||||
setIsActivating(true);
|
||||
try {
|
||||
await triggerHand(selectedHand.name);
|
||||
await triggerHand(selectedHand.id);
|
||||
// Refresh hands list and task history
|
||||
await Promise.all([
|
||||
loadHands(),
|
||||
loadHandRuns(selectedHand.name, { limit: 50 }),
|
||||
loadHandRuns(selectedHand.id, { limit: 50 }),
|
||||
]);
|
||||
} catch {
|
||||
// Error is handled in store
|
||||
|
||||
@@ -367,7 +367,7 @@ export function HandsPanel() {
|
||||
const handleDetails = useCallback(async (hand: Hand) => {
|
||||
// Load full details before showing modal
|
||||
const { getHandDetails } = useGatewayStore.getState();
|
||||
const details = await getHandDetails(hand.name);
|
||||
const details = await getHandDetails(hand.id);
|
||||
setSelectedHand(details || hand);
|
||||
setShowModal(true);
|
||||
}, []);
|
||||
@@ -375,7 +375,7 @@ export function HandsPanel() {
|
||||
const handleActivate = useCallback(async (hand: Hand) => {
|
||||
setActivatingHandId(hand.id);
|
||||
try {
|
||||
await triggerHand(hand.name);
|
||||
await triggerHand(hand.id);
|
||||
// Refresh hands after activation
|
||||
await loadHands();
|
||||
} catch {
|
||||
|
||||
@@ -84,7 +84,7 @@ function StepEditor({ step, hands, index, onUpdate, onRemove, onMoveUp, onMoveDo
|
||||
>
|
||||
<option value="">选择 Hand...</option>
|
||||
{hands.map(hand => (
|
||||
<option key={hand.id} value={hand.name}>
|
||||
<option key={hand.id} value={hand.id}>
|
||||
{hand.name} - {hand.description}
|
||||
</option>
|
||||
))}
|
||||
|
||||
209
plans/virtual-conjuring-stardust.md
Normal file
209
plans/virtual-conjuring-stardust.md
Normal file
@@ -0,0 +1,209 @@
|
||||
# ZCLAW Gateway 连接稳定性与 API 修复计划
|
||||
|
||||
## Context
|
||||
|
||||
**问题背景**: 用户报告"Gateway 连接不稳定",通过 Chrome DevTools 诊断发现:
|
||||
- WebSocket 连接和聊天功能**实际正常工作**
|
||||
- 真正的问题是 **6 个 API 端点返回 404**,大量错误日志被误认为是连接问题
|
||||
|
||||
**诊断结果**:
|
||||
| 项目 | 状态 |
|
||||
|------|------|
|
||||
| WebSocket 连接 | ✅ 正常 |
|
||||
| 消息发送/流式响应 | ✅ 正常 |
|
||||
| 核心 API (agents, hands, workflows) | ✅ 正常 |
|
||||
| 6 个 API 端点 | ❌ 404 错误 |
|
||||
|
||||
**目标**:
|
||||
1. P0: 修复 6 个 404 API 端点(通过前端 fallback 降级)
|
||||
2. P1: 增强连接稳定性(心跳机制 + 改进重连策略)
|
||||
|
||||
---
|
||||
|
||||
## Phase 1: P0 - API Fallback 降级处理
|
||||
|
||||
### 1.1 创建 API Fallback 模块
|
||||
|
||||
**新建文件**: `desktop/src/lib/api-fallbacks.ts`
|
||||
|
||||
```typescript
|
||||
// 提供 6 个 404 API 的降级数据
|
||||
export interface QuickConfigFallback { ... }
|
||||
export interface WorkspaceInfoFallback { ... }
|
||||
export interface UsageStatsFallback { ... }
|
||||
export interface PluginStatusFallback { ... }
|
||||
export interface ScheduledTaskFallback { ... }
|
||||
|
||||
export function getQuickConfigFallback(): QuickConfigFallback { ... }
|
||||
export function getWorkspaceInfoFallback(): WorkspaceInfoFallback { ... }
|
||||
export function getUsageStatsFallback(sessions: Session[]): UsageStatsFallback { ... }
|
||||
export function getPluginStatusFallback(skills: SkillInfo[]): PluginStatusFallback { ... }
|
||||
export function getScheduledTasksFallback(triggers: Trigger[]): ScheduledTaskFallback[] { ... }
|
||||
```
|
||||
|
||||
### 1.2 更新 gateway-client.ts
|
||||
|
||||
为每个 404 API 添加结构化 fallback:
|
||||
|
||||
```typescript
|
||||
async getQuickConfig(): Promise<any> {
|
||||
try {
|
||||
return await this.restGet('/api/config/quick');
|
||||
} catch (error) {
|
||||
if ((error as any).status === 404) {
|
||||
return { quickConfig: getQuickConfigFallback() };
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 1.3 更新 gatewayStore.ts
|
||||
|
||||
在数据加载时使用 fallback:
|
||||
|
||||
```typescript
|
||||
loadUsageStats: async () => {
|
||||
try {
|
||||
const stats = await get().client.getUsageStats();
|
||||
set({ usageStats: stats });
|
||||
} catch {
|
||||
const fallback = getUsageStatsFallback(get().sessions);
|
||||
set({ usageStats: fallback });
|
||||
}
|
||||
},
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Phase 2: P1 - 心跳机制
|
||||
|
||||
### 2.1 添加心跳字段
|
||||
|
||||
**修改文件**: `desktop/src/lib/gateway-client.ts`
|
||||
|
||||
```typescript
|
||||
// 新增私有字段
|
||||
private heartbeatInterval: number | null = null;
|
||||
private heartbeatTimeout: number | null = null;
|
||||
private missedHeartbeats: number = 0;
|
||||
private static readonly HEARTBEAT_INTERVAL = 30000; // 30秒
|
||||
private static readonly HEARTBEAT_TIMEOUT = 10000; // 10秒
|
||||
private static readonly MAX_MISSED_HEARTBEATS = 3;
|
||||
```
|
||||
|
||||
### 2.2 心跳方法
|
||||
|
||||
```typescript
|
||||
private startHeartbeat(): void { ... }
|
||||
private stopHeartbeat(): void { ... }
|
||||
private sendHeartbeat(): void { ... }
|
||||
private handlePong(): void { ... }
|
||||
```
|
||||
|
||||
### 2.3 集成到连接流程
|
||||
|
||||
- `connect()` 成功后调用 `startHeartbeat()`
|
||||
- `cleanup()` 时调用 `stopHeartbeat()`
|
||||
- `handleFrame()` 处理 `pong` 响应
|
||||
|
||||
---
|
||||
|
||||
## Phase 3: P1 - 改进重连策略
|
||||
|
||||
### 3.1 新增配置选项
|
||||
|
||||
```typescript
|
||||
interface GatewayClientOptions {
|
||||
maxReconnectAttempts?: number; // -1=无限, 0=禁用, 默认10
|
||||
reconnectBackoff?: 'linear' | 'exponential' | 'fixed';
|
||||
}
|
||||
```
|
||||
|
||||
### 3.2 更新 scheduleReconnect
|
||||
|
||||
- 支持无限重连模式 (`maxReconnectAttempts: -1`)
|
||||
- 添加重连事件通知 (`reconnecting`, `reconnect_failed`)
|
||||
- 改进退避算法
|
||||
|
||||
### 3.3 流式 WebSocket 重连
|
||||
|
||||
为 `openfangWs` 添加重连逻辑:
|
||||
|
||||
```typescript
|
||||
private streamState = { agentId: null, sessionId: null, lastMessage: null };
|
||||
private scheduleStreamReconnect(runId: string): void { ... }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Phase 4: UI 集成
|
||||
|
||||
### 4.1 创建 ConnectionStatus 组件
|
||||
|
||||
**新建文件**: `desktop/src/components/ConnectionStatus.tsx`
|
||||
|
||||
```typescript
|
||||
export function ConnectionStatus() {
|
||||
const { connectionState } = useGatewayStore();
|
||||
|
||||
const statusConfig = {
|
||||
disconnected: { color: 'red', label: '已断开', icon: WifiOff },
|
||||
connecting: { color: 'yellow', label: '连接中...', icon: Loader2 },
|
||||
connected: { color: 'green', label: '已连接', icon: Wifi },
|
||||
reconnecting: { color: 'orange', label: '重连中...', icon: RefreshCw },
|
||||
};
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
### 4.2 集成到 ChatArea
|
||||
|
||||
在聊天区域顶部显示连接状态指示器。
|
||||
|
||||
---
|
||||
|
||||
## 关键文件
|
||||
|
||||
| 文件 | 修改类型 | 说明 |
|
||||
|------|----------|------|
|
||||
| `desktop/src/lib/api-fallbacks.ts` | 新建 | API 降级数据 |
|
||||
| `desktop/src/lib/gateway-client.ts` | 修改 | 心跳 + 重连 + fallback |
|
||||
| `desktop/src/store/gatewayStore.ts` | 修改 | 使用 fallback |
|
||||
| `desktop/src/components/ConnectionStatus.tsx` | 新建 | 连接状态 UI |
|
||||
| `desktop/src/components/ChatArea.tsx` | 修改 | 集成状态指示器 |
|
||||
| `tests/desktop/gatewayStore.test.ts` | 修改 | 测试覆盖 |
|
||||
|
||||
---
|
||||
|
||||
## Verification
|
||||
|
||||
### 测试清单
|
||||
|
||||
**P0 - API Fallback**:
|
||||
- [ ] 启动应用,检查控制台无 404 错误
|
||||
- [ ] 用量统计面板显示数据(即使 API 404)
|
||||
- [ ] 设置页面显示配置(即使 API 404)
|
||||
|
||||
**P1 - 心跳**:
|
||||
- [ ] 连接后空闲 5 分钟,连接保持
|
||||
- [ ] 检查 WebSocket 流量有 ping/pong 帧
|
||||
|
||||
**P1 - 重连**:
|
||||
- [ ] 关闭 OpenFang,显示"重连中"
|
||||
- [ ] 重启 OpenFang,自动重连成功
|
||||
- [ ] 聊天中断开后自动恢复
|
||||
|
||||
### 运行测试
|
||||
|
||||
```bash
|
||||
pnpm vitest run tests/desktop/gatewayStore.test.ts
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Implementation Sequence
|
||||
|
||||
1. **Day 1**: Phase 1 - API Fallbacks
|
||||
2. **Day 2**: Phase 2 - Heartbeat + Phase 3 - Reconnection
|
||||
3. **Day 3**: Phase 4 - UI Integration + Testing
|
||||
Reference in New Issue
Block a user