Files
ai-terminal-assistant/tests/unit/tools/task/agent_output.test.ts
T
kurihada ad5d30b262 feat: 添加后台 Agent 执行和模型选择功能
- 新增 AgentManager 管理后台 Agent 生命周期
- Task 工具支持 run_in_background 参数实现后台执行
- Task 工具支持 model 参数选择 sonnet/opus/haiku 模型
- 新增 agent_output 工具查询后台 Agent 执行状态和结果
- 添加 AgentManager 和 AgentOutput 工具单元测试
2025-12-11 15:46:30 +08:00

161 lines
4.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { describe, it, expect, beforeEach, vi } from 'vitest';
import { agentOutputTool } from '../../../../src/tools/task/agent_output.js';
import { getAgentManager, resetAgentManager } from '../../../../src/agent/manager.js';
// Mock AgentExecutor - 使用延迟执行让测试更可控
vi.mock('../../../../src/agent/executor.js', () => ({
AgentExecutor: vi.fn().mockImplementation(() => ({
execute: vi.fn().mockImplementation(async () => {
await new Promise((r) => setTimeout(r, 50));
return {
success: true,
text: '任务完成结果',
steps: 3,
sessionId: 'test-session',
};
}),
})),
}));
describe('agentOutputTool - Agent 输出工具', () => {
beforeEach(() => {
vi.clearAllMocks();
resetAgentManager();
});
describe('工具定义', () => {
it('有正确的名称', () => {
expect(agentOutputTool.name).toBe('agent_output');
});
it('有正确的元数据', () => {
expect(agentOutputTool.metadata.category).toBe('agent');
expect(agentOutputTool.metadata.keywords).toContain('agent');
expect(agentOutputTool.metadata.keywords).toContain('output');
expect(agentOutputTool.metadata.keywords).toContain('background');
});
it('agent_id 参数是必须的', () => {
expect(agentOutputTool.parameters.agent_id.required).toBe(true);
});
it('block 和 timeout 参数是可选的', () => {
expect(agentOutputTool.parameters.block.required).toBe(false);
expect(agentOutputTool.parameters.timeout.required).toBe(false);
});
});
describe('execute - 执行', () => {
it('Agent 不存在时返回错误', async () => {
const result = await agentOutputTool.execute({
agent_id: 'non-existent-id',
});
expect(result.success).toBe(false);
expect(result.error).toContain('不存在');
});
it('返回 Agent 的状态', async () => {
const manager = getAgentManager();
const agentId = await manager.runInBackground(
{
name: 'test-agent',
description: '测试',
mode: 'subagent',
},
'测试任务',
'执行测试',
{
provider: 'anthropic',
apiKey: 'test',
model: 'test',
maxTokens: 1000,
systemPrompt: 'test',
},
{} as any,
{ parentSessionId: 'parent', workdir: '/test' }
);
// 立即查询(不阻塞)
const result = await agentOutputTool.execute({
agent_id: agentId,
block: false,
});
// 应该成功返回状态(可能是 running 或 completed
expect(result.output).toBeDefined();
expect(result.metadata?.agentId).toBe(agentId);
});
it('阻塞等待后返回结果', async () => {
const manager = getAgentManager();
const agentId = await manager.runInBackground(
{
name: 'test-agent',
description: '测试',
mode: 'subagent',
},
'测试任务',
'执行测试',
{
provider: 'anthropic',
apiKey: 'test',
model: 'test',
maxTokens: 1000,
systemPrompt: 'test',
},
{} as any,
{ parentSessionId: 'parent', workdir: '/test' }
);
// 使用阻塞模式等待
const result = await agentOutputTool.execute({
agent_id: agentId,
block: true,
timeout: 5,
});
// 等待后应该有确定的状态
expect(result.metadata?.status).toBeDefined();
expect(['running', 'completed', 'failed']).toContain(result.metadata?.status);
});
it('返回包含正确字段的输出', async () => {
const manager = getAgentManager();
const agentId = await manager.runInBackground(
{
name: 'test-agent',
description: '测试',
mode: 'subagent',
},
'测试任务',
'执行测试',
{
provider: 'anthropic',
apiKey: 'test',
model: 'test',
maxTokens: 1000,
systemPrompt: 'test',
},
{} as any,
{ parentSessionId: 'parent', workdir: '/test' }
);
// 等待完成
await new Promise((r) => setTimeout(r, 200));
const result = await agentOutputTool.execute({
agent_id: agentId,
block: false,
});
// 检查返回了有效结果
expect(result.output).toBeDefined();
expect(result.metadata?.agentId).toBe(agentId);
expect(result.metadata?.agentName).toBe('test-agent');
// 状态应该是完成或失败(由于 mock,可能会失败)
expect(['completed', 'failed']).toContain(result.metadata?.status);
});
});
});