Major changes: - Shift from "OpenFang desktop client" to "independent AI Agent desktop app" - Add decision principle: "Is this useful for ZCLAW? Does it affect ZCLAW?" - Simplify project structure and tech stack sections - Replace OpenClaw vs OpenFang comparison with unified backend approach - Consolidate troubleshooting from scattered sections into organized FAQ - Update Hands system documentation with 8 capabilities and status - Stream
460 lines
9.7 KiB
TypeScript
460 lines
9.7 KiB
TypeScript
/**
|
|
* 测试数据工厂
|
|
* 生成一致的测试数据,确保测试可重复性
|
|
*/
|
|
|
|
/**
|
|
* 生成唯一 ID
|
|
*/
|
|
export function generateId(prefix = 'id'): string {
|
|
return `${prefix}-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
}
|
|
|
|
/**
|
|
* 生成时间戳
|
|
*/
|
|
export function generateTimestamp(): string {
|
|
return new Date().toISOString();
|
|
}
|
|
|
|
/**
|
|
* 测试消息工厂
|
|
*/
|
|
export const messageFactory = {
|
|
/**
|
|
* 创建用户消息
|
|
*/
|
|
createUser(content: string, options?: { id?: string; timestamp?: string }) {
|
|
return {
|
|
id: options?.id ?? generateId('msg'),
|
|
role: 'user' as const,
|
|
content,
|
|
timestamp: options?.timestamp ?? generateTimestamp(),
|
|
};
|
|
},
|
|
|
|
/**
|
|
* 创建助手消息
|
|
*/
|
|
createAssistant(content: string, options?: {
|
|
id?: string;
|
|
timestamp?: string;
|
|
streaming?: boolean;
|
|
model?: string;
|
|
}) {
|
|
return {
|
|
id: options?.id ?? generateId('msg'),
|
|
role: 'assistant' as const,
|
|
content,
|
|
timestamp: options?.timestamp ?? generateTimestamp(),
|
|
streaming: options?.streaming ?? false,
|
|
model: options?.model ?? 'claude-3-sonnet',
|
|
};
|
|
},
|
|
|
|
/**
|
|
* 创建工具消息
|
|
*/
|
|
createTool(toolName: string, input: unknown, output: unknown) {
|
|
return {
|
|
id: generateId('msg'),
|
|
role: 'tool' as const,
|
|
content: '',
|
|
timestamp: generateTimestamp(),
|
|
toolName,
|
|
toolInput: JSON.stringify(input),
|
|
toolOutput: JSON.stringify(output),
|
|
};
|
|
},
|
|
|
|
/**
|
|
* 创建 Hand 消息
|
|
*/
|
|
createHand(handName: string, status: string, result?: unknown) {
|
|
return {
|
|
id: generateId('msg'),
|
|
role: 'hand' as const,
|
|
content: '',
|
|
timestamp: generateTimestamp(),
|
|
handName,
|
|
handStatus: status,
|
|
handResult: result,
|
|
};
|
|
},
|
|
|
|
/**
|
|
* 创建 Workflow 消息
|
|
*/
|
|
createWorkflow(workflowId: string, step: string, status: string) {
|
|
return {
|
|
id: generateId('msg'),
|
|
role: 'workflow' as const,
|
|
content: '',
|
|
timestamp: generateTimestamp(),
|
|
workflowId,
|
|
workflowStep: step,
|
|
workflowStatus: status,
|
|
};
|
|
},
|
|
|
|
/**
|
|
* 创建消息列表
|
|
*/
|
|
createConversation(messages: Array<{ role: string; content: string }>) {
|
|
return messages.map((m) => {
|
|
if (m.role === 'user') {
|
|
return this.createUser(m.content);
|
|
}
|
|
return this.createAssistant(m.content);
|
|
});
|
|
},
|
|
};
|
|
|
|
/**
|
|
* 测试分身工厂
|
|
*/
|
|
export const cloneFactory = {
|
|
/**
|
|
* 创建分身
|
|
*/
|
|
create(options?: {
|
|
id?: string;
|
|
name?: string;
|
|
role?: string;
|
|
model?: string;
|
|
workspaceDir?: string;
|
|
}) {
|
|
return {
|
|
id: options?.id ?? generateId('clone'),
|
|
name: options?.name ?? `测试分身-${Date.now()}`,
|
|
role: options?.role ?? 'AI Assistant',
|
|
model: options?.model ?? 'claude-3-sonnet',
|
|
workspaceDir: options?.workspaceDir ?? '/tmp/workspace',
|
|
createdAt: generateTimestamp(),
|
|
onboardingCompleted: true,
|
|
};
|
|
},
|
|
|
|
/**
|
|
* 创建多个分身
|
|
*/
|
|
createMany(count: number) {
|
|
return Array.from({ length: count }, (_, i) =>
|
|
this.create({
|
|
name: `分身-${i + 1}`,
|
|
role: i === 0 ? 'Main Assistant' : `Specialist ${i + 1}`,
|
|
})
|
|
);
|
|
},
|
|
};
|
|
|
|
/**
|
|
* 测试 Hand 工厂
|
|
*/
|
|
export const handFactory = {
|
|
/**
|
|
* 创建 Hand
|
|
*/
|
|
create(options?: {
|
|
id?: string;
|
|
name?: string;
|
|
status?: string;
|
|
category?: string;
|
|
requirementsMet?: boolean;
|
|
}) {
|
|
return {
|
|
id: options?.id ?? generateId('hand'),
|
|
name: options?.name ?? 'TestHand',
|
|
description: `Test Hand: ${options?.name ?? 'TestHand'}`,
|
|
status: options?.status ?? 'idle',
|
|
category: options?.category ?? 'automation',
|
|
requirements_met: options?.requirementsMet ?? true,
|
|
tools: ['tool1', 'tool2'],
|
|
metrics: ['metric1'],
|
|
};
|
|
},
|
|
|
|
/**
|
|
* 创建 Browser Hand
|
|
*/
|
|
createBrowser(status = 'idle') {
|
|
return this.create({
|
|
id: 'browser',
|
|
name: 'Browser',
|
|
status,
|
|
category: 'automation',
|
|
});
|
|
},
|
|
|
|
/**
|
|
* 创建 Collector Hand
|
|
*/
|
|
createCollector(status = 'idle') {
|
|
return this.create({
|
|
id: 'collector',
|
|
name: 'Collector',
|
|
status,
|
|
category: 'data',
|
|
});
|
|
},
|
|
|
|
/**
|
|
* 创建需要审批的 Hand
|
|
*/
|
|
createNeedsApproval() {
|
|
return this.create({
|
|
status: 'needs_approval',
|
|
});
|
|
},
|
|
|
|
/**
|
|
* 创建多个 Hands
|
|
*/
|
|
createMany(count: number) {
|
|
const categories = ['automation', 'data', 'research', 'analytics'];
|
|
return Array.from({ length: count }, (_, i) =>
|
|
this.create({
|
|
id: `hand-${i + 1}`,
|
|
name: `Hand${i + 1}`,
|
|
category: categories[i % categories.length],
|
|
})
|
|
);
|
|
},
|
|
};
|
|
|
|
/**
|
|
* 测试工作流工厂
|
|
*/
|
|
export const workflowFactory = {
|
|
/**
|
|
* 创建工作流
|
|
*/
|
|
create(options?: {
|
|
id?: string;
|
|
name?: string;
|
|
steps?: number;
|
|
}) {
|
|
return {
|
|
id: options?.id ?? generateId('wf'),
|
|
name: options?.name ?? `工作流-${Date.now()}`,
|
|
description: '测试工作流',
|
|
steps: options?.steps ?? 1,
|
|
status: 'idle',
|
|
createdAt: generateTimestamp(),
|
|
};
|
|
},
|
|
|
|
/**
|
|
* 创建工作流步骤
|
|
*/
|
|
createStep(options?: {
|
|
id?: string;
|
|
handName?: string;
|
|
params?: Record<string, unknown>;
|
|
condition?: string;
|
|
}) {
|
|
return {
|
|
id: options?.id ?? generateId('step'),
|
|
handName: options?.handName ?? 'Browser',
|
|
params: options?.params ?? {},
|
|
condition: options?.condition,
|
|
};
|
|
},
|
|
|
|
/**
|
|
* 创建完整工作流(含步骤)
|
|
*/
|
|
createWithSteps(stepCount: number) {
|
|
const steps = Array.from({ length: stepCount }, (_, i) =>
|
|
this.createStep({
|
|
handName: ['Browser', 'Collector', 'Researcher'][i % 3],
|
|
})
|
|
);
|
|
|
|
return {
|
|
...this.create({ steps: stepCount }),
|
|
steps,
|
|
};
|
|
},
|
|
};
|
|
|
|
/**
|
|
* 测试技能工厂
|
|
*/
|
|
export const skillFactory = {
|
|
/**
|
|
* 创建技能
|
|
*/
|
|
create(options?: {
|
|
id?: string;
|
|
name?: string;
|
|
category?: string;
|
|
installed?: boolean;
|
|
}) {
|
|
return {
|
|
id: options?.id ?? generateId('skill'),
|
|
name: options?.name ?? `技能-${Date.now()}`,
|
|
description: '测试技能描述',
|
|
category: options?.category ?? 'development',
|
|
triggers: ['trigger1', 'trigger2'],
|
|
capabilities: ['capability1'],
|
|
installed: options?.installed ?? false,
|
|
};
|
|
},
|
|
|
|
/**
|
|
* 创建已安装的技能
|
|
*/
|
|
createInstalled(name?: string) {
|
|
return this.create({
|
|
name: name ?? '已安装技能',
|
|
installed: true,
|
|
});
|
|
},
|
|
|
|
/**
|
|
* 创建多个技能
|
|
*/
|
|
createMany(count: number) {
|
|
const categories = ['development', 'security', 'analytics', 'productivity'];
|
|
return Array.from({ length: count }, (_, i) =>
|
|
this.create({
|
|
id: `skill-${i + 1}`,
|
|
name: `技能 ${i + 1}`,
|
|
category: categories[i % categories.length],
|
|
installed: i < 2, // 前两个已安装
|
|
})
|
|
);
|
|
},
|
|
};
|
|
|
|
/**
|
|
* 测试团队工厂
|
|
*/
|
|
export const teamFactory = {
|
|
/**
|
|
* 创建团队
|
|
*/
|
|
create(options?: {
|
|
id?: string;
|
|
name?: string;
|
|
pattern?: string;
|
|
}) {
|
|
return {
|
|
id: options?.id ?? generateId('team'),
|
|
name: options?.name ?? `团队-${Date.now()}`,
|
|
description: '测试团队',
|
|
members: [],
|
|
tasks: [],
|
|
pattern: options?.pattern ?? 'sequential',
|
|
status: 'active',
|
|
createdAt: generateTimestamp(),
|
|
};
|
|
},
|
|
|
|
/**
|
|
* 创建团队成员
|
|
*/
|
|
createMember(options?: {
|
|
id?: string;
|
|
role?: string;
|
|
}) {
|
|
return {
|
|
id: options?.id ?? generateId('member'),
|
|
agentId: generateId('agent'),
|
|
role: options?.role ?? 'member',
|
|
joinedAt: generateTimestamp(),
|
|
};
|
|
},
|
|
|
|
/**
|
|
* 创建团队任务
|
|
*/
|
|
createTask(options?: {
|
|
id?: string;
|
|
status?: string;
|
|
}) {
|
|
return {
|
|
id: options?.id ?? generateId('task'),
|
|
title: '测试任务',
|
|
description: '任务描述',
|
|
status: options?.status ?? 'pending',
|
|
assigneeId: null,
|
|
createdAt: generateTimestamp(),
|
|
};
|
|
},
|
|
};
|
|
|
|
/**
|
|
* 测试审批工厂
|
|
*/
|
|
export const approvalFactory = {
|
|
/**
|
|
* 创建审批请求
|
|
*/
|
|
create(options?: {
|
|
id?: string;
|
|
handName?: string;
|
|
status?: string;
|
|
}) {
|
|
return {
|
|
id: options?.id ?? generateId('approval'),
|
|
handName: options?.handName ?? 'Browser',
|
|
reason: '需要用户批准执行',
|
|
params: {},
|
|
status: options?.status ?? 'pending',
|
|
createdAt: generateTimestamp(),
|
|
expiresAt: new Date(Date.now() + 3600000).toISOString(),
|
|
};
|
|
},
|
|
};
|
|
|
|
/**
|
|
* 预设测试场景数据
|
|
*/
|
|
export const testScenarios = {
|
|
/**
|
|
* 完整的聊天场景
|
|
*/
|
|
chatConversation: [
|
|
{ role: 'user', content: '你好' },
|
|
{ role: 'assistant', content: '你好!我是 AI 助手,有什么可以帮助你的吗?' },
|
|
{ role: 'user', content: '请写一个简单的函数' },
|
|
{ role: 'assistant', content: '好的,这是一个简单的函数:\n```python\ndef hello():\n print("Hello, World!")\n```' },
|
|
],
|
|
|
|
/**
|
|
* Hand 执行场景
|
|
*/
|
|
handExecution: {
|
|
hand: handFactory.createBrowser(),
|
|
params: { url: 'https://example.com' },
|
|
expectedStatus: ['idle', 'running', 'completed'],
|
|
},
|
|
|
|
/**
|
|
* 工作流场景
|
|
*/
|
|
workflowExecution: {
|
|
workflow: workflowFactory.createWithSteps(3),
|
|
expectedSteps: ['step-1', 'step-2', 'step-3'],
|
|
},
|
|
|
|
/**
|
|
* 审批场景
|
|
*/
|
|
approvalFlow: {
|
|
approval: approvalFactory.create({ handName: 'Browser' }),
|
|
actions: ['approve', 'reject'] as const,
|
|
},
|
|
|
|
/**
|
|
* 团队协作场景
|
|
*/
|
|
teamCollaboration: {
|
|
team: teamFactory.create({ pattern: 'review_loop' }),
|
|
members: [teamFactory.createMember({ role: 'developer' }), teamFactory.createMember({ role: 'reviewer' })],
|
|
task: teamFactory.createTask(),
|
|
},
|
|
};
|