GPU_GUARD_MONOREPO/packages/backend/test/execute_skill_process_bridge.test.ts
2026-05-20 21:39:12 +08:00

213 lines
7.8 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 { createExecuteSkillTool } from '../src/modules/netaclaw/tools/builtin/execute_skill';
describe('execute_skill process event bridge', () => {
it('bridges skill process events to tool updates', async () => {
let capturedParams: any;
const executor = {
execute: jest.fn(async (params: any) => {
capturedParams = params;
params.onProcessEvent?.({
source: 'skill',
targetType: 'skill',
stage: 'frames',
status: 'running',
message: 'frames extracted',
current: 1,
total: 2,
payload: { frameCount: 10 },
});
return { success: true, output: { ok: true }, duration: 1 };
}),
};
const updates: any[] = [];
const tool = createExecuteSkillTool(executor as any);
const result = await tool.execute(
'call-1',
{ name: 'demo', input: { taskId: 't1' } },
event => updates.push(event),
);
expect(capturedParams).toEqual(expect.objectContaining({
skillName: 'demo',
input: { taskId: 't1' },
toolCallId: 'call-1',
}));
expect(updates).toEqual([
expect.objectContaining({
source: 'skill',
targetType: 'skill',
stage: 'frames',
status: 'running',
message: 'frames extracted',
current: 1,
total: 2,
payload: {
frameCount: 10,
skillName: 'demo',
},
}),
]);
expect(result).toEqual({
type: 'text',
text: JSON.stringify({ ok: true }, null, 2),
});
});
it('does not fail execute_skill when process update observer throws', async () => {
const executor = {
execute: jest.fn(async (params: any) => {
params.onProcessEvent?.({
source: 'skill',
targetType: 'skill',
status: 'running',
message: 'still observational',
});
return { success: true, output: { ok: true }, duration: 1 };
}),
};
const tool = createExecuteSkillTool(executor as any);
const result = await tool.execute(
'call-1',
{ name: 'demo', input: {} },
() => {
throw new Error('observer failed');
},
);
expect(result).toEqual({
type: 'text',
text: JSON.stringify({ ok: true }, null, 2),
});
});
it('returns structured images when skill output includes bestFrameImages', async () => {
const output = {
success: true,
taskId: 'task_1',
summary: { mergedDamageCount: 1, bestFrameCount: 1, candidateDamageCount: 2, reviewImageCount: 1 },
vehicleInfo: { frontPlate: '粤B12345' },
candidates: [
{ id: 'cand_001', part: '前保险杠', type: '划痕' },
],
damages: [
{ id: 'damage_001', part: '前保险杠', type: '划痕', severity: '轻微' },
],
uncertainDamages: [
{ id: 'damage_002', part: '右前门', type: '凹陷', severity: '轻微' },
],
bestFrameImages: [
{
url: '/workspace/vehicle-damage-inspection/task_1/source/best_01_damage_001_2_00s.jpg',
mimeType: 'image/jpeg',
label: '前保险杠划痕 2s',
},
],
reviewImages: [
{
url: '/workspace/vehicle-damage-inspection/task_1/source/review_zoom_damage_001_2_00s.jpg',
mimeType: 'image/jpeg',
label: '模型不确定,放大后复核:前保险杠划痕',
purpose: '模型不确定,放大后复核',
reason: '放大后仍不够清晰,建议人工复核',
},
],
};
const executor = {
execute: jest.fn(async () => ({ success: true, output, duration: 1 })),
};
const tool = createExecuteSkillTool(executor as any);
const result = await tool.execute('call-1', { name: 'vehicle-damage-inspection', input: {} });
expect(result).toEqual({
type: 'images',
text: [
'Skill vehicle-damage-inspection 执行完成。',
'请只依据 finalResult.damages、finalResult.uncertainDamages、finalResult.bestFrameImages、finalResult.reviewImages 和 finalResult.summary 回答用户。',
'damages 是已确认旧伤uncertainDamages 是放大后仍无法明确确认的疑似旧伤,需要提示人工复核,不能说成已确认旧伤。',
'reviewImages 是模型不清楚时生成的放大复核图;如存在,请说明“模型不清楚,已放大后判断”,并把图片返回给前端展示。',
'candidates 是已复核前的中间候选,不代表最终旧伤,不要把 candidates 当成检测结论。',
JSON.stringify({
success: true,
taskId: 'task_1',
finalResult: {
summary: { mergedDamageCount: 1, bestFrameCount: 1, candidateDamageCount: 2, reviewImageCount: 1 },
vehicleInfo: { frontPlate: '粤B12345' },
damages: [
{ id: 'damage_001', part: '前保险杠', type: '划痕', severity: '轻微' },
],
uncertainDamages: [
{ id: 'damage_002', part: '右前门', type: '凹陷', severity: '轻微' },
],
bestFrameImages: output.bestFrameImages,
reviewImages: output.reviewImages,
},
artifacts: undefined,
}, null, 2),
].join('\n'),
images: [
{
url: '/workspace/vehicle-damage-inspection/task_1/source/best_01_damage_001_2_00s.jpg',
mimeType: 'image/jpeg',
label: '前保险杠划痕 2s',
},
{
url: '/workspace/vehicle-damage-inspection/task_1/source/review_zoom_damage_001_2_00s.jpg',
mimeType: 'image/jpeg',
label: '模型不确定,放大后复核:前保险杠划痕',
purpose: '模型不确定,放大后复核',
reason: '放大后仍不够清晰,建议人工复核',
},
],
});
});
it('returns final-only text when reviewed skill output has rejected all candidates', async () => {
const output = {
success: true,
taskId: 'task_empty',
summary: { candidateDamageCount: 1, mergedDamageCount: 0, bestFrameCount: 0, reviewImageCount: 0 },
candidates: [
{ id: 'cand_001', part: '右前翼子板', type: '凹陷' },
],
damages: [],
uncertainDamages: [],
bestFrameImages: [],
reviewImages: [],
};
const executor = {
execute: jest.fn(async () => ({ success: true, output, duration: 1 })),
};
const tool = createExecuteSkillTool(executor as any);
const result = await tool.execute('call-1', { name: 'vehicle-damage-inspection', input: {} });
expect(result).toEqual({
type: 'text',
text: [
'Skill vehicle-damage-inspection 执行完成。',
'请只依据 finalResult.damages、finalResult.uncertainDamages、finalResult.bestFrameImages、finalResult.reviewImages 和 finalResult.summary 回答用户。',
'damages 是已确认旧伤uncertainDamages 是放大后仍无法明确确认的疑似旧伤,需要提示人工复核,不能说成已确认旧伤。',
'reviewImages 是模型不清楚时生成的放大复核图;如存在,请说明“模型不清楚,已放大后判断”,并把图片返回给前端展示。',
'candidates 是已复核前的中间候选,不代表最终旧伤,不要把 candidates 当成检测结论。',
JSON.stringify({
success: true,
taskId: 'task_empty',
finalResult: {
summary: { candidateDamageCount: 1, mergedDamageCount: 0, bestFrameCount: 0, reviewImageCount: 0 },
vehicleInfo: undefined,
damages: [],
uncertainDamages: [],
bestFrameImages: [],
reviewImages: [],
},
artifacts: undefined,
}, null, 2),
].join('\n'),
});
expect((result as any).text).not.toContain('右前翼子板');
});
});