53afc85319
- appSurface/harness 补齐三处 jsdom 缺口:ClipboardEvent、DragEvent、Range.prototype.getBoundingClientRect;否则 Lexical 的粘贴通路直接抛 ReferenceError / TypeError
- appSurface/harness 的 submitChat 改为 async:全选并让编辑器吸收选区、清空、走产品真实 paste 通路写入,再让出一帧让 React 追平 draft,最后点发送
- 新增 composerText / composerValue / composerDisabled / setComposerText 助手,按原生控件与 Lexical contenteditable 两种 DOM 口径读写输入区
- 15 个测试文件中 116 处 toHaveProperty('value', …) 断言等义改写为 await composerText() / await composerValue();1 处 placeholder 断言改查输入区占位文案;2 处 disabled 断言改用 composerDisabled(同时覆盖 data-disabled 与 contenteditable=false);9 处 fireEvent.change 写入改用 setComposerText
- 328 处 submitChat 调用补 await,9 个非 async 用例补 async
- Godot 输入区键盘语义用例按 Lexical 实际行为改写:Shift+Enter 与组合态 Enter 由编辑器消化并插入换行、不发送、草稿保留
- 未改动 src 下任何产品代码,未放宽或删除断言
730 lines
23 KiB
TypeScript
730 lines
23 KiB
TypeScript
import {
|
||
composerText,
|
||
createGameCreationAppManifest,
|
||
createGameCreationAppSeedTasks,
|
||
emptyProjectPolicy,
|
||
expect,
|
||
fireEvent,
|
||
GAME_CREATION_AGENT_RUN_SCHEMA_VERSION,
|
||
type GameCreationAgentRunTrace,
|
||
it,
|
||
renderAppAt,
|
||
screen,
|
||
submitChat,
|
||
vi,
|
||
waitFor,
|
||
within,
|
||
} from '../harness';
|
||
|
||
export function registerProjectRunHistoryTests() {
|
||
it('loads recent run history in the developer project window', async () => {
|
||
const manifest = createGameCreationAppManifest(
|
||
'local-project-draft',
|
||
'未命名游戏原型',
|
||
);
|
||
const makeTrace = (
|
||
runId: string,
|
||
status: string,
|
||
passes: number,
|
||
stopReason: string,
|
||
lifecycleStatus?: string,
|
||
updatedAt = passes,
|
||
) =>
|
||
({
|
||
schemaVersion: GAME_CREATION_AGENT_RUN_SCHEMA_VERSION,
|
||
runId,
|
||
commandId: 'game.generate_draft',
|
||
status,
|
||
lifecycleStatus,
|
||
passes,
|
||
maxPasses: 3,
|
||
toolCallCount: 0,
|
||
maxToolCalls: 128,
|
||
stopReason,
|
||
goal: '做一个厨房弹幕游戏',
|
||
coordination: 'Planner -> Generator',
|
||
steps: [],
|
||
artifacts: [],
|
||
taskGraph: {
|
||
goal: '做一个厨房弹幕游戏',
|
||
readyTaskIds: [],
|
||
activeTaskIds: [],
|
||
carriedTaskIds: [],
|
||
repairFocus: [],
|
||
repairRoutes: [],
|
||
tasks: createGameCreationAppSeedTasks(),
|
||
},
|
||
passPlans: [],
|
||
nextStep: 'preview',
|
||
error: null,
|
||
updatedAt,
|
||
}) satisfies GameCreationAgentRunTrace;
|
||
const traces = new Map([
|
||
[
|
||
'.agent/run.latest.json',
|
||
makeTrace('run-current', 'running', 1, 'planning'),
|
||
],
|
||
[
|
||
'.agent/runs/2026-07-02-new.json',
|
||
makeTrace('run-new', 'passed', 2, 'evaluator-passed', 'done', 600),
|
||
],
|
||
[
|
||
'.agent/runs/2026-07-01-old.json',
|
||
makeTrace(
|
||
'run-old',
|
||
'failed',
|
||
3,
|
||
'max-passes-exhausted',
|
||
undefined,
|
||
700,
|
||
),
|
||
],
|
||
[
|
||
'.agent/runs/2026-06-30-mid.json',
|
||
makeTrace('run-mid', 'running', 1, 'planning', undefined, 500),
|
||
],
|
||
[
|
||
'.agent/runs/2026-06-29-four.json',
|
||
makeTrace(
|
||
'run-four',
|
||
'failed',
|
||
3,
|
||
'max-passes-exhausted',
|
||
undefined,
|
||
400,
|
||
),
|
||
],
|
||
[
|
||
'.agent/runs/2026-06-28-five.json',
|
||
makeTrace('run-five', 'passed', 1, 'preview', 'done', 300),
|
||
],
|
||
[
|
||
'.agent/runs/2026-06-27-hidden.json',
|
||
makeTrace(
|
||
'run-hidden',
|
||
'failed',
|
||
3,
|
||
'max-passes-exhausted',
|
||
undefined,
|
||
200,
|
||
),
|
||
],
|
||
[
|
||
'.agent/runs/2026-06-26-hidden.json',
|
||
makeTrace(
|
||
'run-older-hidden',
|
||
'failed',
|
||
3,
|
||
'max-passes-exhausted',
|
||
undefined,
|
||
100,
|
||
),
|
||
],
|
||
]);
|
||
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_local_conversation') {
|
||
return {
|
||
path: '/tmp/authorized-game/.agent/conversations/project.jsonl',
|
||
agentId: null,
|
||
messages: [],
|
||
};
|
||
}
|
||
if (command === 'read_project_permission_policy') {
|
||
return emptyProjectPolicy();
|
||
}
|
||
if (command === 'list_local_project_files') {
|
||
return {
|
||
projectPath: String(args?.projectPath ?? ''),
|
||
files: [
|
||
{
|
||
path: '.agent/runs/2026-07-02-new.json',
|
||
kind: 'file',
|
||
size: 120,
|
||
},
|
||
{
|
||
path: '.agent/runs/2026-07-01-old.json',
|
||
kind: 'file',
|
||
size: 110,
|
||
},
|
||
{
|
||
path: '.agent/runs/2026-06-30-mid.json',
|
||
kind: 'file',
|
||
size: 100,
|
||
},
|
||
{
|
||
path: '.agent/runs/2026-06-29-four.json',
|
||
kind: 'file',
|
||
size: 90,
|
||
},
|
||
{
|
||
path: '.agent/runs/2026-06-28-five.json',
|
||
kind: 'file',
|
||
size: 80,
|
||
},
|
||
{
|
||
path: '.agent/runs/2026-06-27-hidden.json',
|
||
kind: 'file',
|
||
size: 70,
|
||
},
|
||
{
|
||
path: '.agent/runs/2026-06-26-hidden.json',
|
||
kind: 'file',
|
||
size: 60,
|
||
},
|
||
],
|
||
};
|
||
}
|
||
if (command === 'read_local_project_file') {
|
||
const trace = traces.get(String(args?.relativePath ?? ''));
|
||
if (!trace) {
|
||
throw new Error(
|
||
`missing trace ${String(args?.relativePath ?? '')}`,
|
||
);
|
||
}
|
||
return {
|
||
path: String(args?.relativePath ?? ''),
|
||
absolutePath: `${String(args?.projectPath ?? '')}/${String(
|
||
args?.relativePath ?? '',
|
||
)}`,
|
||
content: JSON.stringify(trace),
|
||
};
|
||
}
|
||
throw new Error(`unexpected invoke ${command}`);
|
||
},
|
||
);
|
||
window.__TAURI__ = { core: { invoke } };
|
||
renderAppAt('/?dev&projectPath=%2Ftmp%2Fauthorized-game');
|
||
|
||
const runHistory = await screen.findByLabelText('Agent run history');
|
||
expect(screen.getByText(/run-new/)).not.toBeNull();
|
||
expect(
|
||
screen.getByText(/passed \/ done · 2\/3 轮 · evaluator-passed/),
|
||
).not.toBeNull();
|
||
const oldRunButton = within(runHistory).getByText(
|
||
'run-old · failed · 3/3 轮 · max-passes-exhausted · updated: 700 · .agent/runs/2026-07-01-old.json · 110B',
|
||
);
|
||
expect(oldRunButton).not.toBeNull();
|
||
expect(screen.getByText(/updated: 700/)).not.toBeNull();
|
||
expect(
|
||
screen.getByText(/\.agent\/runs\/2026-07-01-old\.json/),
|
||
).not.toBeNull();
|
||
expect(screen.queryByText('还有 2 个历史 run')).toBeNull();
|
||
expect(screen.getByText(/run-hidden/)).not.toBeNull();
|
||
expect(runHistory.querySelector('button')?.textContent).toContain(
|
||
'run-old',
|
||
);
|
||
expect(runHistory.querySelector('[aria-current="true"]')).toBeNull();
|
||
|
||
fireEvent.click(oldRunButton.closest('button') as Element);
|
||
|
||
expect(
|
||
await screen.findByText('run: failed · 3/3 轮 · max-passes-exhausted'),
|
||
).not.toBeNull();
|
||
expect(oldRunButton.closest('button')?.getAttribute('aria-current')).toBe(
|
||
'true',
|
||
);
|
||
expect(invoke).toHaveBeenCalledWith('read_local_project_file', {
|
||
projectPath: '/tmp/authorized-game',
|
||
relativePath: '.agent/runs/2026-07-01-old.json',
|
||
commandId: 'agent.trace_read',
|
||
});
|
||
});
|
||
|
||
it('uses run history for agent status cards when the latest trace is missing', async () => {
|
||
const manifest = createGameCreationAppManifest(
|
||
'local-project-draft',
|
||
'未命名游戏原型',
|
||
);
|
||
const trace: GameCreationAgentRunTrace = {
|
||
schemaVersion: GAME_CREATION_AGENT_RUN_SCHEMA_VERSION,
|
||
runId: 'run-history-only',
|
||
commandId: 'game.generate_draft',
|
||
status: 'running',
|
||
lifecycleStatus: 'running',
|
||
passes: 1,
|
||
maxPasses: 3,
|
||
toolCallCount: 1,
|
||
maxToolCalls: 128,
|
||
stopReason: 'planning',
|
||
goal: '做一个厨房弹幕游戏',
|
||
coordination: 'Planner -> Generator',
|
||
steps: [
|
||
{
|
||
pass: 1,
|
||
agent: 'Generator',
|
||
phase: 'generate',
|
||
taskId: 'code-prototype',
|
||
group: 'code',
|
||
role: 'Code',
|
||
status: 'running',
|
||
inputPaths: ['.agent/spec.md'],
|
||
outputPaths: ['game/index.html'],
|
||
summary: 'Generator 历史草案生成',
|
||
toolCalls: [],
|
||
},
|
||
],
|
||
artifacts: [],
|
||
taskGraph: {
|
||
goal: '做一个厨房弹幕游戏',
|
||
readyTaskIds: [],
|
||
activeTaskIds: ['code-prototype'],
|
||
carriedTaskIds: [],
|
||
repairFocus: [],
|
||
repairRoutes: [],
|
||
tasks: createGameCreationAppSeedTasks(),
|
||
},
|
||
passPlans: [],
|
||
nextStep: 'generator',
|
||
error: null,
|
||
updatedAt: 1,
|
||
};
|
||
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_local_conversation') {
|
||
return {
|
||
path: '/tmp/authorized-game/.agent/conversations/project.jsonl',
|
||
agentId: null,
|
||
messages: [],
|
||
};
|
||
}
|
||
if (command === 'read_project_permission_policy') {
|
||
return emptyProjectPolicy();
|
||
}
|
||
if (command === 'list_local_project_files') {
|
||
return {
|
||
projectPath: String(args?.projectPath ?? ''),
|
||
files: [
|
||
{
|
||
path: '.agent/runs/run-history-only.json',
|
||
kind: 'file',
|
||
size: 120,
|
||
},
|
||
],
|
||
};
|
||
}
|
||
if (command === 'read_local_project_file') {
|
||
const relativePath = String(args?.relativePath ?? '');
|
||
if (relativePath === '.agent/run.latest.json') {
|
||
throw new Error(
|
||
'读取文件元数据失败:/tmp/authorized-game/.agent/run.latest.json: No such file or directory (os error 2)',
|
||
);
|
||
}
|
||
return {
|
||
path: relativePath,
|
||
absolutePath: `/tmp/authorized-game/${relativePath}`,
|
||
content: JSON.stringify(trace),
|
||
};
|
||
}
|
||
throw new Error(`unexpected invoke ${command}`);
|
||
},
|
||
);
|
||
window.__TAURI__ = { core: { invoke } };
|
||
renderAppAt('/?dev&projectPath=%2Ftmp%2Fauthorized-game');
|
||
|
||
const runHistory = await screen.findByLabelText('Agent run history');
|
||
expect(within(runHistory).getByText(/run-history-only/)).not.toBeNull();
|
||
expect(screen.getByText('Generator 历史草案生成')).not.toBeNull();
|
||
});
|
||
|
||
it('keeps run history out of the regular project chat window', async () => {
|
||
const manifest = createGameCreationAppManifest(
|
||
'local-project-draft',
|
||
'未命名游戏原型',
|
||
);
|
||
const trace = {
|
||
schemaVersion: GAME_CREATION_AGENT_RUN_SCHEMA_VERSION,
|
||
runId: 'run-hidden-from-chat',
|
||
commandId: 'game.generate_draft',
|
||
status: 'passed',
|
||
lifecycleStatus: 'done',
|
||
passes: 1,
|
||
maxPasses: 3,
|
||
toolCallCount: 0,
|
||
maxToolCalls: 128,
|
||
stopReason: 'evaluator-passed',
|
||
goal: '做一个厨房弹幕游戏',
|
||
coordination: 'Planner -> Generator',
|
||
steps: [],
|
||
artifacts: [],
|
||
taskGraph: {
|
||
goal: '做一个厨房弹幕游戏',
|
||
readyTaskIds: [],
|
||
activeTaskIds: [],
|
||
carriedTaskIds: [],
|
||
repairFocus: [],
|
||
repairRoutes: [],
|
||
tasks: createGameCreationAppSeedTasks(),
|
||
},
|
||
passPlans: [],
|
||
nextStep: 'preview',
|
||
error: null,
|
||
updatedAt: 1,
|
||
} satisfies GameCreationAgentRunTrace;
|
||
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 === 'chat_with_game_creator_agent') {
|
||
return {
|
||
replyText: `主聊天回复:${String(args?.prompt ?? '')}`,
|
||
};
|
||
}
|
||
if (command === 'read_local_conversation') {
|
||
return {
|
||
path: '/tmp/authorized-game/.agent/conversations/project.jsonl',
|
||
agentId: null,
|
||
messages: [],
|
||
};
|
||
}
|
||
if (command === 'list_local_project_files') {
|
||
return {
|
||
projectPath: String(args?.projectPath ?? ''),
|
||
files: [
|
||
{
|
||
path: '.agent/runs/run-hidden-from-chat.json',
|
||
kind: 'file',
|
||
size: 100,
|
||
},
|
||
],
|
||
};
|
||
}
|
||
if (command === 'read_local_project_file') {
|
||
return {
|
||
path: String(args?.relativePath ?? ''),
|
||
absolutePath: `${String(args?.projectPath ?? '')}/${String(
|
||
args?.relativePath ?? '',
|
||
)}`,
|
||
content: JSON.stringify(trace),
|
||
};
|
||
}
|
||
throw new Error(`unexpected invoke ${command}`);
|
||
},
|
||
);
|
||
window.__TAURI__ = { core: { invoke } };
|
||
renderAppAt('/?main&projectPath=%2Ftmp%2Fauthorized-game');
|
||
|
||
await waitFor(() => {
|
||
expect(invoke).toHaveBeenCalledWith('list_local_project_files', {
|
||
projectPath: '/tmp/authorized-game',
|
||
});
|
||
});
|
||
await waitFor(() => {
|
||
expect(invoke).toHaveBeenCalledWith('read_local_project_file', {
|
||
projectPath: '/tmp/authorized-game',
|
||
relativePath: '.agent/runs/run-hidden-from-chat.json',
|
||
commandId: 'agent.trace_read',
|
||
});
|
||
});
|
||
expect(screen.queryByLabelText('最近 run')).toBeNull();
|
||
expect(screen.queryByText(/run-hidden-from-chat/)).toBeNull();
|
||
|
||
const runReadCountBeforeRunsCommand = invoke.mock.calls.filter(
|
||
([command]) => command === 'read_local_project_file',
|
||
).length;
|
||
await submitChat('/runs');
|
||
expect(await screen.findByText(/Run 历史读取命令:/)).not.toBeNull();
|
||
expect(
|
||
screen.getByText(
|
||
/run-hidden-from-chat .* \.agent\/runs\/run-hidden-from-chat\.json .*:\/read \.agent\/runs\/run-hidden-from-chat\.json/,
|
||
),
|
||
).not.toBeNull();
|
||
fireEvent.click(screen.getByRole('button', { name: '读取首个历史 Run' }));
|
||
expect(await composerText()).toBe(
|
||
'/read .agent/runs/run-hidden-from-chat.json',
|
||
);
|
||
expect(
|
||
invoke.mock.calls.filter(
|
||
([command]) => command === 'read_local_project_file',
|
||
),
|
||
).toHaveLength(runReadCountBeforeRunsCommand);
|
||
});
|
||
|
||
it('confirms before opening a run history trace when policy requires it', async () => {
|
||
const manifest = createGameCreationAppManifest(
|
||
'local-project-draft',
|
||
'未命名游戏原型',
|
||
);
|
||
const makeTrace = (runId: string, status: string, updatedAt: number) =>
|
||
({
|
||
schemaVersion: GAME_CREATION_AGENT_RUN_SCHEMA_VERSION,
|
||
runId,
|
||
commandId: 'game.generate_draft',
|
||
status,
|
||
lifecycleStatus: status === 'passed' ? 'done' : undefined,
|
||
passes: 1,
|
||
maxPasses: 3,
|
||
toolCallCount: 0,
|
||
maxToolCalls: 128,
|
||
stopReason: status === 'passed' ? 'evaluator-passed' : 'planning',
|
||
goal: '做一个厨房弹幕游戏',
|
||
coordination: 'Planner -> Generator',
|
||
steps: [],
|
||
artifacts: [],
|
||
taskGraph: {
|
||
goal: '做一个厨房弹幕游戏',
|
||
readyTaskIds: [],
|
||
activeTaskIds: [],
|
||
carriedTaskIds: [],
|
||
repairFocus: [],
|
||
repairRoutes: [],
|
||
tasks: createGameCreationAppSeedTasks(),
|
||
},
|
||
passPlans: [],
|
||
nextStep: 'preview',
|
||
error: null,
|
||
updatedAt,
|
||
}) satisfies GameCreationAgentRunTrace;
|
||
const traces = new Map([
|
||
['.agent/run.latest.json', makeTrace('run-current', 'running', 500)],
|
||
['.agent/runs/2026-07-01-old.json', makeTrace('run-old', 'passed', 400)],
|
||
]);
|
||
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_local_conversation') {
|
||
return {
|
||
path: '/tmp/authorized-game/.agent/conversations/project.jsonl',
|
||
agentId: null,
|
||
messages: [],
|
||
};
|
||
}
|
||
if (command === 'read_project_permission_policy') {
|
||
return {
|
||
path: '.agent/policy.json',
|
||
policy: {
|
||
deniedCommands: [],
|
||
confirmCommands: ['agent.trace_read'],
|
||
},
|
||
};
|
||
}
|
||
if (command === 'list_local_project_files') {
|
||
return {
|
||
projectPath: String(args?.projectPath ?? ''),
|
||
files: [
|
||
{
|
||
path: '.agent/runs/2026-07-01-old.json',
|
||
kind: 'file',
|
||
size: 110,
|
||
},
|
||
],
|
||
};
|
||
}
|
||
if (command === 'read_local_project_file') {
|
||
const trace = traces.get(String(args?.relativePath ?? ''));
|
||
if (!trace) {
|
||
throw new Error(
|
||
`missing trace ${String(args?.relativePath ?? '')}`,
|
||
);
|
||
}
|
||
return {
|
||
path: String(args?.relativePath ?? ''),
|
||
absolutePath: `${String(args?.projectPath ?? '')}/${String(
|
||
args?.relativePath ?? '',
|
||
)}`,
|
||
content: JSON.stringify(trace),
|
||
};
|
||
}
|
||
throw new Error(`unexpected invoke ${command}`);
|
||
},
|
||
);
|
||
window.__TAURI__ = { core: { invoke } };
|
||
renderAppAt('/?dev&projectPath=%2Ftmp%2Fauthorized-game');
|
||
|
||
expect(await screen.findByText(/run-old/)).not.toBeNull();
|
||
invoke.mockClear();
|
||
|
||
fireEvent.click(screen.getByText(/run-old/).closest('button') as Element);
|
||
|
||
expect(await screen.findByText('agent.trace_read')).not.toBeNull();
|
||
expect(
|
||
screen.getByText(
|
||
'读取 /tmp/authorized-game 的 .agent/runs/2026-07-01-old.json',
|
||
),
|
||
).not.toBeNull();
|
||
expect(invoke).not.toHaveBeenCalledWith('read_local_project_file', {
|
||
projectPath: '/tmp/authorized-game',
|
||
relativePath: '.agent/runs/2026-07-01-old.json',
|
||
commandId: 'agent.trace_read',
|
||
});
|
||
|
||
fireEvent.click(screen.getByRole('button', { name: '确认' }));
|
||
|
||
await waitFor(() => {
|
||
expect(invoke).toHaveBeenCalledWith('read_local_project_file', {
|
||
projectPath: '/tmp/authorized-game',
|
||
relativePath: '.agent/runs/2026-07-01-old.json',
|
||
commandId: 'agent.trace_read',
|
||
});
|
||
});
|
||
});
|
||
|
||
it('shows more run history entries on demand', async () => {
|
||
const manifest = createGameCreationAppManifest(
|
||
'local-project-draft',
|
||
'未命名游戏原型',
|
||
);
|
||
const makeTrace = (runId: string, updatedAt: number) =>
|
||
({
|
||
schemaVersion: GAME_CREATION_AGENT_RUN_SCHEMA_VERSION,
|
||
runId,
|
||
commandId: 'game.generate_draft',
|
||
status: 'passed',
|
||
lifecycleStatus: 'done',
|
||
passes: 1,
|
||
maxPasses: 3,
|
||
toolCallCount: 0,
|
||
maxToolCalls: 128,
|
||
stopReason: 'evaluator-passed',
|
||
goal: '做一个厨房弹幕游戏',
|
||
coordination: 'Planner -> Generator',
|
||
steps: [],
|
||
artifacts: [],
|
||
taskGraph: {
|
||
goal: '做一个厨房弹幕游戏',
|
||
readyTaskIds: [],
|
||
activeTaskIds: [],
|
||
carriedTaskIds: [],
|
||
repairFocus: [],
|
||
repairRoutes: [],
|
||
tasks: createGameCreationAppSeedTasks(),
|
||
},
|
||
passPlans: [],
|
||
nextStep: 'preview',
|
||
error: null,
|
||
updatedAt,
|
||
}) satisfies GameCreationAgentRunTrace;
|
||
const runFiles = Array.from({ length: 22 }, (_, index) => {
|
||
const number = String(index + 1).padStart(2, '0');
|
||
return {
|
||
path: `.agent/runs/run-${number}.json`,
|
||
kind: 'file',
|
||
size: 100 + index,
|
||
};
|
||
});
|
||
const traces = new Map<string, GameCreationAgentRunTrace>([
|
||
['.agent/run.latest.json', makeTrace('run-current', 1000)],
|
||
...runFiles.map(
|
||
(file, index) =>
|
||
[
|
||
file.path,
|
||
makeTrace(`run-${String(index + 1).padStart(2, '0')}`, index + 1),
|
||
] as const,
|
||
),
|
||
]);
|
||
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_local_conversation') {
|
||
return {
|
||
path: '/tmp/authorized-game/.agent/conversations/project.jsonl',
|
||
agentId: null,
|
||
messages: [],
|
||
};
|
||
}
|
||
if (command === 'list_local_project_files') {
|
||
return {
|
||
projectPath: String(args?.projectPath ?? ''),
|
||
files: runFiles,
|
||
};
|
||
}
|
||
if (command === 'read_local_project_file') {
|
||
const trace = traces.get(String(args?.relativePath ?? ''));
|
||
if (!trace) {
|
||
throw new Error(
|
||
`missing trace ${String(args?.relativePath ?? '')}`,
|
||
);
|
||
}
|
||
return {
|
||
path: String(args?.relativePath ?? ''),
|
||
absolutePath: `${String(args?.projectPath ?? '')}/${String(
|
||
args?.relativePath ?? '',
|
||
)}`,
|
||
content: JSON.stringify(trace),
|
||
};
|
||
}
|
||
throw new Error(`unexpected invoke ${command}`);
|
||
},
|
||
);
|
||
window.__TAURI__ = { core: { invoke } };
|
||
renderAppAt('/?dev&projectPath=%2Ftmp%2Fauthorized-game');
|
||
|
||
const runHistory = await screen.findByLabelText('Agent run history');
|
||
expect(screen.getByText(/run-03/)).not.toBeNull();
|
||
expect(screen.queryByText(/run-02/)).toBeNull();
|
||
expect(invoke).not.toHaveBeenCalledWith('read_local_project_file', {
|
||
projectPath: '/tmp/authorized-game',
|
||
relativePath: '.agent/runs/run-02.json',
|
||
});
|
||
expect(
|
||
screen.getByRole('button', {
|
||
name: '显示更多 · 还有 2 个历史 run',
|
||
}),
|
||
).not.toBeNull();
|
||
fireEvent.scroll(runHistory);
|
||
|
||
expect(await screen.findByText(/run-02/)).not.toBeNull();
|
||
expect(screen.getByText(/run-01/)).not.toBeNull();
|
||
expect(runHistory.querySelector('button')?.textContent).toContain('run-22');
|
||
expect(
|
||
screen.queryByRole('button', {
|
||
name: /显示更多/,
|
||
}),
|
||
).toBeNull();
|
||
});
|
||
}
|