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

@@ -1,5 +1,7 @@
import { create } from 'zustand';
import { invoke } from '@tauri-apps/api/core';
import type { GatewayClient } from '../lib/gateway-client';
import type { KernelClient } from '../lib/kernel-client';
// === Core Types (previously imported from gatewayStore) ===
@@ -327,11 +329,168 @@ function createWorkflowClientFromGateway(client: GatewayClient): WorkflowClient
};
}
// === Pipeline types (from Tauri backend) ===
interface PipelineInfo {
id: string;
displayName: string;
description: string;
category: string;
industry: string;
tags: string[];
icon: string;
version: string;
author: string;
inputs: Array<{
name: string;
inputType: string;
required: boolean;
label: string;
placeholder?: string;
default?: unknown;
options: string[];
}>;
}
interface RunPipelineResponse {
runId: string;
pipelineId: string;
status: string;
}
interface PipelineRunResponse {
runId: string;
pipelineId: string;
status: string;
currentStep?: string;
percentage: number;
message: string;
outputs?: unknown;
error?: string;
startedAt: string;
endedAt?: string;
}
/**
* Helper to create a WorkflowClient adapter from a KernelClient.
* Uses direct Tauri invoke() calls to pipeline_commands since KernelClient
* does not have workflow methods (workflows in Tauri mode are pipelines).
*/
function createWorkflowClientFromKernel(_client: KernelClient): WorkflowClient {
return {
listWorkflows: async () => {
try {
const pipelines = await invoke<PipelineInfo[]>('pipeline_list', {});
if (!pipelines) return null;
return {
workflows: pipelines.map((p) => ({
id: p.id,
name: p.displayName || p.id,
steps: p.inputs.length,
description: p.description,
createdAt: undefined,
})),
};
} catch {
return null;
}
},
getWorkflow: async (id: string) => {
try {
const pipeline = await invoke<PipelineInfo>('pipeline_get', { pipelineId: id });
return {
id: pipeline.id,
name: pipeline.displayName || pipeline.id,
description: pipeline.description,
steps: pipeline.inputs.map((input) => ({
handName: input.inputType,
name: input.label,
params: input.default ? { default: input.default } : undefined,
})),
createdAt: undefined,
} satisfies WorkflowDetail;
} catch {
return null;
}
},
createWorkflow: async () => {
throw new Error('Workflow creation not supported in KernelClient mode. Pipelines are file-based YAML definitions.');
},
updateWorkflow: async () => {
throw new Error('Workflow update not supported in KernelClient mode. Pipelines are file-based YAML definitions.');
},
deleteWorkflow: async () => {
throw new Error('Workflow deletion not supported in KernelClient mode. Pipelines are file-based YAML definitions.');
},
executeWorkflow: async (id: string, input?: Record<string, unknown>) => {
try {
const result = await invoke<RunPipelineResponse>('pipeline_run', {
request: { pipelineId: id, inputs: input || {} },
});
return { runId: result.runId, status: result.status };
} catch {
return null;
}
},
cancelWorkflow: async (_workflowId: string, runId: string) => {
try {
await invoke('pipeline_cancel', { runId });
return { status: 'cancelled' };
} catch {
return { status: 'error' };
}
},
listWorkflowRuns: async (workflowId: string) => {
try {
const runs = await invoke<PipelineRunResponse[]>('pipeline_runs', {});
// Filter runs by pipeline ID and map to RawWorkflowRun shape
const filteredRuns: RawWorkflowRun[] = runs
.filter((r) => r.pipelineId === workflowId)
.map((r) => ({
run_id: r.runId,
workflow_id: r.pipelineId,
status: r.status,
started_at: r.startedAt,
completed_at: r.endedAt,
current_step: r.currentStep ? Math.round(r.percentage) : undefined,
error: r.error,
result: r.outputs,
}));
return { runs: filteredRuns };
} catch {
return { runs: [] };
}
},
};
}
/**
* Sets the client for the workflow store.
* Called by the coordinator during initialization.
* Detects whether the client is a KernelClient (Tauri) or GatewayClient (browser).
*/
export function setWorkflowStoreClient(client: unknown): void {
const workflowClient = createWorkflowClientFromGateway(client as GatewayClient);
let workflowClient: WorkflowClient;
// Check if it's a KernelClient (has listHands method, which KernelClient has but GatewayClient doesn't)
if (client && typeof client === 'object' && 'listHands' in client) {
workflowClient = createWorkflowClientFromKernel(client as KernelClient);
} else if (client && typeof client === 'object') {
// It's a GatewayClient
workflowClient = createWorkflowClientFromGateway(client as GatewayClient);
} else {
// Fallback: return a stub client that gracefully handles all calls
workflowClient = {
listWorkflows: async () => null,
getWorkflow: async () => null,
createWorkflow: async () => null,
updateWorkflow: async () => null,
deleteWorkflow: async () => ({ status: 'error' }),
executeWorkflow: async () => null,
cancelWorkflow: async () => ({ status: 'error' }),
listWorkflowRuns: async () => null,
};
}
useWorkflowStore.getState().setWorkflowStoreClient(workflowClient);
}