feat: 实现循环防护和安全验证功能
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

refactor(loop_guard): 为LoopGuard添加Clone派生
feat(capabilities): 实现CapabilityManager.validate()安全验证
fix(agentStore): 添加token用量追踪
chore: 删除未实现的Predictor/Lead HAND.toml文件
style(Credits): 移除假数据并标注开发中状态
refactor(Skills): 动态加载技能卡片
perf(configStore): 为定时任务添加localStorage降级
docs: 更新功能文档和版本变更记录
This commit is contained in:
iven
2026-03-27 07:56:53 +08:00
parent 0d4fa96b82
commit eed347e1a6
14 changed files with 724 additions and 476 deletions

View File

@@ -522,13 +522,180 @@ export function createHandClientFromGateway(client: GatewayClient): HandClient {
};
}
// === Kernel Client Adapter ===
import type { KernelClient } from '../lib/kernel-client';
/**
* Helper to create a HandClient adapter from a KernelClient.
* Maps KernelClient methods (Tauri invoke) to the HandClient interface.
*/
function createHandClientFromKernel(client: KernelClient): HandClient {
return {
listHands: async () => {
try {
const result = await client.listHands();
// KernelClient returns typed objects; cast to Record<string, unknown> for HandClient compatibility
const hands: Array<Record<string, unknown>> = result.hands.map((h) => ({
id: h.id || h.name,
name: h.name,
description: h.description,
status: h.status,
requirements_met: h.requirements_met,
category: h.category,
icon: h.icon,
tool_count: h.tool_count,
tools: h.tools,
metric_count: h.metric_count,
metrics: h.metrics,
}));
return { hands };
} catch {
return null;
}
},
getHand: async (name: string) => {
try {
const result = await client.getHand(name);
return result as Record<string, unknown> || null;
} catch {
return null;
}
},
listHandRuns: async (name: string, opts) => {
try {
const result = await client.listHandRuns(name, opts);
return result as unknown as { runs?: RawHandRun[] } | null;
} catch {
return null;
}
},
triggerHand: async (name: string, params) => {
try {
const result = await client.triggerHand(name, params);
return { runId: result.runId, status: result.status };
} catch {
return null;
}
},
approveHand: async (name: string, runId: string, approved: boolean, reason?: string) => {
return client.approveHand(name, runId, approved, reason);
},
cancelHand: async (name: string, runId: string) => {
return client.cancelHand(name, runId);
},
listTriggers: async () => {
try {
const result = await client.listTriggers();
if (!result?.triggers) return { triggers: [] };
// Map KernelClient trigger shape to HandClient Trigger shape
const triggers: Trigger[] = result.triggers.map((t) => ({
id: t.id,
type: t.triggerType,
enabled: t.enabled,
}));
return { triggers };
} catch {
return { triggers: [] };
}
},
getTrigger: async (id: string) => {
try {
const result = await client.getTrigger(id);
if (!result) return null;
return {
id: result.id,
type: result.triggerType,
enabled: result.enabled,
} as Trigger;
} catch {
return null;
}
},
createTrigger: async (trigger) => {
try {
const result = await client.createTrigger({
id: `${trigger.type}_${Date.now()}`,
name: trigger.name || trigger.type,
handId: trigger.handName || '',
triggerType: { type: trigger.type },
enabled: trigger.enabled,
description: trigger.config ? JSON.stringify(trigger.config) : undefined,
});
return result ? { id: result.id } : null;
} catch {
return null;
}
},
updateTrigger: async (id: string, updates) => {
const result = await client.updateTrigger(id, {
name: updates.name,
enabled: updates.enabled,
handId: updates.handName,
triggerType: updates.config ? { type: (updates.config as Record<string, unknown>).type as string } : undefined,
});
return { id: result.id };
},
deleteTrigger: async (id: string) => {
await client.deleteTrigger(id);
return { status: 'deleted' };
},
listApprovals: async () => {
try {
const result = await client.listApprovals();
// Map KernelClient approval shape to HandClient RawApproval shape
const approvals: RawApproval[] = (result?.approvals || []).map((a) => ({
id: a.id,
hand_id: a.handId,
status: a.status,
requestedAt: a.createdAt,
}));
return { approvals };
} catch {
return { approvals: [] };
}
},
respondToApproval: async (approvalId: string, approved: boolean, reason?: string) => {
await client.respondToApproval(approvalId, approved, reason);
return { status: approved ? 'approved' : 'rejected' };
},
};
}
// === Client Injection ===
/**
* Sets the client for the hand store.
* Called by the coordinator during initialization.
* Detects whether the client is a KernelClient (Tauri) or GatewayClient (browser).
*/
export function setHandStoreClient(client: unknown): void {
const handClient = createHandClientFromGateway(client as GatewayClient);
let handClient: HandClient;
// Check if it's a KernelClient (has listHands method that returns typed objects)
if (client && typeof client === 'object' && 'listHands' in client) {
handClient = createHandClientFromKernel(client as KernelClient);
} else if (client && typeof client === 'object') {
// It's a GatewayClient
handClient = createHandClientFromGateway(client as GatewayClient);
} else {
// Fallback: return a stub client that gracefully handles all calls
handClient = {
listHands: async () => null,
getHand: async () => null,
listHandRuns: async () => null,
triggerHand: async () => null,
approveHand: async () => ({ status: 'error' }),
cancelHand: async () => ({ status: 'error' }),
listTriggers: async () => ({ triggers: [] }),
getTrigger: async () => null,
createTrigger: async () => null,
updateTrigger: async () => ({ id: '' }),
deleteTrigger: async () => ({ status: 'error' }),
listApprovals: async () => ({ approvals: [] }),
respondToApproval: async () => ({ status: 'error' }),
};
}
useHandStore.getState().setHandStoreClient(handClient);
}