72a6d635da
新增独立 Tauri 壳 apps/ai-game-creator-shell,只保留聊天入口,正式用户窗口不承载游戏预览画面,开发模式通过独立窗口展示任务、文件、记忆、预览和日志 新增游戏创作专业组与种子任务图契约,覆盖策划/美术/程序/数值/音乐/运营 6 组下 15 个组内角色 新增共享契约本地项目 manifest、内置命令权限枚举、run trace schema 和 ready-task 选择器 扩展平台 LLM 支持流式请求,增加 GENARRATIVE_GAME_CREATOR_LLM_STREAM 开关 新增 AI 游戏创作 App 的聊天命令集:/help /capabilities /audit /project /llm-status /status /files /assets /read /tasks /trace /smoke /run /preview /preview-status /preview-open /preview-stop /memory /remember /forget-memory /canvas /sync-canvas-project /import-canvas-asset /import-canvas-export 新增本地项目初始化、权限 gate(pending/confirm/cancel)、文件上传、受限命令白名单 game.static_smoke 和本地 HTTP 预览 新增 Planner / 6 组角色 agent / Generator / Evaluator / ArtifactWriter / Playtest 文件驱动 loop,最多 3 轮返工,结构化 repairRoutes 新增实时 run trace 写入 .agent/run.latest.json 与 .agent/runs/,包含 taskGraph、passPlans 和步级 toolCalls 新增短期/长期记忆 memory/session.md 和 memory/project.md,支持聊天读取/追加/覆盖/删除 新增画板对接:canvas.project_open、canvas.project_sync、canvas.asset_import、canvas.export_import 新增 npm run ai-game-creator-shell:check 开发验收入口,覆盖 typecheck、单元测试、端到端 smoke 新增 check:native-shells 静态守门:release 只登记 main 聊天窗口,CSP 禁止内嵌预览,开发窗口只在 debug 下打开,用户侧预览命令必须调用 open_local_game_preview
133 lines
4.4 KiB
TypeScript
133 lines
4.4 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
|
|
|
import {
|
|
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 memory scope', () => {
|
|
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: '覆盖后的长期设定',
|
|
});
|
|
});
|
|
|
|
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');
|
|
});
|
|
|
|
it('requires an initialized local project before chat memory writes', () => {
|
|
expect(resolveChatProjectPath(null)).toBeNull();
|
|
expect(resolveChatProjectPath({ projectPath: '/tmp/game' })).toBe(
|
|
'/tmp/game',
|
|
);
|
|
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');
|
|
});
|
|
|
|
it('describes local generation side effects before confirmation', () => {
|
|
expect(
|
|
pendingCommandDetail(
|
|
{
|
|
id: 'game.generate_draft',
|
|
prompt: '做一个厨房弹幕游戏',
|
|
},
|
|
'/tmp/game',
|
|
),
|
|
).toBe(
|
|
'调用 LLM,写入 /tmp/game/game、assets、memory、exports,通过自检后启动本地 HTTP 预览并交给外部浏览器',
|
|
);
|
|
});
|
|
|
|
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('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.create')).toBe(false);
|
|
expect(needsInitializedChatProject('canvas.project_open')).toBe(false);
|
|
});
|
|
|
|
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');
|
|
});
|
|
});
|