Files
Genarrative/apps/ai-game-creator-shell/tests/agentRunTrace.test.ts
T
k88936 7b1f7bdf17 给 parseAgentRunTrace 与 formatAgentRunStatus 补上纯函数用例
- 新增 tests/agentRunTrace.test.ts,用旧 trace 夹具断言归一化结果(路径/工具数组/上限/任务图/通行计划)
- 覆盖 toolCalls 总数回填、taskGraph 缺失时回退种子任务图
- 覆盖畸形 trace 的拒绝路径(steps / schemaVersion / artifacts / taskGraph / dependencyWaves)
- 覆盖 formatAgentRunStatus 带与不带 lifecycleStatus 的两种文案
2026-09-21 11:36:02 +08:00

156 lines
5.1 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, expect, it } from 'vitest';
import {
createGameCreationAppSeedTasks,
GAME_CREATION_AGENT_RUN_MAX_PASSES,
GAME_CREATION_AGENT_RUN_SCHEMA_VERSION,
GAME_CREATION_AGENT_TOOL_CALL_MAX,
} from '../../../packages/shared/src/contracts/gameCreationApp';
import { formatAgentRunStatus } from '../src/features/project-summary/agentTrace';
import { parseAgentRunTrace } from '../src/features/project-workspace/agentRunTrace';
const TRACE_FORMAT_ERROR = 'Agent run trace 格式不正确';
/**
* 旧 trace`876529e66` 之前的 App 命令聊天会在面板里渲染它):步骤没有路径与工具数组、
* 缺 `artifacts` / `taskGraph` 以外的归一化字段。这里保留同一份夹具,只是改成直接断言
* 归一化函数的输出,不再渲染整个 App。
*/
function legacyTraceFixture(): Record<string, unknown> {
return {
schemaVersion: GAME_CREATION_AGENT_RUN_SCHEMA_VERSION,
runId: 'run-legacy-trace',
commandId: 'game.generate_draft',
status: 'running',
passes: 1,
stopReason: 'planning',
goal: '做一个厨房弹幕游戏',
coordination: 'legacy',
steps: [legacyStep()],
taskGraph: {
goal: '做一个厨房弹幕游戏',
activeTaskIds: ['code-prototype'],
tasks: createGameCreationAppSeedTasks(),
},
passPlans: [
{
pass: 1,
mode: 'repair',
summary: '旧 pass plan',
activeTaskIds: ['code-prototype'],
},
],
nextStep: 'continue',
error: null,
updatedAt: 1,
};
}
function legacyStep(overrides: Record<string, unknown> = {}) {
return {
pass: 1,
agent: 'Generator',
phase: 'generate',
taskId: 'code-prototype',
group: 'code',
role: 'Code',
status: 'completed',
summary: '旧 trace 没有路径和工具数组',
...overrides,
};
}
function traceJson(overrides: Record<string, unknown> = {}) {
return JSON.stringify({ ...legacyTraceFixture(), ...overrides });
}
describe('Agent run trace 归一化与展示', () => {
it('normalizes a legacy trace before it reaches the renderers', () => {
const trace = parseAgentRunTrace(traceJson());
expect(trace.steps[0]!.inputPaths).toEqual([]);
expect(trace.steps[0]!.outputPaths).toEqual([]);
expect(trace.steps[0]!.toolCalls).toEqual([]);
expect(trace.artifacts).toEqual([]);
expect(trace.maxPasses).toBe(GAME_CREATION_AGENT_RUN_MAX_PASSES);
expect(trace.maxToolCalls).toBe(GAME_CREATION_AGENT_TOOL_CALL_MAX);
expect(trace.toolCallCount).toBe(0);
expect(trace.taskGraph.readyTaskIds).toEqual([]);
expect(trace.taskGraph.carriedTaskIds).toEqual([]);
expect(trace.taskGraph.repairFocus).toEqual([]);
expect(trace.taskGraph.repairRoutes).toEqual([]);
expect(trace.passPlans[0]!.carriedTaskIds).toEqual([]);
expect(trace.passPlans[0]!.dependencyWaves).toEqual([]);
expect(trace.passPlans[0]!.repairFocus).toEqual([]);
expect(trace.passPlans[0]!.repairRoutes).toEqual([]);
});
it('counts tool calls from the steps when the legacy trace omits the total', () => {
const trace = parseAgentRunTrace(
traceJson({
steps: [
legacyStep({ toolCalls: [{ toolId: 'file.read' }] }),
legacyStep({
taskId: 'quality-review',
toolCalls: [
{ toolId: 'game.static_smoke' },
{ toolId: 'game.run_local' },
],
}),
],
}),
);
expect(trace.toolCallCount).toBe(3);
});
it('falls back to the seed task graph when the trace predates task graphs', () => {
const fixture = legacyTraceFixture();
delete fixture.taskGraph;
const trace = parseAgentRunTrace(JSON.stringify(fixture));
expect(trace.taskGraph.goal).toBe('做一个厨房弹幕游戏');
expect(trace.taskGraph.tasks.length).toBe(
createGameCreationAppSeedTasks().length,
);
expect(trace.taskGraph.activeTaskIds).toEqual([]);
});
it('rejects malformed traces instead of rendering a half-parsed panel', () => {
expect(() => parseAgentRunTrace(traceJson({ steps: null }))).toThrow(
TRACE_FORMAT_ERROR,
);
expect(() =>
parseAgentRunTrace(traceJson({ schemaVersion: 'legacy' })),
).toThrow(TRACE_FORMAT_ERROR);
expect(() => parseAgentRunTrace(traceJson({ artifacts: {} }))).toThrow(
TRACE_FORMAT_ERROR,
);
expect(() => parseAgentRunTrace(traceJson({ taskGraph: [] }))).toThrow(
TRACE_FORMAT_ERROR,
);
expect(() =>
parseAgentRunTrace(
traceJson({
passPlans: [
{ pass: 1, mode: 'repair', dependencyWaves: [['a'], 'b'] },
],
}),
),
).toThrow(TRACE_FORMAT_ERROR);
});
it('formats the run status with and without a lifecycle status', () => {
const running = parseAgentRunTrace(traceJson());
expect(formatAgentRunStatus(running)).toBe('running · 1/3 轮 · planning');
const passed = parseAgentRunTrace(
traceJson({ status: 'passed', passes: 2, lifecycleStatus: 'completed' }),
);
expect(formatAgentRunStatus(passed)).toBe(
'passed / completed · 2/3 轮 · planning',
);
});
});