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
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:
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user