Files
kdletters 676c4beb60
Project CI / Backend tests (push) Successful in 5m35s
Project CI / Native shell tests (push) Failing after 11m9s
Project CI / Repository checks (push) Successful in 2m23s
Project CI / Frontend tests (push) Successful in 2m16s
补齐Gitea测试门禁
拆分前端、后端与原生壳测试任务
接入server-rs workspace正式全量测试
将AI游戏创作分支及独立依赖纳入CI
修复既有测试断言、格式与异步稳定性问题
稳定AI Tauri并发回执测试
更新开发运维和共享流程文档
2026-07-21 19:40:38 +08:00

320 lines
10 KiB
TypeScript

import { describe, expect, it } from 'vitest';
import {
createGameCreationAppManifest,
GAME_CREATION_AGENT_RUN_SCHEMA_VERSION,
type GameCreationAgentRunTrace,
} from '../../../packages/shared/src/contracts/gameCreationApp';
import {
deriveAgentStatusCards,
isAbsoluteProjectPath,
needsInitializedChatProject,
parseRememberInput,
pendingCommandDetail,
resolveChatProjectPath,
resolvePendingCommandProjectPath,
} from '../src/App';
describe('AI 游戏创作聊天记忆命令', () => {
it('recognizes local project absolute paths across desktop platforms', () => {
expect(isAbsoluteProjectPath('/tmp/game')).toBe(true);
expect(isAbsoluteProjectPath('C:\\Games\\demo')).toBe(true);
expect(isAbsoluteProjectPath('D:/Games/demo')).toBe(true);
expect(isAbsoluteProjectPath('relative-game')).toBe(false);
});
it('defaults /remember to long memory and supports short and blackboard scopes', () => {
expect(parseRememberInput('主角喜欢反弹弹幕')).toEqual({
scope: 'long',
content: '主角喜欢反弹弹幕',
});
expect(parseRememberInput('short 本轮先修输入手感')).toEqual({
scope: 'short',
content: '本轮先修输入手感',
});
expect(parseRememberInput('长期 保留厨房主题')).toEqual({
scope: 'long',
content: '保留厨房主题',
});
expect(parseRememberInput('project 覆盖后的长期设定')).toEqual({
scope: 'long',
content: '覆盖后的长期设定',
});
expect(parseRememberInput('blackboard 共享美术约束')).toEqual({
scope: 'blackboard',
content: '共享美术约束',
});
expect(parseRememberInput('黑板 统一使用俯视角')).toEqual({
scope: 'blackboard',
content: '统一使用俯视角',
});
});
it('describes append and replace memory writes before confirmation', () => {
expect(
pendingCommandDetail(
{
id: 'memory.write',
scope: 'long',
content: '保留厨房主题',
mode: 'append',
},
'/tmp/game',
),
).toBe('追加到 /tmp/game/memory/project.md');
expect(
pendingCommandDetail(
{
id: 'memory.write',
scope: 'short',
content: '覆盖本轮上下文',
mode: 'replace',
},
'/tmp/game',
),
).toBe('覆盖保存到 /tmp/game/memory/session.md');
expect(
pendingCommandDetail(
{
id: 'memory.write',
scope: 'blackboard',
content: '共享美术约束',
mode: 'append',
},
'/tmp/game',
),
).toBe('追加到 /tmp/game/memory/blackboard.md');
});
it('requires an initialized local project before chat memory writes', () => {
expect(resolveChatProjectPath(null)).toBeNull();
expect(resolveChatProjectPath({ projectPath: '/tmp/game' })).toBe(
'/tmp/game',
);
expect(resolveChatProjectPath({ projectPath: 'relative-game' })).toBeNull();
expect(
resolveChatProjectPath({ projectPath: '/tmp/bad\u0007game' }),
).toBeNull();
expect(parseRememberInput('long')).toEqual({
scope: 'long',
content: '',
});
expect(parseRememberInput('short 覆盖本轮上下文')).toEqual({
scope: 'short',
content: '覆盖本轮上下文',
});
expect(
pendingCommandDetail(
{
id: 'memory.delete',
scope: 'short',
},
'/tmp/game',
),
).toBe('删除 /tmp/game/memory/session.md');
expect(
pendingCommandDetail(
{
id: 'memory.delete',
scope: 'blackboard',
},
'/tmp/game',
),
).toBe('删除 /tmp/game/memory/blackboard.md');
});
it('describes local generation side effects before confirmation', () => {
expect(
pendingCommandDetail(
{
id: 'game.generate_draft',
prompt: '做一个厨房弹幕游戏',
},
'/tmp/game',
),
).toBe(
'调用 LLM Planner / Generator,编排 6 组角色 brief,写入 /tmp/game/game、assets、memory、exports,通过 Evaluator 和自检后启动本地 HTTP 预览并载入客户端运行视图',
);
});
it('describes preview side effects before confirmation', () => {
expect(pendingCommandDetail({ id: 'preview.start' }, '/tmp/game')).toBe(
'启动 /tmp/game/game/ 并载入客户端运行视图',
);
expect(pendingCommandDetail({ id: 'preview.open' }, '/tmp/game')).toBe(
'打开 /tmp/game 的当前本地预览',
);
});
it('requires a project before chat commands write or run local artifacts', () => {
expect(needsInitializedChatProject('game.generate_draft')).toBe(true);
expect(needsInitializedChatProject('asset.upload')).toBe(true);
expect(needsInitializedChatProject('agent.kill')).toBe(true);
expect(needsInitializedChatProject('agent.retry')).toBe(true);
expect(needsInitializedChatProject('agent.resume')).toBe(true);
expect(needsInitializedChatProject('command.run_limited')).toBe(true);
expect(needsInitializedChatProject('preview.open')).toBe(true);
expect(needsInitializedChatProject('preview.start')).toBe(true);
expect(needsInitializedChatProject('canvas.project_sync')).toBe(true);
expect(needsInitializedChatProject('canvas.export_import')).toBe(true);
expect(needsInitializedChatProject('memory.write')).toBe(true);
expect(needsInitializedChatProject('memory.delete')).toBe(true);
expect(needsInitializedChatProject('project.checkpoint')).toBe(true);
expect(needsInitializedChatProject('project.restore')).toBe(true);
expect(needsInitializedChatProject('project.policy_write')).toBe(true);
expect(needsInitializedChatProject('project.create')).toBe(false);
expect(needsInitializedChatProject('canvas.project_open')).toBe(false);
});
it('describes project safety commands before confirmation', () => {
expect(
pendingCommandDetail({ id: 'project.checkpoint' }, '/tmp/game'),
).toBe('保存 /tmp/game 当前项目快照');
expect(
pendingCommandDetail(
{ id: 'project.restore', checkpointId: 'checkpoint-1' },
'/tmp/game',
),
).toBe('从 checkpoint-1 恢复 /tmp/game 的已跟踪项目文件');
expect(
pendingCommandDetail(
{
id: 'project.policy_write',
policy: {
deniedCommands: ['file.write'],
confirmCommands: [],
},
},
'/tmp/game',
),
).toBe(
'写入 /tmp/game/.agent/policy.json · 拒绝:file.write · 确认:无 · Agent:无 Agent 独立策略',
);
});
it('describes canvas asset imports before confirmation', () => {
expect(
pendingCommandDetail(
{
id: 'canvas.asset_import',
localPath: 'assets/hero.png',
canvasProjectId: 'canvas-project-1',
canvasAssetId: '',
canvasAssetObjectId: 'asset-object-1',
kind: 'character',
mediaType: 'image/png',
},
'/tmp/game',
),
).toBe(
'导入 /tmp/game/assets/hero.png · 画板 canvas-project-1 / object:asset-object-1 · character · image/png',
);
expect(
pendingCommandDetail(
{
id: 'canvas.export_import',
exportPath: '/tmp/canvas-export.zip',
canvasProjectId: 'canvas-project-1',
},
'/tmp/game',
),
).toBe(
'导入 /tmp/canvas-export.zip 到 /tmp/game/assets/canvas-imports/ · 画板 canvas-project-1',
);
});
it('describes agent run lifecycle controls before confirmation', () => {
expect(pendingCommandDetail({ id: 'agent.kill' }, '/tmp/game')).toBe(
'标记 /tmp/game/.agent/run.latest.json 为 killed,并写入 activity/output',
);
expect(pendingCommandDetail({ id: 'agent.retry' }, '/tmp/game')).toBe(
'使用 /tmp/game/.agent/run.latest.json 的目标重新运行一次',
);
expect(
pendingCommandDetail(
{ id: 'agent.resume', detail: '继续修复输入监听' },
'/tmp/game',
),
).toBe(
'附加说明「继续修复输入监听」,继续运行 /tmp/game/.agent/run.latest.json 的目标',
);
});
it('shows the authorized project path in pending write command details', () => {
expect(
resolvePendingCommandProjectPath(
{ id: 'game.generate_draft', prompt: '做一个厨房弹幕游戏' },
{ projectPath: '/authorized/game' },
'/draft/dev-input',
),
).toBe('/authorized/game');
expect(
resolvePendingCommandProjectPath(
{ id: 'project.create', projectPath: '/new/game' },
{ projectPath: '/authorized/game' },
'/draft/dev-input',
),
).toBe('/new/game');
});
it('derives per-agent status cards from manifest and latest trace steps', () => {
const manifest = createGameCreationAppManifest(
'local-project-draft',
'未命名游戏原型',
);
const trace = {
schemaVersion: GAME_CREATION_AGENT_RUN_SCHEMA_VERSION,
runId: 'run-agent-status',
commandId: 'game.generate_draft',
status: 'passed',
passes: 1,
maxPasses: 3,
toolCallCount: 1,
maxToolCalls: 128,
stopReason: 'evaluator-passed',
goal: '像素动作',
coordination: 'filesystem',
steps: [
{
pass: 1,
agent: 'Planner',
phase: 'plan',
taskId: 'design-director',
group: 'design',
role: 'Director',
status: 'completed',
inputPaths: [],
outputPaths: ['.agent/spec.md'],
summary: '拆解完成',
toolCalls: [],
},
],
artifacts: [],
taskGraph: {
goal: '像素动作',
readyTaskIds: [],
activeTaskIds: [],
carriedTaskIds: [],
repairFocus: [],
repairRoutes: [],
tasks: manifest.tasks,
},
passPlans: [],
nextStep: 'manual-playtest',
error: null,
updatedAt: 1,
} satisfies GameCreationAgentRunTrace;
const traced = deriveAgentStatusCards(manifest, trace);
expect(traced[0]).toMatchObject({
id: 'design-director',
title: '拆解创作方向',
status: 'completed',
summary: '拆解完成',
});
const fallback = deriveAgentStatusCards(manifest, null);
expect(fallback[0]?.summary).toBe('创作目标、范围和专业组分工明确');
});
});