补充主窗口项目摘要
新增主窗口任务资产和最近命令摘要 补充项目摘要的用户面测试 同步AI游戏创作App技术方案和共享决策记录
This commit is contained in:
@@ -27,6 +27,7 @@ import {
|
||||
type GameCreationAgentRunStep,
|
||||
type GameCreationAgentToolCallTrace,
|
||||
type GameCreationAgentRunTrace,
|
||||
type GameCreationAppAssetSourceKind,
|
||||
type GameCreationAppManifest,
|
||||
type GameCreationAppPermission,
|
||||
type GameCreationAppPreviewStatus,
|
||||
@@ -1786,6 +1787,12 @@ const previewStatusLabels: Record<GameCreationAppPreviewStatus, string> = {
|
||||
failed: '失败',
|
||||
};
|
||||
|
||||
const assetSourceKindLabels: Record<GameCreationAppAssetSourceKind, string> = {
|
||||
uploaded: '上传',
|
||||
generated: '生成',
|
||||
canvas: '画板',
|
||||
};
|
||||
|
||||
const capabilityAreaLabels: Record<
|
||||
(typeof GAME_CREATION_AGENT_CAPABILITIES)[number]['area'],
|
||||
string
|
||||
@@ -1944,6 +1951,54 @@ function summarizeProjectStatus(
|
||||
.join('\n');
|
||||
}
|
||||
|
||||
function summarizeMainProjectHeader(
|
||||
nextManifest: GameCreationAppManifest,
|
||||
agents: AgentStatusCard[],
|
||||
) {
|
||||
const tasks = taskRowsFromManifest(nextManifest);
|
||||
const completedCount = tasks.filter(
|
||||
(task) => task.status === 'completed',
|
||||
).length;
|
||||
const readyTaskIds = new Set(
|
||||
selectGameCreationAppReadyTasks({ tasks }).map((task) => task.id),
|
||||
);
|
||||
for (const agent of agents) {
|
||||
if (agent.taskGraphState === 'ready') {
|
||||
readyTaskIds.add(agent.taskId);
|
||||
}
|
||||
}
|
||||
const sourceCounts = nextManifest.assets.reduce(
|
||||
(counts, asset) => {
|
||||
counts[asset.source.kind] += 1;
|
||||
return counts;
|
||||
},
|
||||
{
|
||||
uploaded: 0,
|
||||
generated: 0,
|
||||
canvas: 0,
|
||||
} satisfies Record<GameCreationAppAssetSourceKind, number>,
|
||||
);
|
||||
const sourceSummary = (
|
||||
Object.keys(sourceCounts) as GameCreationAppAssetSourceKind[]
|
||||
)
|
||||
.filter((source) => sourceCounts[source] > 0)
|
||||
.map((source) => `${assetSourceKindLabels[source]} ${sourceCounts[source]}`)
|
||||
.join(' / ');
|
||||
const commandRuns = nextManifest.commandRuns ?? [];
|
||||
const latestCommandRun = commandRuns[commandRuns.length - 1];
|
||||
return [
|
||||
`任务:已完成 ${completedCount}/${tasks.length} · ready ${readyTaskIds.size}`,
|
||||
`资产:${nextManifest.assets.length} 个${
|
||||
sourceSummary ? ` · ${sourceSummary}` : ''
|
||||
}`,
|
||||
latestCommandRun
|
||||
? `最近命令:${latestCommandRun.commandId} ${
|
||||
latestCommandRun.status === 'completed' ? '完成' : '失败'
|
||||
}`
|
||||
: '最近命令:暂无',
|
||||
].join(' · ');
|
||||
}
|
||||
|
||||
function summarizeProjectFiles(files: LocalProjectFileEntry[]) {
|
||||
if (files.length === 0) {
|
||||
return '本地项目还没有文件。';
|
||||
@@ -9189,6 +9244,9 @@ export function App() {
|
||||
manifest,
|
||||
agentRunTrace ?? agentRunHistory[0]?.trace ?? null,
|
||||
);
|
||||
const mainProjectSummary = localProject
|
||||
? summarizeMainProjectHeader(manifest, agentStatusCards)
|
||||
: null;
|
||||
const visibleAgentRunHistory = agentRunHistory.slice(
|
||||
0,
|
||||
agentRunHistoryVisibleCount,
|
||||
@@ -9339,6 +9397,9 @@ export function App() {
|
||||
) : null}
|
||||
<small>run: {agentRunStatus}</small>
|
||||
<small>preview: {previewStatus}</small>
|
||||
{mainProjectSummary ? (
|
||||
<small aria-label="项目摘要">{mainProjectSummary}</small>
|
||||
) : null}
|
||||
<small>{workspaceStatus}</small>
|
||||
</div>
|
||||
<div className="chat-header-actions">
|
||||
|
||||
@@ -200,6 +200,7 @@ describe('AI 游戏创作 App 界面边界', () => {
|
||||
expect(screen.getByRole('button', { name: '配置' })).not.toBeNull();
|
||||
expect(screen.getByRole('button', { name: 'LLM状态' })).not.toBeNull();
|
||||
expect(screen.getByRole('button', { name: '显示目录' })).not.toBeNull();
|
||||
expect(screen.queryByLabelText('项目摘要')).toBeNull();
|
||||
expect(
|
||||
(screen.getByRole('button', { name: '灵感草稿' }) as HTMLButtonElement)
|
||||
.disabled,
|
||||
@@ -1665,6 +1666,22 @@ describe('AI 游戏创作 App 界面边界', () => {
|
||||
localPath: 'assets/uploads/hero.txt',
|
||||
source: { kind: 'uploaded' },
|
||||
});
|
||||
manifest.assets.push({
|
||||
id: 'asset-canvas',
|
||||
kind: 'canvas',
|
||||
mediaType: 'image/png',
|
||||
localPath: 'assets/canvas-sync/tiles.png',
|
||||
source: { kind: 'canvas', canvasProjectId: 'canvas-1' },
|
||||
});
|
||||
manifest.commandRuns = [
|
||||
{
|
||||
commandId: 'game.static_smoke',
|
||||
status: 'completed',
|
||||
output: 'static smoke passed',
|
||||
logPath: '.agent/logs/command.log',
|
||||
updatedAt: 1700000003,
|
||||
},
|
||||
];
|
||||
const trace: GameCreationAgentRunTrace = {
|
||||
schemaVersion: GAME_CREATION_AGENT_RUN_SCHEMA_VERSION,
|
||||
runId: 'run-main-shortcut-trace',
|
||||
@@ -1705,7 +1722,7 @@ describe('AI 游戏创作 App 界面边界', () => {
|
||||
],
|
||||
taskGraph: {
|
||||
goal: '做一个厨房弹幕游戏',
|
||||
readyTaskIds: [],
|
||||
readyTaskIds: ['art-asset-plan'],
|
||||
activeTaskIds: [],
|
||||
carriedTaskIds: [],
|
||||
repairFocus: [],
|
||||
@@ -1915,6 +1932,21 @@ describe('AI 游戏创作 App 界面边界', () => {
|
||||
expect(screen.getByText('未命名游戏原型')).not.toBeNull();
|
||||
expect(screen.getByText('/tmp/authorized-game')).not.toBeNull();
|
||||
expect(screen.getByText('preview: 未启动')).not.toBeNull();
|
||||
const projectSummary = screen.getByLabelText('项目摘要');
|
||||
expect(projectSummary.textContent).toContain('任务:已完成 0/');
|
||||
expect(projectSummary.textContent).toContain('ready');
|
||||
expect(projectSummary.textContent).toContain(
|
||||
'资产:2 个 · 上传 1 / 画板 1',
|
||||
);
|
||||
expect(projectSummary.textContent).toContain(
|
||||
'最近命令:game.static_smoke 完成',
|
||||
);
|
||||
expect(invoke).not.toHaveBeenCalledWith(
|
||||
'get_local_game_manifest',
|
||||
expect.anything(),
|
||||
);
|
||||
expect(screen.queryByLabelText('开发环境')).toBeNull();
|
||||
expect(screen.queryByText('编排 Trace')).toBeNull();
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: '能力' }));
|
||||
expect(await screen.findByText(/Native Runtime 能力/)).not.toBeNull();
|
||||
@@ -11572,16 +11604,19 @@ describe('AI 游戏创作 App 界面边界', () => {
|
||||
|
||||
submitChat('/status');
|
||||
|
||||
expect(await screen.findByText(/项目:未命名游戏原型/)).not.toBeNull();
|
||||
expect(screen.getByText(/目录:\/tmp\/authorized-game/)).not.toBeNull();
|
||||
expect(screen.getByText(/任务:已完成 1,待处理 15/)).not.toBeNull();
|
||||
expect(screen.getByText(/资产:1 个/)).not.toBeNull();
|
||||
expect(
|
||||
screen.getByText(/预览:运行中 http:\/\/127\.0\.0\.1:3210\//),
|
||||
).not.toBeNull();
|
||||
expect(
|
||||
screen.getByText(/最近命令:game\.static_smoke · 完成/),
|
||||
).not.toBeNull();
|
||||
const statusMessage = await screen.findByText((_, element) =>
|
||||
element?.classList.contains('message--assistant') === true &&
|
||||
element.textContent?.includes('项目:未命名游戏原型') === true &&
|
||||
element.textContent.includes('目录:/tmp/authorized-game'),
|
||||
);
|
||||
expect(statusMessage.textContent).toContain('任务:已完成 1,待处理 15');
|
||||
expect(statusMessage.textContent).toContain('资产:1 个');
|
||||
expect(statusMessage.textContent).toContain(
|
||||
'预览:运行中 http://127.0.0.1:3210/',
|
||||
);
|
||||
expect(statusMessage.textContent).toContain(
|
||||
'最近命令:game.static_smoke · 完成',
|
||||
);
|
||||
expect(invoke).toHaveBeenCalledWith('get_local_game_manifest', {
|
||||
projectPath: '/tmp/authorized-game',
|
||||
commandId: 'project.status',
|
||||
|
||||
@@ -3855,6 +3855,7 @@
|
||||
- 2026-06-25 调整:`.agent/run.latest.json` 和 `.agent/runs/<runId>.json` 必须记录 loop 的 `maxPasses` 与 `stopReason`,开发窗口直接展示该状态,避免只从 summary 文案推断 loop 是否跑满、通过、返工、写入产物或进入预览。本地 HTTP 预览的 `/` 映射到 `game/index.html`,路径解析必须 canonicalize 项目根目录和目标文件,只允许访问项目内 `game/` 与 `assets/`,拒绝 `memory/`、`.agent/`、`exports/`、`..`、反斜杠和符号链接越界;常见图片、音频、视频和 Web 资源必须返回对应 MIME。这样上传和画板回流资产能被生成游戏引用,但记忆、trace 和导出包不会被预览服务暴露。
|
||||
- 2026-06-26 调整,2026-07-03 更新:AI 游戏创作 App 借鉴 Harbour 的控制平面思想,但不搬 Harbour 后台。最近 run 在 `.agent/run.latest.json` 增加可选 `lifecycleStatus`,并通过 `/agent-status`、`/agent-kill`、`/agent-retry`、`/agent-resume [说明]` 控制本地生命周期,写入 `.agent/activity.jsonl`、`.agent/output.jsonl` 和 `.agent/context.bundle.json`;聊天里的状态 / 控制结果可填入 `/read .agent/output.jsonl` 草稿继续查看 run 输出,但不直接读取文件或绕过 `file.read` 策略。v1 的 kill/retry/resume 只更新本地状态和上下文包,不伪装成能中断已发出的上游 LLM 请求;后续引入独立 runner 后再把 `pending` 接入 claim。
|
||||
- 2026-07-03 调整:主窗口 Agent 状态栏新增“继续说明”,只把 `/agent-resume ` 填入聊天输入框,让用户补充说明后再走原确认流;策略快捷入口新增 conversation.read / conversation.write 确认草稿,同样只填输入框,不直接写 `.agent/policy.json`。
|
||||
- 2026-07-03 调整:主窗口 header 常驻项目摘要只从当前已加载的 manifest / trace 派生任务完成数、ready 数、资产来源分布和最近命令结果;未选择工作区时不显示,不为了摘要额外触发 Tauri 读取或写入,也不把任务、文件、run history 或预览开发面板搬进普通用户窗口。
|
||||
- 2026-06-25 调整:本地 HTTP 预览静态 `HEAD` 必须返回与 `GET` 相同的真实 `Content-Length`,但不返回 body;浏览器、图片、音频和视频探测不能拿到 `Content-Length: 0` 的假响应。
|
||||
- 2026-06-25 调整:普通用户通过聊天输入 `/run` 触发待确认 `game.run_local`,确认后只能复用白名单 `game.static_smoke` 自检当前 `game/index.html`,通过后启动 `127.0.0.1` 本地 HTTP 预览。独立执行 `game.static_smoke` 时如果已有 `.agent/run.latest.json`,必须追加 `Playtest / game.static_smoke` trace step,避免“运行了代码但编排 trace 不可见”。
|
||||
- 2026-06-25 调整:普通用户通过聊天输入 `/trace` 触发只读 `agent.trace_read`,读取 `.agent/run.latest.json` 并在聊天里摘要 loop 轮次、stopReason、nextStep、active / carry-over 任务、repairRoutes、agent 建议命令和最近 step。trace 面板仍只在开发窗口展示,普通用户窗口不新增面板。
|
||||
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user