6106b4e67c
- 删除应用侧 /history 精确匹配分支与 reloadHistory、chatPromptPolish 的 / 前缀绕过、chatCommandMetadata、memoryCommands、projectSummaryConstants 命令清单 - 删除只服务退役摘要面板的 project-summary/*Summaries.ts 与 agentTrace.ts 及对应测试 - 删除 /sync-canvas-project、/read、/trace 草稿回填死链(agentPresentation.ts 与 Rust suggested_canvas_tool_call) - 删除无人调用的 Tauri 命令 get_game_creation_agent_capabilities 与 get_limited_local_commands - 删除 --swarm-chat 入口、SwarmChat 变体、src/swarm_cli.rs 与整个 swarm_cli/ 目录 - 收敛 agent/interaction.rs 至自然语言 steer 决策路径,删除交互内核整层 - 删除 SWARM_TURN_*_ERROR、print_runtime_response_stream_status 及其专属测试 - 更新 ChatMarkdownMessage、chatPromptPolish、rememberCommand 与 appSurface 用例,移除斜杠命令断言
841 lines
31 KiB
TypeScript
841 lines
31 KiB
TypeScript
import { directCodexUserItemFromContent } from '../../src/features/project-workspace/resourceReferences';
|
|
import {
|
|
directCodexPolicyRetryInput,
|
|
isDirectCodexTurnAlreadyRunningError,
|
|
} from '../../src/view/project-development/chat/conversation/directCodexConversation';
|
|
import {
|
|
createGameCreationAppManifest,
|
|
emptyProjectPolicy,
|
|
expect,
|
|
fireEvent,
|
|
it,
|
|
mockRoleAgentReply,
|
|
renderAppAt,
|
|
roleAgentMockReply,
|
|
screen,
|
|
vi,
|
|
waitFor,
|
|
within,
|
|
} from './harness';
|
|
|
|
export function registerProjectConversationTests() {
|
|
it('filters only the stable same-turn in-progress rejection from terminal Direct Codex failures', () => {
|
|
expect(
|
|
isDirectCodexTurnAlreadyRunningError(
|
|
new Error(
|
|
'direct-codex-turn-already-running: 当前 Direct 客户端回合仍在运行',
|
|
),
|
|
),
|
|
).toBe(true);
|
|
expect(
|
|
isDirectCodexTurnAlreadyRunningError(
|
|
'codex-app-server-error:unauthorized',
|
|
),
|
|
).toBe(false);
|
|
expect(
|
|
isDirectCodexTurnAlreadyRunningError(
|
|
'direct-codex-turn-already-running 当前回合失败',
|
|
),
|
|
).toBe(false);
|
|
});
|
|
|
|
it('carries the whole direct turn input, including @ references, into the policy-confirmation retry', () => {
|
|
// 确认 `conversation.write` 之后重跑的是同一轮输入:漏掉任何一项都会让用户
|
|
// 在确认之后拿到另一轮内容。历史缺陷正是漏了 canonical user item 里的 @ 引用
|
|
// (引用被静默丢掉),所以这里把「首轮入参整体带过去」钉成硬约束。
|
|
const firstTurn = {
|
|
clientTurnId: 'direct-turn-1',
|
|
creationType: 'game' as const,
|
|
userItem: directCodexUserItemFromContent(
|
|
[
|
|
{ type: 'input_text' as const, text: '用这张图改一下' },
|
|
{
|
|
type: 'agc_resource_reference' as const,
|
|
resourceId: 'reference-hero',
|
|
},
|
|
{
|
|
type: 'agc_attachment_reference' as const,
|
|
name: '角色草图.png',
|
|
mediaType: 'image/png',
|
|
size: 128,
|
|
localPath: '',
|
|
status: 'imported' as const,
|
|
},
|
|
],
|
|
'direct-turn-1:user',
|
|
),
|
|
};
|
|
|
|
expect(directCodexPolicyRetryInput(firstTurn)).toEqual({
|
|
...firstTurn,
|
|
directPolicyChecked: true,
|
|
});
|
|
|
|
// canonical content 是这一次输入的判别项,单独再断言一遍,避免上面整体相等
|
|
// 被未来字段扩展掩盖。
|
|
expect(directCodexPolicyRetryInput(firstTurn).userItem.content).toEqual(
|
|
firstTurn.userItem.content,
|
|
);
|
|
});
|
|
|
|
it.skip('requires confirmation before reading a specific agent conversation when policy asks for it', async () => {
|
|
const manifest = createGameCreationAppManifest(
|
|
'local-project-draft',
|
|
'未命名游戏原型',
|
|
);
|
|
const invoke = vi.fn(
|
|
async (command: string, args?: Record<string, unknown>) => {
|
|
if (command === 'set_agc_plugin_project_path') return null;
|
|
if (command === 'list_agc_plugins') return [];
|
|
if (command === 'append_local_permission_log') {
|
|
return {};
|
|
}
|
|
if (command === 'init_local_game_project') {
|
|
const projectPath = String(args?.projectPath ?? '');
|
|
return {
|
|
projectPath,
|
|
manifestPath: `${projectPath}/.agent/manifest.json`,
|
|
manifest,
|
|
};
|
|
}
|
|
if (command === 'read_project_permission_policy') {
|
|
return {
|
|
path: '.agent/policy.json',
|
|
policy: {
|
|
deniedCommands: [],
|
|
confirmCommands: ['conversation.read'],
|
|
},
|
|
};
|
|
}
|
|
if (command === 'read_local_conversation') {
|
|
return {
|
|
path: args?.agentId
|
|
? '/tmp/authorized-game/.agent/conversations/agents/design-director.jsonl'
|
|
: '/tmp/authorized-game/.agent/conversations/project.jsonl',
|
|
agentId: args?.agentId ?? null,
|
|
messages: [],
|
|
};
|
|
}
|
|
if (command === 'read_local_agent_memory') {
|
|
return {
|
|
taskId: args?.taskId,
|
|
path: '/tmp/authorized-game/memory/agents/design/director.md',
|
|
content: '',
|
|
exists: false,
|
|
};
|
|
}
|
|
if (command === 'read_local_project_file') {
|
|
throw new Error(
|
|
'读取文件元数据失败:/tmp/authorized-game/.agent/run.latest.json: No such file or directory (os error 2)',
|
|
);
|
|
}
|
|
if (command === 'list_local_project_files') {
|
|
return { projectPath: String(args?.projectPath ?? ''), files: [] };
|
|
}
|
|
throw new Error(`unexpected invoke ${command}`);
|
|
},
|
|
);
|
|
window.__TAURI__ = { core: { invoke } };
|
|
renderAppAt('/?main&projectPath=%2Ftmp%2Fauthorized-game');
|
|
invoke.mockClear();
|
|
fireEvent.click(screen.getByRole('button', { name: /拆解创作方向/ }));
|
|
|
|
expect(await screen.findByText('准备读取 Agent 对话。')).not.toBeNull();
|
|
expect(screen.getByText('conversation.read')).not.toBeNull();
|
|
expect(invoke).not.toHaveBeenCalledWith('read_local_conversation', {
|
|
projectPath: '/tmp/authorized-game',
|
|
agentId: 'design-director',
|
|
});
|
|
|
|
fireEvent.click(screen.getByRole('button', { name: '确认' }));
|
|
|
|
expect(
|
|
await screen.findByText(
|
|
'已读取 0 条:/tmp/authorized-game/.agent/conversations/agents/design-director.jsonl',
|
|
),
|
|
).not.toBeNull();
|
|
expect(invoke).toHaveBeenCalledWith('read_local_conversation', {
|
|
projectPath: '/tmp/authorized-game',
|
|
agentId: 'design-director',
|
|
});
|
|
});
|
|
|
|
it.skip('leaves the agent conversation panel usable after cancelling read confirmation', async () => {
|
|
const manifest = createGameCreationAppManifest(
|
|
'local-project-draft',
|
|
'未命名游戏原型',
|
|
);
|
|
const invoke = vi.fn(
|
|
async (command: string, args?: Record<string, unknown>) => {
|
|
if (command === 'set_agc_plugin_project_path') return null;
|
|
if (command === 'list_agc_plugins') return [];
|
|
if (command === 'append_local_permission_log') {
|
|
return {};
|
|
}
|
|
if (command === 'init_local_game_project') {
|
|
const projectPath = String(args?.projectPath ?? '');
|
|
return {
|
|
projectPath,
|
|
manifestPath: `${projectPath}/.agent/manifest.json`,
|
|
manifest,
|
|
};
|
|
}
|
|
if (command === 'read_project_permission_policy') {
|
|
return {
|
|
path: '.agent/policy.json',
|
|
policy: {
|
|
deniedCommands: [],
|
|
confirmCommands: ['conversation.read'],
|
|
},
|
|
};
|
|
}
|
|
if (command === 'read_local_conversation') {
|
|
return {
|
|
path: args?.agentId
|
|
? '/tmp/authorized-game/.agent/conversations/agents/design-director.jsonl'
|
|
: '/tmp/authorized-game/.agent/conversations/project.jsonl',
|
|
agentId: args?.agentId ?? null,
|
|
messages: [],
|
|
};
|
|
}
|
|
if (command === 'read_local_project_file') {
|
|
throw new Error(
|
|
'读取文件元数据失败:/tmp/authorized-game/.agent/run.latest.json: No such file or directory (os error 2)',
|
|
);
|
|
}
|
|
if (command === 'list_local_project_files') {
|
|
return { projectPath: String(args?.projectPath ?? ''), files: [] };
|
|
}
|
|
throw new Error(`unexpected invoke ${command}`);
|
|
},
|
|
);
|
|
window.__TAURI__ = { core: { invoke } };
|
|
renderAppAt('/?main&projectPath=%2Ftmp%2Fauthorized-game');
|
|
invoke.mockClear();
|
|
fireEvent.click(screen.getByRole('button', { name: /拆解创作方向/ }));
|
|
|
|
const conversationReadCommand =
|
|
await screen.findByText('conversation.read');
|
|
fireEvent.click(
|
|
within(
|
|
conversationReadCommand.closest('.pending-command') as HTMLElement,
|
|
).getByRole('button', { name: '取消' }),
|
|
);
|
|
|
|
await waitFor(() => {
|
|
expect(screen.queryByText('conversation.read')).toBeNull();
|
|
});
|
|
expect(screen.getAllByText('已取消读取 Agent 对话').length).toBeGreaterThan(
|
|
0,
|
|
);
|
|
expect(screen.getByText('暂无对话')).not.toBeNull();
|
|
expect(invoke).not.toHaveBeenCalledWith('read_local_conversation', {
|
|
projectPath: '/tmp/authorized-game',
|
|
agentId: 'design-director',
|
|
});
|
|
});
|
|
|
|
it.skip('keeps loaded agent conversation after cancelling private memory read confirmation', async () => {
|
|
const manifest = createGameCreationAppManifest(
|
|
'local-project-draft',
|
|
'未命名游戏原型',
|
|
);
|
|
const invoke = vi.fn(
|
|
async (command: string, args?: Record<string, unknown>) => {
|
|
if (command === 'append_local_permission_log') {
|
|
return {};
|
|
}
|
|
if (command === 'init_local_game_project') {
|
|
const projectPath = String(args?.projectPath ?? '');
|
|
return {
|
|
projectPath,
|
|
manifestPath: `${projectPath}/.agent/manifest.json`,
|
|
manifest,
|
|
};
|
|
}
|
|
if (command === 'read_project_permission_policy') {
|
|
return {
|
|
path: '.agent/policy.json',
|
|
policy: {
|
|
deniedCommands: [],
|
|
confirmCommands: ['memory.read'],
|
|
},
|
|
};
|
|
}
|
|
if (command === 'read_local_conversation') {
|
|
return {
|
|
path: args?.agentId
|
|
? '/tmp/authorized-game/.agent/conversations/agents/design-director.jsonl'
|
|
: '/tmp/authorized-game/.agent/conversations/project.jsonl',
|
|
agentId: args?.agentId ?? null,
|
|
messages: args?.agentId
|
|
? [
|
|
{
|
|
schemaVersion: 'game-creator-conversation.v1',
|
|
role: 'assistant',
|
|
content: '已读到 Agent 对话',
|
|
agentId: 'design-director',
|
|
updatedAt: 1,
|
|
},
|
|
]
|
|
: [],
|
|
};
|
|
}
|
|
if (command === 'read_local_agent_memory') {
|
|
throw new Error('should wait for memory confirmation');
|
|
}
|
|
if (command === 'read_local_project_file') {
|
|
throw new Error(
|
|
'读取文件元数据失败:/tmp/authorized-game/.agent/run.latest.json: No such file or directory (os error 2)',
|
|
);
|
|
}
|
|
if (command === 'list_local_project_files') {
|
|
return { projectPath: String(args?.projectPath ?? ''), files: [] };
|
|
}
|
|
throw new Error(`unexpected invoke ${command}`);
|
|
},
|
|
);
|
|
window.__TAURI__ = { core: { invoke } };
|
|
renderAppAt('/?main&projectPath=%2Ftmp%2Fauthorized-game');
|
|
invoke.mockClear();
|
|
fireEvent.click(screen.getByRole('button', { name: /拆解创作方向/ }));
|
|
|
|
expect(await screen.findByText('已读到 Agent 对话')).not.toBeNull();
|
|
const memoryReadCommand = await screen.findByText('memory.read');
|
|
fireEvent.click(
|
|
within(
|
|
memoryReadCommand.closest('.pending-command') as HTMLElement,
|
|
).getByRole('button', { name: '取消' }),
|
|
);
|
|
|
|
await waitFor(() => {
|
|
expect(screen.queryByText('memory.read')).toBeNull();
|
|
});
|
|
expect(screen.getByText('已读到 Agent 对话')).not.toBeNull();
|
|
expect(screen.getByLabelText('Agent 私有记忆').textContent).toContain(
|
|
'已取消读取 Agent 私有记忆',
|
|
);
|
|
expect(invoke).not.toHaveBeenCalledWith('read_local_agent_memory', {
|
|
projectPath: '/tmp/authorized-game',
|
|
taskId: 'design-director',
|
|
});
|
|
});
|
|
|
|
it.skip('does not show unsaved agent messages when persistence fails', async () => {
|
|
const manifest = createGameCreationAppManifest(
|
|
'local-project-draft',
|
|
'未命名游戏原型',
|
|
);
|
|
const invoke = vi.fn(
|
|
async (command: string, args?: Record<string, unknown>) => {
|
|
if (command === 'append_local_permission_log') {
|
|
return {};
|
|
}
|
|
if (command === 'init_local_game_project') {
|
|
const projectPath = String(args?.projectPath ?? '');
|
|
return {
|
|
projectPath,
|
|
manifestPath: `${projectPath}/.agent/manifest.json`,
|
|
manifest,
|
|
};
|
|
}
|
|
if (command === 'read_project_permission_policy') {
|
|
return emptyProjectPolicy();
|
|
}
|
|
if (command === 'read_local_conversation') {
|
|
return {
|
|
path: '/tmp/authorized-game/.agent/conversations/agents/design-director.jsonl',
|
|
agentId: args?.agentId,
|
|
messages: [],
|
|
};
|
|
}
|
|
if (command === 'read_local_agent_memory') {
|
|
return {
|
|
taskId: args?.taskId,
|
|
path: '/tmp/authorized-game/memory/agents/design/director.md',
|
|
content: '',
|
|
exists: false,
|
|
};
|
|
}
|
|
if (command === 'append_local_conversation_message') {
|
|
throw new Error('保存 Agent 对话失败');
|
|
}
|
|
if (command === 'read_local_project_file') {
|
|
throw new Error(
|
|
'读取文件元数据失败:/tmp/authorized-game/.agent/run.latest.json: No such file or directory (os error 2)',
|
|
);
|
|
}
|
|
if (command === 'list_local_project_files') {
|
|
return { projectPath: String(args?.projectPath ?? ''), files: [] };
|
|
}
|
|
throw new Error(`unexpected invoke ${command}`);
|
|
},
|
|
);
|
|
window.__TAURI__ = { core: { invoke } };
|
|
renderAppAt('/?main&projectPath=%2Ftmp%2Fauthorized-game');
|
|
fireEvent.click(screen.getByRole('button', { name: /拆解创作方向/ }));
|
|
const input = await screen.findByLabelText('Agent 对话内容');
|
|
fireEvent.change(input, { target: { value: '这条不应该显示成已保存' } });
|
|
fireEvent.submit(input.closest('form') as HTMLFormElement);
|
|
|
|
expect(await screen.findByText('保存 Agent 对话失败')).not.toBeNull();
|
|
expect(screen.getByText('暂无对话')).not.toBeNull();
|
|
expect(screen.queryByText('这条不应该显示成已保存')).toBeNull();
|
|
expect(screen.getByLabelText('Agent 对话内容')).toHaveProperty(
|
|
'value',
|
|
'这条不应该显示成已保存',
|
|
);
|
|
});
|
|
|
|
it.skip('keeps the saved user message visible when the local agent receipt fails', async () => {
|
|
const manifest = createGameCreationAppManifest(
|
|
'local-project-draft',
|
|
'未命名游戏原型',
|
|
);
|
|
const agentMessages: Array<{
|
|
schemaVersion: string;
|
|
role: 'user' | 'assistant';
|
|
content: string;
|
|
agentId: string | null;
|
|
updatedAt: number;
|
|
}> = [];
|
|
const invoke = vi.fn(
|
|
async (command: string, args?: Record<string, unknown>) => {
|
|
if (command === 'append_local_permission_log') {
|
|
return {};
|
|
}
|
|
if (command === 'init_local_game_project') {
|
|
const projectPath = String(args?.projectPath ?? '');
|
|
return {
|
|
projectPath,
|
|
manifestPath: `${projectPath}/.agent/manifest.json`,
|
|
manifest,
|
|
};
|
|
}
|
|
if (command === 'read_project_permission_policy') {
|
|
return emptyProjectPolicy();
|
|
}
|
|
if (command === 'read_local_conversation') {
|
|
return {
|
|
path: '/tmp/authorized-game/.agent/conversations/agents/design-director.jsonl',
|
|
agentId: args?.agentId,
|
|
messages: agentMessages,
|
|
};
|
|
}
|
|
if (command === 'read_local_agent_memory') {
|
|
return {
|
|
taskId: args?.taskId,
|
|
path: '/tmp/authorized-game/memory/agents/design/director.md',
|
|
content: '',
|
|
exists: false,
|
|
};
|
|
}
|
|
if (command === 'chat_with_game_creator_role_agent') {
|
|
throw new Error('Agent LLM 未配置');
|
|
}
|
|
if (command === 'append_local_conversation_message') {
|
|
const message = args?.message as {
|
|
role: 'user' | 'assistant';
|
|
content: string;
|
|
agentId: string | null;
|
|
};
|
|
agentMessages.push({
|
|
schemaVersion: 'game-creator-conversation.v1',
|
|
role: message.role,
|
|
content: message.content,
|
|
agentId: message.agentId,
|
|
updatedAt: 1,
|
|
});
|
|
return {
|
|
path: '/tmp/authorized-game/.agent/conversations/agents/design-director.jsonl',
|
|
agentId: args?.agentId,
|
|
messages: agentMessages,
|
|
};
|
|
}
|
|
if (command === 'read_local_project_file') {
|
|
throw new Error(
|
|
'读取文件元数据失败:/tmp/authorized-game/.agent/run.latest.json: No such file or directory (os error 2)',
|
|
);
|
|
}
|
|
if (command === 'list_local_project_files') {
|
|
return { projectPath: String(args?.projectPath ?? ''), files: [] };
|
|
}
|
|
throw new Error(`unexpected invoke ${command}`);
|
|
},
|
|
);
|
|
window.__TAURI__ = { core: { invoke } };
|
|
renderAppAt('/?main&projectPath=%2Ftmp%2Fauthorized-game');
|
|
fireEvent.click(screen.getByRole('button', { name: /拆解创作方向/ }));
|
|
const input = await screen.findByLabelText('Agent 对话内容');
|
|
fireEvent.change(input, { target: { value: '先保留这个方向' } });
|
|
fireEvent.submit(input.closest('form') as HTMLFormElement);
|
|
|
|
expect(
|
|
(
|
|
await screen.findAllByText(
|
|
/已保存用户消息;Agent 回复失败:Agent LLM 未配置/,
|
|
)
|
|
).length,
|
|
).toBeGreaterThan(0);
|
|
expect(screen.getByText('先保留这个方向')).not.toBeNull();
|
|
expect(screen.queryByText(roleAgentMockReply)).toBeNull();
|
|
expect(screen.getByLabelText('Agent 对话内容')).toHaveProperty('value', '');
|
|
});
|
|
|
|
it.skip('shows LLM configuration gaps in the project agent conversation before sending', async () => {
|
|
const manifest = createGameCreationAppManifest(
|
|
'local-project-draft',
|
|
'未命名游戏原型',
|
|
);
|
|
const invoke = vi.fn(
|
|
async (command: string, args?: Record<string, unknown>) => {
|
|
if (command === 'init_local_game_project') {
|
|
const projectPath = String(args?.projectPath ?? '');
|
|
return {
|
|
projectPath,
|
|
manifestPath: `${projectPath}/.agent/manifest.json`,
|
|
manifest,
|
|
};
|
|
}
|
|
if (command === 'append_local_permission_log') {
|
|
return {};
|
|
}
|
|
if (command === 'read_project_permission_policy') {
|
|
return emptyProjectPolicy();
|
|
}
|
|
if (command === 'check_game_creator_llm_config') {
|
|
return {
|
|
configured: false,
|
|
apiKeyPresent: false,
|
|
baseUrl: 'https://api.openai.com/v1',
|
|
model: 'gpt-4.1',
|
|
apiKind: 'openai_responses',
|
|
stream: false,
|
|
webSearchEnabled: false,
|
|
error:
|
|
'LLM 未配置:请在 game-creator.config.json 的 llm.apiKey 中设置 API Key',
|
|
agents: [
|
|
{
|
|
agentId: 'design-director',
|
|
label: '拆解创作方向',
|
|
configured: false,
|
|
apiKeyPresent: false,
|
|
baseUrl: 'https://api.openai.com/v1',
|
|
model: 'gpt-4.1',
|
|
apiKind: 'openai_responses',
|
|
stream: false,
|
|
webSearchEnabled: false,
|
|
error:
|
|
'LLM 未配置:请在 agentLlm.design-director.apiKey 中设置 API Key',
|
|
},
|
|
],
|
|
};
|
|
}
|
|
if (command === 'read_local_conversation') {
|
|
return {
|
|
path: args?.agentId
|
|
? '/tmp/authorized-game/.agent/conversations/agents/design-director.jsonl'
|
|
: '/tmp/authorized-game/.agent/conversations/project.jsonl',
|
|
agentId: args?.agentId ?? null,
|
|
messages: [],
|
|
};
|
|
}
|
|
if (command === 'read_local_agent_memory') {
|
|
return {
|
|
taskId: args?.taskId,
|
|
path: '/tmp/authorized-game/memory/agents/design/director.md',
|
|
content: '',
|
|
exists: false,
|
|
};
|
|
}
|
|
if (command === 'read_local_project_file') {
|
|
throw new Error(
|
|
'读取文件元数据失败:/tmp/authorized-game/.agent/run.latest.json: No such file or directory (os error 2)',
|
|
);
|
|
}
|
|
if (command === 'list_local_project_files') {
|
|
return { projectPath: String(args?.projectPath ?? ''), files: [] };
|
|
}
|
|
throw new Error(`unexpected invoke ${command}`);
|
|
},
|
|
);
|
|
window.__TAURI__ = { core: { invoke } };
|
|
renderAppAt('/?main&projectPath=%2Ftmp%2Fauthorized-game');
|
|
fireEvent.click(screen.getByRole('button', { name: /拆解创作方向/ }));
|
|
|
|
const agentDialog = await screen.findByLabelText('Agent 对话');
|
|
const warning = await within(agentDialog).findByRole('status');
|
|
expect(warning.textContent).toContain(
|
|
'当前 Agent 智能服务未就绪:官方智能服务暂不可用,请稍后重试',
|
|
);
|
|
expect(within(agentDialog).getByLabelText('Agent 对话内容')).toHaveProperty(
|
|
'disabled',
|
|
true,
|
|
);
|
|
expect(
|
|
within(agentDialog).getByRole('button', { name: '发送' }),
|
|
).toHaveProperty('disabled', true);
|
|
expect(invoke).not.toHaveBeenCalledWith(
|
|
'chat_with_game_creator_role_agent',
|
|
expect.anything(),
|
|
);
|
|
});
|
|
|
|
it.skip('reports Tauri availability when saving an agent conversation without invoke', async () => {
|
|
const manifest = createGameCreationAppManifest(
|
|
'local-project-draft',
|
|
'未命名游戏原型',
|
|
);
|
|
const invoke = vi.fn(
|
|
async (command: string, args?: Record<string, unknown>) => {
|
|
if (command === 'append_local_permission_log') {
|
|
return {};
|
|
}
|
|
if (command === 'init_local_game_project') {
|
|
const projectPath = String(args?.projectPath ?? '');
|
|
return {
|
|
projectPath,
|
|
manifestPath: `${projectPath}/.agent/manifest.json`,
|
|
manifest,
|
|
};
|
|
}
|
|
if (command === 'read_project_permission_policy') {
|
|
return emptyProjectPolicy();
|
|
}
|
|
if (command === 'read_local_conversation') {
|
|
return {
|
|
path: '/tmp/authorized-game/.agent/conversations/agents/design-director.jsonl',
|
|
agentId: args?.agentId,
|
|
messages: [],
|
|
};
|
|
}
|
|
if (command === 'read_local_agent_memory') {
|
|
return {
|
|
taskId: args?.taskId,
|
|
path: '/tmp/authorized-game/memory/agents/design/director.md',
|
|
content: '',
|
|
exists: false,
|
|
};
|
|
}
|
|
if (command === 'read_local_project_file') {
|
|
throw new Error(
|
|
'读取文件元数据失败:/tmp/authorized-game/.agent/run.latest.json: No such file or directory (os error 2)',
|
|
);
|
|
}
|
|
if (command === 'list_local_project_files') {
|
|
return { projectPath: String(args?.projectPath ?? ''), files: [] };
|
|
}
|
|
throw new Error(`unexpected invoke ${command}`);
|
|
},
|
|
);
|
|
window.__TAURI__ = { core: { invoke } };
|
|
renderAppAt('/?main&projectPath=%2Ftmp%2Fauthorized-game');
|
|
fireEvent.click(screen.getByRole('button', { name: /拆解创作方向/ }));
|
|
const input = await screen.findByLabelText('Agent 对话内容');
|
|
fireEvent.change(input, { target: { value: '确认运行环境提示' } });
|
|
delete window.__TAURI__;
|
|
fireEvent.submit(input.closest('form') as HTMLFormElement);
|
|
|
|
expect(await screen.findByText('需要在 Tauri App 内运行')).not.toBeNull();
|
|
expect(screen.queryByText('请先初始化本地项目')).toBeNull();
|
|
});
|
|
|
|
it.skip('falls back to a saved normal reply when the agent stream listener rejects', async () => {
|
|
const manifest = createGameCreationAppManifest(
|
|
'local-project-draft',
|
|
'未命名游戏原型',
|
|
);
|
|
const savedAgentMessages: Array<{
|
|
schemaVersion: string;
|
|
role: 'user' | 'assistant';
|
|
content: string;
|
|
agentId: string | null;
|
|
updatedAt: number;
|
|
}> = [];
|
|
const invoke = vi.fn(
|
|
async (command: string, args?: Record<string, unknown>) => {
|
|
if (command === 'append_local_permission_log') {
|
|
return {};
|
|
}
|
|
if (command === 'init_local_game_project') {
|
|
const projectPath = String(args?.projectPath ?? '');
|
|
return {
|
|
projectPath,
|
|
manifestPath: `${projectPath}/.agent/manifest.json`,
|
|
manifest,
|
|
};
|
|
}
|
|
if (command === 'read_project_permission_policy') {
|
|
return emptyProjectPolicy();
|
|
}
|
|
if (command === 'read_local_conversation') {
|
|
return {
|
|
path: '/tmp/authorized-game/.agent/conversations/agents/design-director.jsonl',
|
|
agentId: args?.agentId,
|
|
messages: savedAgentMessages,
|
|
};
|
|
}
|
|
if (command === 'read_local_agent_memory') {
|
|
return {
|
|
taskId: args?.taskId,
|
|
path: '/tmp/authorized-game/memory/agents/design/director.md',
|
|
content: '',
|
|
exists: false,
|
|
};
|
|
}
|
|
if (command === 'chat_with_game_creator_role_agent') {
|
|
return {
|
|
replyText: mockRoleAgentReply(),
|
|
};
|
|
}
|
|
if (command === 'append_local_conversation_message') {
|
|
const message = args?.message as {
|
|
role: 'user' | 'assistant';
|
|
content: string;
|
|
agentId: string | null;
|
|
};
|
|
savedAgentMessages.push({
|
|
schemaVersion: 'game-creator-conversation.v1',
|
|
role: message.role,
|
|
content: message.content,
|
|
agentId: message.agentId,
|
|
updatedAt: 1,
|
|
});
|
|
return {
|
|
path: '/tmp/authorized-game/.agent/conversations/agents/design-director.jsonl',
|
|
agentId: args?.agentId,
|
|
messages: savedAgentMessages,
|
|
};
|
|
}
|
|
if (command === 'read_local_project_file') {
|
|
throw new Error(
|
|
'读取文件元数据失败:/tmp/authorized-game/.agent/run.latest.json: No such file or directory (os error 2)',
|
|
);
|
|
}
|
|
if (command === 'list_local_project_files') {
|
|
return { projectPath: String(args?.projectPath ?? ''), files: [] };
|
|
}
|
|
throw new Error(`unexpected invoke ${command}`);
|
|
},
|
|
);
|
|
const listen = vi.fn(async (eventName: string) => {
|
|
if (
|
|
eventName === 'game-creator-agent-progress' ||
|
|
eventName === 'game-creator-agent-runtime-update'
|
|
) {
|
|
return vi.fn();
|
|
}
|
|
if (eventName === 'game-creator-role-agent-chat-stream') {
|
|
throw new Error('core:event:allow-listen denied');
|
|
}
|
|
throw new Error(`unexpected listen ${eventName}`);
|
|
});
|
|
window.__TAURI__ = { core: { invoke }, event: { listen } };
|
|
renderAppAt('/?main&projectPath=%2Ftmp%2Fauthorized-game');
|
|
fireEvent.click(screen.getByRole('button', { name: /拆解创作方向/ }));
|
|
const input = await screen.findByLabelText('Agent 对话内容');
|
|
fireEvent.change(input, { target: { value: '先记住这个方向' } });
|
|
fireEvent.submit(input.closest('form') as HTMLFormElement);
|
|
|
|
expect(await screen.findByText(/已保存 2 条/)).not.toBeNull();
|
|
expect(screen.getByText('先记住这个方向')).not.toBeNull();
|
|
expect(screen.getByText(roleAgentMockReply)).not.toBeNull();
|
|
expect(screen.getByLabelText('Agent 对话内容')).toHaveProperty('value', '');
|
|
expect(listen).toHaveBeenCalledWith(
|
|
'game-creator-role-agent-chat-stream',
|
|
expect.any(Function),
|
|
);
|
|
expect(invoke).toHaveBeenCalledWith('chat_with_game_creator_role_agent', {
|
|
projectPath: '/tmp/authorized-game',
|
|
agentId: 'design-director',
|
|
prompt: '先记住这个方向',
|
|
});
|
|
});
|
|
|
|
it.skip('clears stale agent messages when another agent conversation fails to load', async () => {
|
|
const manifest = createGameCreationAppManifest(
|
|
'local-project-draft',
|
|
'未命名游戏原型',
|
|
);
|
|
const invoke = vi.fn(
|
|
async (command: string, args?: Record<string, unknown>) => {
|
|
if (command === 'append_local_permission_log') {
|
|
return {};
|
|
}
|
|
if (command === 'init_local_game_project') {
|
|
const projectPath = String(args?.projectPath ?? '');
|
|
return {
|
|
projectPath,
|
|
manifestPath: `${projectPath}/.agent/manifest.json`,
|
|
manifest,
|
|
};
|
|
}
|
|
if (command === 'read_project_permission_policy') {
|
|
return emptyProjectPolicy();
|
|
}
|
|
if (command === 'resume_game_creator_agent_runtime_tasks') {
|
|
return [];
|
|
}
|
|
if (command === 'read_game_creator_agent_runtimes') {
|
|
return [];
|
|
}
|
|
if (command === 'read_local_conversation') {
|
|
if (args?.agentId === null) {
|
|
return {
|
|
path: '/tmp/authorized-game/.agent/conversations/project.jsonl',
|
|
agentId: null,
|
|
messages: [],
|
|
};
|
|
}
|
|
if (args?.agentId === 'design-director') {
|
|
return {
|
|
path: '/tmp/authorized-game/.agent/conversations/agents/design-director.jsonl',
|
|
agentId: 'design-director',
|
|
messages: [
|
|
{
|
|
schemaVersion: 'game-creator-conversation.v1',
|
|
role: 'user',
|
|
content: '旧 Agent 历史消息',
|
|
agentId: 'design-director',
|
|
updatedAt: 1,
|
|
},
|
|
],
|
|
};
|
|
}
|
|
throw new Error('读取 Agent 对话失败');
|
|
}
|
|
if (command === 'read_local_agent_memory') {
|
|
return {
|
|
taskId: args?.taskId,
|
|
path: `/tmp/authorized-game/memory/agents/${String(
|
|
args?.taskId ?? '',
|
|
)}.md`,
|
|
content: '',
|
|
exists: false,
|
|
};
|
|
}
|
|
if (command === 'read_local_project_file') {
|
|
throw new Error(
|
|
'读取文件元数据失败:/tmp/authorized-game/.agent/run.latest.json: No such file or directory (os error 2)',
|
|
);
|
|
}
|
|
if (command === 'list_local_project_files') {
|
|
return { projectPath: String(args?.projectPath ?? ''), files: [] };
|
|
}
|
|
throw new Error(`unexpected invoke ${command}`);
|
|
},
|
|
);
|
|
window.__TAURI__ = { core: { invoke } };
|
|
renderAppAt('/?main&projectPath=%2Ftmp%2Fauthorized-game');
|
|
fireEvent.click(screen.getByRole('button', { name: /拆解创作方向/ }));
|
|
expect(await screen.findByText('旧 Agent 历史消息')).not.toBeNull();
|
|
|
|
fireEvent.click(screen.getByRole('button', { name: '关闭' }));
|
|
fireEvent.click(screen.getByRole('button', { name: /确定视觉方向/ }));
|
|
|
|
expect(await screen.findByText('读取 Agent 对话失败')).not.toBeNull();
|
|
expect(screen.getByText('暂无对话')).not.toBeNull();
|
|
expect(screen.queryByText('旧 Agent 历史消息')).toBeNull();
|
|
});
|
|
}
|