From 1a72e3236a7764a073cae323725c2abe9f796012 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E4=BA=94=E9=A6=99=E4=B8=B8=E5=AD=90?= <15518898337@163.com>
Date: Mon, 24 Aug 2026 21:12:58 +0800
Subject: [PATCH 1/5] =?UTF-8?q?=E9=9A=90=E8=97=8F=E9=A6=96=E9=A1=B5?=
=?UTF-8?q?=E6=A8=A1=E7=B3=8A=E9=A1=B9=E7=9B=AE=E6=8F=90=E7=A4=BA?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
首页默认项目状态改为空
移除最近项目标题下的解释性副标题
补充首页视觉规则并更新界面测试
---
.../src/features/app-shell/WorkspaceLauncher.tsx | 2 +-
apps/ai-game-creator-shell/src/view/home/index.tsx | 3 ---
apps/ai-game-creator-shell/tests/appSurface/home.suite.ts | 2 +-
docs/prd/【AI游戏创作】项目开发工作台PRD-2026-07-20.md | 1 +
4 files changed, 3 insertions(+), 5 deletions(-)
diff --git a/apps/ai-game-creator-shell/src/features/app-shell/WorkspaceLauncher.tsx b/apps/ai-game-creator-shell/src/features/app-shell/WorkspaceLauncher.tsx
index 14199a02f..e8ca749c5 100644
--- a/apps/ai-game-creator-shell/src/features/app-shell/WorkspaceLauncher.tsx
+++ b/apps/ai-game-creator-shell/src/features/app-shell/WorkspaceLauncher.tsx
@@ -34,7 +34,7 @@ export function WorkspaceLauncherShell({
ProjectSupervisor,
}: WorkspaceLauncherShellProps) {
const accountWallet = useAccountWallet(currentUser.id);
- const [status, setStatus] = useState('请选择项目');
+ const [status, setStatus] = useState('');
const recentProjects = useRecentProjects(setStatus);
const { recentProjectRows, rememberRecentWorkspace } = recentProjects;
const [launcherView, setLauncherView] = useState(initialView);
diff --git a/apps/ai-game-creator-shell/src/view/home/index.tsx b/apps/ai-game-creator-shell/src/view/home/index.tsx
index f73810e01..00fa69ca7 100644
--- a/apps/ai-game-creator-shell/src/view/home/index.tsx
+++ b/apps/ai-game-creator-shell/src/view/home/index.tsx
@@ -253,9 +253,6 @@ export default function HomeView({
最近项目
-
- 选择一个项目继续创作
-
- {status ? (
-
- {status}
-
- ) : null}
{
rejectAutomaticProject?.(new Error('自动创建测试结束'));
@@ -1522,7 +1523,7 @@ export function registerHomeProjectCreationTests() {
await waitFor(() => {
expect((createButton as HTMLButtonElement).disabled).toBe(false);
});
- expect(screen.getAllByText('自动创建测试结束')).not.toHaveLength(0);
+ expect(screen.queryByText('自动创建测试结束')).toBeNull();
});
it('refreshes Direct Codex art commits while the turn is still running and after a later failure', async () => {
diff --git a/docs/prd/【AI游戏创作】项目开发工作台PRD-2026-07-20.md b/docs/prd/【AI游戏创作】项目开发工作台PRD-2026-07-20.md
index 6fe0495e6..653d993f3 100644
--- a/docs/prd/【AI游戏创作】项目开发工作台PRD-2026-07-20.md
+++ b/docs/prd/【AI游戏创作】项目开发工作台PRD-2026-07-20.md
@@ -118,7 +118,7 @@
- 客户端正式产品仍只按最小 `1280×720` 横屏合同交付,并保留 `1280×800` 默认窗口与既有基线验收;更窄浏览器样式只负责不崩溃和开发兼容,不改成移动端创作工作台。
- 普通用户界面不默认展示内部错误码或本机绝对路径,也不展示 External Editor Base URL 或 Developer API Key;官方服务地址由客户端构建固定,远端编辑自动使用当前陶泥儿登录态。仅独立 standalone game-chat release 或显式高级自定义模式在隔离配置页维护 External v1 URL/Key,确需诊断的信息进入受控详情或开发模式。
- 创建模式素材画布的“素材名称”是用户可编辑的正式输出名称;“资源用途”是 manifest subtype,不向普通用户开放自由文本。新增资源默认“普通游戏美术”,可从普通游戏美术、统一视觉规范、游戏界面原型、核心美术图集四项中选择。图片精修继承源名称和用途,不显示创建模式保存设置;候选图片只从选中图片的“设为最终图”提交。精修顶栏只保留返回、导入、定位当前最终图、撤销和重做,删除进入图片上下文工具栏,通用 AI 生成只保留给创建模式。图片输出统一使用 PNG;创建模式工具动作与保存设置分层展示,“保存到项目”在 `1280×800` 和窄容器中都必须完整可见。
-- 首页创作输入区与“最近项目”之间默认不展示项目选择占位状态,“最近项目”标题下不追加解释性副标题;进行中或失败等可操作反馈仍按实际状态展示。
+- 首页创作输入区与“最近项目”之间不展示共享项目状态文本,“最近项目”标题下也不追加解释性副标题;默认、成功、进行中或失败状态均不得在该位置形成文字行,项目管理页继续保留自己的状态反馈。
### 3.8 现有 Godot 项目
--
2.52.0
From a207928d39c0d50c8b5bac0be74ca063e4be3c3c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E4=BA=94=E9=A6=99=E4=B8=B8=E5=AD=90?= <15518898337@163.com>
Date: Mon, 24 Aug 2026 21:58:43 +0800
Subject: [PATCH 3/5] =?UTF-8?q?=E4=BC=98=E5=8C=96=E8=BF=90=E8=A1=8C?=
=?UTF-8?q?=E8=A7=86=E7=AA=97=E6=92=AD=E6=94=BE=E5=85=A5=E5=8F=A3?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
将桌面运行视窗播放按钮移至顶部中间
资源管理点击播放后直接启动本地预览并切换运行视窗
更新播放链路回归测试与工作台规则
---
apps/ai-game-creator-shell/src/App.tsx | 6 +-
apps/ai-game-creator-shell/src/styles.css | 14 ++++
.../appSurface/project-development.suite.ts | 64 +++++++++++--------
...AI游戏创作】项目开发工作台PRD-2026-07-20.md | 1 +
4 files changed, 57 insertions(+), 28 deletions(-)
diff --git a/apps/ai-game-creator-shell/src/App.tsx b/apps/ai-game-creator-shell/src/App.tsx
index ddf21796e..53dba1e34 100644
--- a/apps/ai-game-creator-shell/src/App.tsx
+++ b/apps/ai-game-creator-shell/src/App.tsx
@@ -1141,6 +1141,9 @@ export function App({
const initialProjectOpenedRef = useRef(false);
const pendingUiConfirmationActionRef = useRef<(() => void) | null>(null);
const queueRunLocalShortcutRef = useRef<() => void>(() => undefined);
+ const executeRunLocalRef = useRef<(announceToChat: boolean) => void>(
+ () => undefined,
+ );
function requestRuntimeConfigOpen() {
if (projectSupervisorOnly && !supervisorChatOnly && !gameChatOnly) {
@@ -3045,6 +3048,7 @@ export function App({
]);
}
queueRunLocalShortcutRef.current = queueRunLocalShortcut;
+ executeRunLocalRef.current = executeRunLocal;
useEffect(() => {
const nextProjectPath = localProject?.projectPath;
@@ -3062,7 +3066,7 @@ export function App({
}
handledPlayRequestRef.current = requestKey;
onPlayRequestHandled?.(playRequest.requestId);
- queueRunLocalShortcutRef.current();
+ void executeRunLocalRef.current(true);
}, [
localProject?.projectPath,
onPlayRequestHandled,
diff --git a/apps/ai-game-creator-shell/src/styles.css b/apps/ai-game-creator-shell/src/styles.css
index 6760b2656..1c531b7ee 100644
--- a/apps/ai-game-creator-shell/src/styles.css
+++ b/apps/ai-game-creator-shell/src/styles.css
@@ -5087,6 +5087,7 @@ iframe.preview-frame {
}
.game-workbench-toolbar {
+ position: relative;
display: flex;
align-items: center;
justify-content: space-between;
@@ -5183,6 +5184,9 @@ iframe.preview-frame {
}
.game-workbench-view-actions .game-workbench-play-button {
+ position: absolute;
+ left: 50%;
+ transform: translateX(-50%);
border-color: var(--platform-button-primary-border);
background: var(--platform-button-primary-fill);
color: var(--platform-button-primary-text);
@@ -7377,6 +7381,11 @@ iframe.preview-frame {
.game-workbench-view-actions {
justify-content: flex-end;
}
+
+ .game-workbench-view-actions .game-workbench-play-button {
+ position: static;
+ transform: none;
+ }
}
@media (max-width: 760px) {
@@ -7407,6 +7416,11 @@ iframe.preview-frame {
flex-direction: column;
}
+ .game-workbench-view-actions .game-workbench-play-button {
+ position: static;
+ transform: none;
+ }
+
.game-workbench-tabs {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
diff --git a/apps/ai-game-creator-shell/tests/appSurface/project-development.suite.ts b/apps/ai-game-creator-shell/tests/appSurface/project-development.suite.ts
index 683457993..8a4d34cc2 100644
--- a/apps/ai-game-creator-shell/tests/appSurface/project-development.suite.ts
+++ b/apps/ai-game-creator-shell/tests/appSurface/project-development.suite.ts
@@ -773,7 +773,9 @@ export function registerProjectWorkbenchFoundationTests() {
'workbench-art-viewport-memory',
'美术资源视口记忆',
);
- const codeTask = manifest.tasks.find((task) => task.id === 'code-prototype');
+ const codeTask = manifest.tasks.find(
+ (task) => task.id === 'code-prototype',
+ );
expect(codeTask).toBeDefined();
codeTask!.status = 'completed';
manifest.assets = [
@@ -1071,7 +1073,9 @@ export function registerProjectWorkbenchFoundationTests() {
).not.toBeNull();
fireEvent.click(within(outline).getByRole('button', { name: /设计文档/ }));
- dispatchPageWheel(screen.getByRole('button', { name: /下一页\s*美术资源/ }));
+ dispatchPageWheel(
+ screen.getByRole('button', { name: /下一页\s*美术资源/ }),
+ );
expect(
screen.getByRole('region', { name: '美术资源资源画布' }),
).not.toBeNull();
@@ -1134,9 +1138,7 @@ export function registerProjectWorkbenchFoundationTests() {
name: /打开资源详情:设计文档 section\.md/,
}),
);
- expect(
- screen.getByRole('dialog', { name: 'section.md' }),
- ).not.toBeNull();
+ expect(screen.getByRole('dialog', { name: 'section.md' })).not.toBeNull();
dispatchPageWheel(dependencyCanvas);
expect(
screen.getByRole('region', { name: '美术资源资源画布' }),
@@ -1278,9 +1280,7 @@ export function registerProjectWorkbenchFoundationTests() {
}),
);
});
- fireEvent.click(
- within(outline).getByRole('button', { name: /项目版本/ }),
- );
+ fireEvent.click(within(outline).getByRole('button', { name: /项目版本/ }));
await act(async () => {
await new Promise((resolve) => setTimeout(resolve, 220));
});
@@ -1288,9 +1288,7 @@ export function registerProjectWorkbenchFoundationTests() {
screen.getByRole('region', { name: '项目版本资源画布' }),
).not.toBeNull();
- fireEvent.click(
- within(outline).getByRole('button', { name: /设计文档/ }),
- );
+ fireEvent.click(within(outline).getByRole('button', { name: /设计文档/ }));
act(() => {
dependencyCanvas.dispatchEvent(
new WheelEvent('wheel', {
@@ -3704,10 +3702,7 @@ export function registerProjectWorkbenchFoundationTests() {
'[data-resource-viewport]',
);
const viewportScale = Number(
- viewportElement
- ?.getAttribute('data-resource-viewport')
- ?.split(',')
- .at(2),
+ viewportElement?.getAttribute('data-resource-viewport')?.split(',').at(2),
);
expect(viewportScale).toBeGreaterThan(0);
const expectedX = Math.round(12 + (-100 - 20) / viewportScale);
@@ -4727,7 +4722,7 @@ export function registerUserSurfaceBoundaryTests() {
}
export function registerProjectSupervisorSurfaceTests() {
- it('routes a top workbench play request into the existing game.run_local confirmation', async () => {
+ it('runs a top workbench play request without a second confirmation', async () => {
const projectPath = '/tmp/top-play-request';
const supervisorHarness = createProjectSupervisorRuntimeHarness({
projectPath,
@@ -4745,6 +4740,19 @@ export function registerProjectSupervisorSurfaceTests() {
recentRunStopReason: null,
};
}
+ if (command === 'start_local_game_preview') {
+ return {
+ url: 'http://127.0.0.1:43127/',
+ port: 43127,
+ root: projectPath,
+ };
+ }
+ if (command === 'get_local_game_manifest') {
+ return createGameCreationAppManifest(
+ 'top-play-request',
+ '顶部播放请求',
+ );
+ }
return supervisorHarness.invoke(command, args);
},
);
@@ -4764,14 +4772,17 @@ export function registerProjectSupervisorSurfaceTests() {
);
expect(
- await screen.findByText('game.run_local', {}, { timeout: 3_000 }),
+ await screen.findByText(
+ /运行通过,已载入客户端运行视图/,
+ {},
+ { timeout: 3_000 },
+ ),
).not.toBeNull();
expect(handled).toHaveBeenCalledWith(7);
- expect(screen.getByText(`启动 ${projectPath}/game/`)).not.toBeNull();
- expect(invoke).not.toHaveBeenCalledWith(
- 'start_local_game_preview',
- expect.anything(),
- );
+ expect(invoke).toHaveBeenCalledWith('start_local_game_preview', {
+ projectPath,
+ });
+ expect(screen.queryByText('game.run_local')).toBeNull();
});
it('starts a direct product preview without the legacy Canvas-only static smoke', async () => {
@@ -4812,9 +4823,6 @@ export function registerProjectSupervisorSurfaceTests() {
}),
);
- expect(await screen.findByText('game.run_local')).not.toBeNull();
- fireEvent.click(screen.getByRole('button', { name: '确认' }));
-
expect(
await screen.findByText(
'运行通过,已载入客户端运行视图:http://127.0.0.1:43126/',
@@ -9961,7 +9969,8 @@ export function registerProjectSupervisorSurfaceTests() {
projectPath,
});
let directTurnUpdateHandler:
- ((event: { payload: Record }) => void) | null = null;
+ | ((event: { payload: Record }) => void)
+ | null = null;
const listen = vi.fn(
async (
eventName: string,
@@ -10919,7 +10928,8 @@ export function registerProjectAgentStatusTests() {
},
);
let runtimeUpdateHandler:
- ((event: { payload: Record }) => void) | null = null;
+ | ((event: { payload: Record }) => void)
+ | null = null;
const listen = vi.fn(
async (
eventName: string,
diff --git a/docs/prd/【AI游戏创作】项目开发工作台PRD-2026-07-20.md b/docs/prd/【AI游戏创作】项目开发工作台PRD-2026-07-20.md
index 653d993f3..a74487190 100644
--- a/docs/prd/【AI游戏创作】项目开发工作台PRD-2026-07-20.md
+++ b/docs/prd/【AI游戏创作】项目开发工作台PRD-2026-07-20.md
@@ -116,6 +116,7 @@
- 资源总览的“资源依赖 / 资源类型”视图切换使用连通的分段按钮组,相邻选项共享边界并保持唯一选中语义。每个分段都必须有清晰的键盘焦点指示,焦点环不得被分段容器的圆角或 `overflow` 裁切。
- 右侧 Supervisor 对话中,用户消息使用右对齐、最大宽度受限的主题暖色气泡,assistant 消息保持左对齐;消息换行不得产生水平溢出,执行过程卡继续占满消息区可用宽度。气泡正文在 light / dark 平台主题下均须满足 WCAG AA 普通文本 `4.5:1` 对比度。
- 客户端正式产品仍只按最小 `1280×720` 横屏合同交付,并保留 `1280×800` 默认窗口与既有基线验收;更窄浏览器样式只负责不崩溃和开发兼容,不改成移动端创作工作台。
+- 工作台顶部播放按钮在桌面视窗中水平居中;资源管理视窗触发播放时直接切换到运行视窗并启动本地预览,不再弹出 `game.run_local` 二次确认。
- 普通用户界面不默认展示内部错误码或本机绝对路径,也不展示 External Editor Base URL 或 Developer API Key;官方服务地址由客户端构建固定,远端编辑自动使用当前陶泥儿登录态。仅独立 standalone game-chat release 或显式高级自定义模式在隔离配置页维护 External v1 URL/Key,确需诊断的信息进入受控详情或开发模式。
- 创建模式素材画布的“素材名称”是用户可编辑的正式输出名称;“资源用途”是 manifest subtype,不向普通用户开放自由文本。新增资源默认“普通游戏美术”,可从普通游戏美术、统一视觉规范、游戏界面原型、核心美术图集四项中选择。图片精修继承源名称和用途,不显示创建模式保存设置;候选图片只从选中图片的“设为最终图”提交。精修顶栏只保留返回、导入、定位当前最终图、撤销和重做,删除进入图片上下文工具栏,通用 AI 生成只保留给创建模式。图片输出统一使用 PNG;创建模式工具动作与保存设置分层展示,“保存到项目”在 `1280×800` 和窄容器中都必须完整可见。
- 首页创作输入区与“最近项目”之间不展示共享项目状态文本,“最近项目”标题下也不追加解释性副标题;默认、成功、进行中或失败状态均不得在该位置形成文字行,项目管理页继续保留自己的状态反馈。
--
2.52.0
From f103a01857cc1925f5861621fe072f5de0c39f5b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E4=BA=94=E9=A6=99=E4=B8=B8=E5=AD=90?= <15518898337@163.com>
Date: Mon, 24 Aug 2026 23:05:54 +0800
Subject: [PATCH 4/5] =?UTF-8?q?=E4=BC=98=E5=8C=96=20Agent=20=E5=AF=B9?=
=?UTF-8?q?=E8=AF=9D=E4=B8=8E=E8=BF=90=E8=A1=8C=E5=85=A5=E5=8F=A3?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
优化 Agent 消息排版与悬浮输入框
隐藏资源管理搜索框并调整运行入口
恢复运行视窗播放按钮位置与箭头样式
---
.../ProjectSupervisorView.tsx | 11 +-
apps/ai-game-creator-shell/src/styles.css | 361 +++++++++++++++++-
.../src/view/project-development/index.tsx | 259 ++++++++-----
3 files changed, 524 insertions(+), 107 deletions(-)
diff --git a/apps/ai-game-creator-shell/src/features/project-workspace/ProjectSupervisorView.tsx b/apps/ai-game-creator-shell/src/features/project-workspace/ProjectSupervisorView.tsx
index f51a37029..0d13a7671 100644
--- a/apps/ai-game-creator-shell/src/features/project-workspace/ProjectSupervisorView.tsx
+++ b/apps/ai-game-creator-shell/src/features/project-workspace/ProjectSupervisorView.tsx
@@ -1,4 +1,4 @@
-import { Send } from 'lucide-react';
+import { ArrowUp } from 'lucide-react';
import type {
ComponentProps,
Dispatch,
@@ -210,14 +210,7 @@ export function ProjectSupervisorView({
type="submit"
disabled={runtimePanelProps.controlBusy || needsUserInput}
>
-
-
- {needsUserInput
- ? '等待回答'
- : runtimePanelProps.controlBusy
- ? '思考中'
- : '发送'}
-
+
diff --git a/apps/ai-game-creator-shell/src/styles.css b/apps/ai-game-creator-shell/src/styles.css
index 1c531b7ee..c9899170e 100644
--- a/apps/ai-game-creator-shell/src/styles.css
+++ b/apps/ai-game-creator-shell/src/styles.css
@@ -5087,7 +5087,6 @@ iframe.preview-frame {
}
.game-workbench-toolbar {
- position: relative;
display: flex;
align-items: center;
justify-content: space-between;
@@ -5184,9 +5183,6 @@ iframe.preview-frame {
}
.game-workbench-view-actions .game-workbench-play-button {
- position: absolute;
- left: 50%;
- transform: translateX(-50%);
border-color: var(--platform-button-primary-border);
background: var(--platform-button-primary-fill);
color: var(--platform-button-primary-text);
@@ -6601,6 +6597,7 @@ iframe.preview-frame {
align-content: start;
gap: 8px;
min-width: 0;
+ min-height: 156px;
padding: 12px;
border: 1px solid #eadbd4;
border-radius: 12px;
@@ -6744,12 +6741,260 @@ iframe.preview-frame {
font-size: 10px;
}
-.game-workbench-chat-avatar {
- width: 48px;
- height: 48px;
+.game-workbench-chat .supervisor-chat-only-runtime-controls {
+ max-width: none;
+}
+
+.game-workbench-chat
+ .supervisor-chat-only-runtime-controls
+ .pending-command {
+ gap: 12px;
+ padding: 10px 12px;
+ border: 1px solid var(--platform-surface-border);
+ border-radius: 10px;
+ background: var(--platform-warm-bg);
+}
+
+.game-workbench-chat
+ .supervisor-chat-only-runtime-controls
+ .pending-command
+ > span {
+ gap: 4px;
+ min-width: 0;
+ margin: 0;
+ overflow-wrap: anywhere;
+ color: var(--platform-text-strong);
+ font-size: 12px;
+ font-weight: 800;
+}
+
+.game-workbench-chat
+ .supervisor-chat-only-runtime-controls
+ .pending-command
+ > span
+ small {
+ color: var(--platform-text-soft);
+ font-weight: 500;
+ line-height: 1.4;
+}
+
+.game-workbench-chat .pending-command-actions button {
+ min-width: 52px;
+ height: 34px;
flex: 0 0 auto;
- object-fit: contain;
- filter: drop-shadow(0 5px 9px rgb(182 98 63 / 16%));
+ border: 1px solid var(--platform-surface-border);
+ background: var(--platform-button-secondary-fill);
+ color: var(--platform-button-secondary-text);
+ font-size: 11px;
+ font-weight: 800;
+}
+
+.game-workbench-chat .pending-command-actions button:last-child {
+ border-color: var(--platform-button-primary-border);
+ background: var(--platform-button-primary-fill);
+ color: var(--platform-button-primary-text);
+}
+
+.agent-runtime-status .project-runtime-pending-command > span {
+ min-width: 0;
+ margin: 0;
+ overflow-wrap: anywhere;
+ color: var(--platform-text-strong);
+ font-size: 12px;
+ font-weight: 800;
+}
+
+.agent-runtime-status .project-runtime-pending-command > span small {
+ display: block;
+ margin-top: 4px;
+ color: var(--platform-text-soft);
+ font-weight: 500;
+ line-height: 1.4;
+}
+
+.agent-runtime-status .project-runtime-pending-command .pending-command-actions {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ flex: 0 0 auto;
+}
+
+.agent-runtime-status
+ .project-runtime-pending-command
+ .pending-command-actions
+ button:last-child {
+ border-color: var(--platform-button-primary-border);
+ background: var(--platform-button-primary-fill);
+ color: var(--platform-button-primary-text);
+}
+
+.game-workbench-chat
+ .agent-runtime-status
+ .project-runtime-pending-command {
+ grid-template-columns: minmax(0, 1fr) 136px;
+}
+
+.game-workbench-chat
+ .agent-runtime-status
+ .project-runtime-pending-command
+ .pending-command-actions {
+ display: grid;
+ grid-template-columns: repeat(2, 64px);
+ gap: 8px;
+ width: 136px;
+ flex: 0 0 136px;
+}
+
+.game-workbench-chat
+ .agent-runtime-status
+ .project-runtime-pending-command
+ .pending-command-actions
+ button {
+ width: 64px;
+ min-width: 64px;
+ max-width: 64px;
+ padding: 0;
+ white-space: nowrap;
+}
+
+/* Runtime 确认卡在部分项目状态下位于状态包装层外,工作台样式直接绑定卡片自身,避免回落旧的蓝色按钮样式。 */
+.game-workbench-chat .project-runtime-pending-command {
+ display: grid;
+ grid-template-columns: minmax(0, 1fr) 136px;
+ align-items: center;
+ gap: 12px;
+ padding: 10px 12px;
+ border: 1px solid var(--platform-surface-border);
+ border-radius: 10px;
+ background: var(--platform-warm-bg);
+}
+
+.game-workbench-chat .project-supervisor-surface > .project-runtime-pending-command,
+.game-workbench-chat .project-supervisor-conversation .project-runtime-pending-command {
+ display: grid;
+ grid-template-columns: minmax(0, 1fr) 136px;
+ align-items: center;
+ gap: 12px;
+ width: 100%;
+ margin: 0;
+ padding: 10px 12px;
+ border: 1px solid var(--platform-surface-border);
+ border-radius: 10px;
+ background: var(--platform-warm-bg);
+}
+
+.game-workbench-chat .project-supervisor-conversation .project-runtime-pending-command > span {
+ min-width: 0;
+ margin: 0;
+ overflow-wrap: anywhere;
+ color: var(--platform-text-strong);
+ font-size: 12px;
+ font-weight: 800;
+}
+
+.game-workbench-chat .project-supervisor-conversation .project-runtime-pending-command > span small {
+ display: block;
+ margin-top: 4px;
+ color: var(--platform-text-soft);
+ font-size: 10px;
+ font-weight: 500;
+ line-height: 1.4;
+}
+
+.game-workbench-chat .project-supervisor-conversation .project-runtime-pending-command .pending-command-actions {
+ display: grid;
+ grid-template-columns: repeat(2, 64px);
+ gap: 8px;
+ width: 136px;
+ flex: 0 0 136px;
+}
+
+.game-workbench-chat .project-supervisor-conversation .project-runtime-pending-command .pending-command-actions button {
+ width: 64px;
+ min-width: 64px;
+ max-width: 64px;
+ height: 34px;
+ padding: 0;
+ border: 1px solid var(--platform-surface-border);
+ border-radius: 8px;
+ background: var(--platform-button-secondary-fill);
+ color: var(--platform-button-secondary-text);
+ font-size: 11px;
+ font-weight: 800;
+ white-space: nowrap;
+}
+
+.game-workbench-chat .project-supervisor-conversation .project-runtime-pending-command .pending-command-actions button:last-child {
+ border-color: var(--platform-button-primary-border);
+ background: var(--platform-button-primary-fill);
+ color: var(--platform-button-primary-text);
+}
+
+/* 该卡片的两个操作按钮禁止被全局 button 规则压缩,确保“拒绝 / 确认”完整可见。 */
+.project-runtime-pending-command .pending-command-actions button {
+ display: inline-flex !important;
+ width: 64px !important;
+ min-width: 64px !important;
+ max-width: 64px !important;
+ flex: 0 0 64px !important;
+ align-items: center;
+ justify-content: center;
+ padding: 0 !important;
+ overflow: visible;
+ white-space: nowrap;
+}
+
+.game-workbench-chat .project-runtime-pending-command > span {
+ min-width: 0;
+ margin: 0;
+ overflow-wrap: anywhere;
+ color: var(--platform-text-strong);
+ font-size: 12px;
+ font-weight: 800;
+}
+
+.game-workbench-chat .project-runtime-pending-command > span small {
+ display: block;
+ margin-top: 4px;
+ color: var(--platform-text-soft);
+ font-size: 10px;
+ font-weight: 500;
+ line-height: 1.4;
+}
+
+.game-workbench-chat .project-runtime-pending-command .pending-command-actions {
+ display: grid;
+ grid-template-columns: repeat(2, 64px);
+ gap: 8px;
+ width: 136px;
+ flex: 0 0 136px;
+}
+
+.game-workbench-chat
+ .project-runtime-pending-command
+ .pending-command-actions
+ button {
+ width: 64px;
+ min-width: 64px;
+ max-width: 64px;
+ height: 34px;
+ padding: 0;
+ border: 1px solid var(--platform-surface-border);
+ border-radius: 8px;
+ background: var(--platform-button-secondary-fill);
+ color: var(--platform-button-secondary-text);
+ font-size: 11px;
+ font-weight: 800;
+ white-space: nowrap;
+}
+
+.game-workbench-chat
+ .project-runtime-pending-command
+ .pending-command-actions
+ button:last-child {
+ border-color: var(--platform-button-primary-border);
+ background: var(--platform-button-primary-fill);
+ color: var(--platform-button-primary-text);
}
.game-workbench-chat .project-supervisor-surface {
@@ -6971,6 +7216,100 @@ iframe.preview-frame {
white-space: nowrap;
}
+/* 右侧对话采用简洁的 Codex 式消息流与编辑框,减少卡片嵌套和视觉噪音。 */
+.game-workbench-chat .project-supervisor-message-list {
+ position: absolute;
+ top: 0;
+ right: 0;
+ bottom: 104px;
+ left: 0;
+ gap: 34px;
+ height: auto;
+ padding: 18px 20px 24px;
+ scroll-padding-bottom: 24px;
+ border: 1px solid var(--platform-subpanel-border);
+ border-radius: 12px;
+ background: var(--platform-input-fill);
+}
+
+.game-workbench-chat .project-supervisor-message-list .message {
+ display: block;
+ width: 100%;
+ max-width: 100%;
+ padding: 4px 0;
+ border: 0;
+ border-radius: 0;
+ background: transparent;
+ color: var(--platform-text-base);
+ font-size: 14px;
+ line-height: 1.85;
+}
+
+.game-workbench-chat .project-supervisor-message-list .message + .message {
+ margin-top: 0;
+}
+
+.game-workbench-chat .project-supervisor-message-list .message--user {
+ width: fit-content;
+ max-width: min(82%, 640px);
+ margin-left: auto;
+ padding: 11px 16px;
+ border: 1px solid var(--platform-button-primary-border);
+ border-radius: 14px 14px 4px;
+ background: var(--platform-warm-bg);
+ color: var(--platform-text-strong);
+}
+
+.game-workbench-chat .project-supervisor-composer {
+ display: block;
+ position: absolute;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ z-index: 5;
+ padding: 0;
+ pointer-events: none;
+}
+
+.game-workbench-chat .project-supervisor-composer textarea {
+ display: block;
+ width: 100%;
+ min-height: 88px;
+ max-height: 180px;
+ padding: 14px 48px 38px 15px;
+ border: 1px solid var(--platform-surface-border);
+ border-radius: 14px;
+ background: var(--platform-input-fill);
+ color: var(--platform-text-strong);
+ font-size: 13px;
+ line-height: 1.6;
+ resize: vertical;
+ pointer-events: auto;
+}
+
+.game-workbench-chat .project-supervisor-composer button {
+ position: absolute;
+ right: 10px;
+ bottom: 10px;
+ display: grid;
+ width: 30px;
+ min-width: 30px;
+ height: 30px;
+ min-height: 30px;
+ padding: 0;
+ border: 0;
+ border-radius: 9px;
+ background: var(--platform-button-primary-fill);
+ color: var(--platform-button-primary-text);
+ place-items: center;
+ pointer-events: auto;
+}
+
+.game-workbench-chat .project-supervisor-composer button svg {
+ width: 14px;
+ height: 14px;
+}
+
.game-agent-dock {
position: relative;
z-index: 30;
@@ -7382,10 +7721,6 @@ iframe.preview-frame {
justify-content: flex-end;
}
- .game-workbench-view-actions .game-workbench-play-button {
- position: static;
- transform: none;
- }
}
@media (max-width: 760px) {
diff --git a/apps/ai-game-creator-shell/src/view/project-development/index.tsx b/apps/ai-game-creator-shell/src/view/project-development/index.tsx
index ac08c3748..1b54b9f4e 100644
--- a/apps/ai-game-creator-shell/src/view/project-development/index.tsx
+++ b/apps/ai-game-creator-shell/src/view/project-development/index.tsx
@@ -45,7 +45,6 @@ import type {
GameIterationVersion,
ProjectResourceCanvasLayoutMode,
} from '../../../../../packages/shared/src/contracts/gameCreationApp';
-import PRODUCT_LOGO from '../../../../../packages/shared/src/icons/taonier-product-ip.png';
import { canonicalAssetBaseName } from '../../features/asset-canvas/assetCanvasNaming';
import {
type AssetCanvasCommitNotification,
@@ -833,6 +832,15 @@ export default function ProjectDevelopmentView({
useState(defaultResourceCanvasViewports);
const [activeResourceCategory, setActiveResourceCategory] =
useState(null);
+ const [unreadResourceCategoryState, setUnreadResourceCategoryState] =
+ useState<{
+ scopeKey: string;
+ categories: Set;
+ }>(() => ({ scopeKey: '', categories: new Set() }));
+ const resourceCategorySnapshotRef = useRef<{
+ scopeKey: string;
+ resourceIdsByCategory: Map>;
+ } | null>(null);
const [selectedResourceId, setSelectedResourceId] = useState(
null,
);
@@ -872,8 +880,10 @@ export default function ProjectDevelopmentView({
const [hiddenCommittedResourceId, setHiddenCommittedResourceId] = useState<
string | null
>(null);
- const [resourcePreviewVersionByResourceId, setResourcePreviewVersionByResourceId] =
- useState
@@ -4792,11 +4886,6 @@ export default function ProjectDevelopmentView({
{!uiEditorRoute ? (
) : null}
- {confirmableToolAction ? (
-
+ {pendingToolAction ? (
+
- {confirmableToolAction.tool}
- {confirmableToolAction.inputSummary ? (
- {confirmableToolAction.inputSummary}
+ {pendingToolAction.tool}
+ {pendingToolAction.inputSummary ? (
+ {pendingToolAction.inputSummary}
) : null}
-
-
+
+
+
+
) : null}
>
diff --git a/apps/ai-game-creator-shell/src/features/project-workspace/SupervisorChatOnlyView.tsx b/apps/ai-game-creator-shell/src/features/project-workspace/SupervisorChatOnlyView.tsx
index 47bc26644..8dc10b02b 100644
--- a/apps/ai-game-creator-shell/src/features/project-workspace/SupervisorChatOnlyView.tsx
+++ b/apps/ai-game-creator-shell/src/features/project-workspace/SupervisorChatOnlyView.tsx
@@ -1526,7 +1526,7 @@ export function SupervisorChatOnlyView({
{pendingCommand ? (
@@ -1539,47 +1539,51 @@ export function SupervisorChatOnlyView({
)}
-
-
+
+
+
+
) : null}
{pendingConfirmation ? (
{pendingConfirmation.commandId}
{pendingConfirmation.detail}
-
-
+
+
+
+
) : null}
diff --git a/apps/ai-game-creator-shell/src/styles.css b/apps/ai-game-creator-shell/src/styles.css
index d195ba73a..278a30356 100644
--- a/apps/ai-game-creator-shell/src/styles.css
+++ b/apps/ai-game-creator-shell/src/styles.css
@@ -1879,6 +1879,19 @@ textarea {
margin: 0;
}
+.supervisor-chat-only-runtime-controls .pending-command {
+ display: grid;
+ grid-template-columns: minmax(0, 1fr) auto;
+ align-items: center;
+}
+
+.pending-command-actions {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ flex: 0 0 auto;
+}
+
.supervisor-chat-only-composer {
display: grid;
grid-template-columns: minmax(0, 1fr) 42px;
@@ -2668,23 +2681,15 @@ textarea {
min-height: 560px;
}
-/* 这一列的成员数量随链路变化:做游戏时 PlanGddStageProgress 与 GddApprovalCard 都返回
- null,做方案时它们出现,pendingCommand / pendingConfirmation 也各自条件渲染,最多能到
- 八个。原先用 `grid-template-rows` 按位置分配轨道,只对做游戏那几个成员成立——做方案一
- 进来整列后移两格:可伸缩的轨道被阶段进度条占走,审批卡落到能被压到 0 的
- `minmax(0, auto)` 上,末尾成员溢出到隐式行再被容器的 `overflow: hidden` 裁掉,界面上
- 就是审批卡截在半句话、消息列表和运行时面板糊在一起。
- 改成弹性列后「谁伸缩」由类名决定而不是由出现顺序决定,加减成员不再移位。 */
.project-supervisor-conversation {
- display: flex;
- flex-direction: column;
+ display: grid;
+ grid-template-rows: minmax(340px, 1fr) auto auto auto;
+ align-content: stretch;
gap: 10px;
min-width: 0;
- min-height: 0;
}
.project-supervisor-message-list {
- flex: 1 1 auto;
min-height: 340px;
max-height: calc(100vh - 330px);
border-radius: 8px;
@@ -2978,18 +2983,25 @@ textarea {
}
.agent-runtime-status .project-runtime-pending-command {
- grid-template-columns: minmax(0, 1fr) auto auto;
- gap: 6px;
+ grid-template-columns: minmax(0, 1fr) auto;
+ align-items: center;
+ gap: 12px;
margin-top: 4px;
- padding: 7px;
- border-radius: 8px;
- background: #fff8e8;
+ padding: 10px 12px;
+ border: 1px solid var(--platform-surface-border);
+ border-radius: 10px;
+ background: var(--platform-warm-bg);
}
.agent-runtime-status .project-runtime-pending-command button {
- height: 28px;
- padding: 0 9px;
- font-size: 10px;
+ min-width: 52px;
+ height: 34px;
+ flex: 0 0 auto;
+ border: 1px solid var(--platform-surface-border);
+ background: var(--platform-button-secondary-fill);
+ color: var(--platform-button-secondary-text);
+ font-size: 11px;
+ font-weight: 800;
}
.agent-runtime-status .project-runtime-pending-command small {
@@ -3307,8 +3319,11 @@ textarea {
min-height: auto;
}
+ .project-supervisor-conversation {
+ grid-template-rows: minmax(320px, auto) auto auto auto;
+ }
+
.project-supervisor-message-list {
- min-height: 320px;
max-height: 52vh;
}
@@ -3526,273 +3541,6 @@ textarea {
gap: 8px;
}
-/* 策划区是一个面:阶段进度是标题栏,审批卡是正文。边框和圆角只画在外壳上,里面两块
- 不再各自带框。 */
-.plan-gdd-surface {
- display: grid;
- min-width: 0;
- border: 1px solid #cfd7e6;
- border-radius: 10px;
- background: #fff;
- overflow: hidden;
-}
-
-.gdd-approval-card {
- display: grid;
- gap: 14px;
- padding: 16px 18px 18px;
- background: #f8fbff;
-}
-
-.plan-gdd-surface--with-card .plan-gdd-stage-progress {
- border-bottom: 1px solid #dbe4f1;
-}
-
-.plan-gdd-stage-progress {
- display: grid;
- gap: 6px;
- padding: 10px 12px;
- background: #fff;
- color: #526173;
- font-size: 12px;
-}
-
-.plan-gdd-stage-progress__header,
-.plan-gdd-stage-progress__meta {
- display: flex;
- flex-wrap: wrap;
- align-items: center;
- justify-content: space-between;
- gap: 8px 14px;
-}
-
-/* 批准后的交付行。它是策划阶段唯一的产物出口,所以给一条分隔线把它和上面的状态
- 区分开,而不是混成第三行元信息。 */
-.plan-gdd-stage-progress__delivery {
- display: grid;
- gap: 8px;
- margin-top: 4px;
- padding-top: 9px;
- border-top: 1px solid #e6ecf5;
-}
-
-.plan-gdd-stage-progress__delivery code {
- min-width: 0;
- color: #3c4a5c;
- font-size: 11px;
- overflow-wrap: anywhere;
-}
-
-.plan-gdd-stage-progress__delivery-actions {
- display: flex;
- flex-wrap: wrap;
- gap: 8px;
-}
-
-.plan-gdd-stage-progress__delivery-actions button {
- min-height: 30px;
- padding: 0 12px;
- border: 1px solid #cfd7e6;
- border-radius: 6px;
- color: #27364a;
- background: #fff;
- font-size: 12px;
-}
-
-.plan-gdd-stage-progress__delivery-actions button:hover:not(:disabled),
-.plan-gdd-stage-progress__delivery-actions button:focus-visible {
- border-color: #1f6feb;
- color: #1f6feb;
-}
-
-.plan-gdd-stage-progress__delivery-actions button:disabled {
- opacity: 0.58;
-}
-
-.plan-gdd-stage-progress__delivery-error {
- color: #b42323;
- overflow-wrap: anywhere;
-}
-
-.plan-gdd-stage-progress__header strong {
- color: #27364a;
-}
-
-.plan-gdd-stage-progress__header span {
- padding: 3px 8px;
- border-radius: 999px;
- background: #eef6ff;
- color: #1f6feb;
-}
-
-.gdd-approval-card__header {
- display: grid;
- gap: 5px;
- min-width: 0;
-}
-
-.gdd-approval-card h2,
-.gdd-approval-card h3,
-.gdd-approval-card p {
- margin: 0;
-}
-
-.gdd-approval-card h2 {
- font-size: 18px;
-}
-
-.gdd-approval-card__header p {
- color: #526173;
- overflow-wrap: anywhere;
-}
-
-.gdd-approval-card__details-trace {
- color: #6b7a8c;
- font-size: 11px;
- overflow-wrap: anywhere;
-}
-
-.gdd-approval-card__details-trigger {
- justify-self: start;
- min-height: 30px;
- padding: 0 10px;
- border: 1px solid #cfd7e6;
- border-radius: 6px;
- color: #27364a;
- background: #fff;
-}
-
-.gdd-approval-card__decisions {
- display: grid;
- gap: 8px;
-}
-
-.gdd-approval-card__decisions article {
- display: grid;
- gap: 3px;
- padding: 9px 10px;
- border: 1px solid #e2e8f0;
- border-radius: 7px;
- background: #fff;
-}
-
-.gdd-approval-card__decisions article span {
- color: #1f6feb;
- font-size: 12px;
-}
-
-.gdd-approval-card__decisions article small {
- color: #526173;
- overflow-wrap: anywhere;
-}
-
-.gdd-approval-card__actions,
-.gdd-approval-card__dialog-actions,
-.gdd-approval-card__recovery {
- display: flex;
- flex-wrap: wrap;
- align-items: center;
- gap: 8px;
-}
-
-.gdd-approval-card__actions button,
-.gdd-approval-card__dialog-actions button,
-.gdd-approval-card__recovery button {
- min-height: 32px;
- padding: 0 12px;
- border: 1px solid #cfd7e6;
- border-radius: 6px;
- color: #fff;
- background: #1f6feb;
-}
-
-.gdd-approval-card__actions button:nth-child(n + 2),
-.gdd-approval-card__dialog-actions button:first-child {
- color: #27364a;
- background: #fff;
-}
-
-.gdd-approval-card button:disabled {
- cursor: not-allowed;
- opacity: 0.55;
-}
-
-.gdd-approval-card__recovery {
- justify-content: space-between;
- padding: 9px 10px;
- border-radius: 7px;
- color: #854d0e;
- background: #fff7df;
- font-size: 13px;
-}
-
-.gdd-approval-card__error {
- padding: 9px 10px;
- border-radius: 7px;
- color: #991b1b;
- background: #fff1f2;
- overflow-wrap: anywhere;
-}
-
-.gdd-approval-card__dialog-backdrop {
- position: fixed;
- inset: 0;
- z-index: 220;
- display: grid;
- padding: 24px;
- background: rgb(0 0 0 / 56%);
- place-items: center;
-}
-
-.gdd-approval-card__dialog {
- display: grid;
- gap: 12px;
- width: min(520px, 100%);
- padding: 20px;
- border: 1px solid #e5e7eb;
- border-radius: 8px;
- background: #fff;
- box-shadow: 0 18px 52px rgb(0 0 0 / 18%);
-}
-
-.gdd-approval-card__details {
- max-height: min(760px, calc(100vh - 32px));
- overflow: auto;
-}
-
-.gdd-approval-card__details header,
-.gdd-approval-card__details article {
- display: grid;
- gap: 5px;
-}
-
-.gdd-approval-card__details article {
- padding-top: 10px;
- border-top: 1px solid #e2e8f0;
-}
-
-.gdd-approval-card__details p {
- white-space: pre-line;
- overflow-wrap: anywhere;
-}
-
-.gdd-approval-card__dialog textarea {
- width: 100%;
- min-height: 110px;
- padding: 9px;
- border: 1px solid #cfd7e6;
- border-radius: 6px;
- resize: vertical;
-}
-
-.gdd-approval-card__dialog-hint {
- padding: 9px 10px;
- border-radius: 7px;
- color: #854d0e;
- background: #fff7df;
- font-size: 13px;
-}
-
.panel-header {
margin-bottom: 10px;
}
@@ -5300,16 +5048,6 @@ iframe.preview-frame {
min-height: 0;
}
-/* 做方案在拿到第一个已登记资源之前,资源画布是空的,双栏等于把对话挤进 360px 又空着
- 一大片。这段时间收成单栏,画布只是 `display: none`,内部状态留着,有产物后自动恢复。 */
-.game-workbench-layout.is-conversation-only {
- grid-template-columns: minmax(0, 1fr);
-}
-
-.game-workbench-layout.is-conversation-only .game-workbench-stage {
- display: none;
-}
-
.game-workbench-layout.is-ui-editor {
grid-template-columns: minmax(0, 1fr);
}
@@ -5502,7 +5240,7 @@ iframe.preview-frame {
}
.game-resource-search {
- display: flex;
+ display: none;
align-items: center;
gap: 8px;
margin: 10px 12px 0;
@@ -6428,7 +6166,7 @@ iframe.preview-frame {
align-content: start;
gap: 6px;
min-height: 0;
- padding: 18px 20px 24px;
+ padding: 24px 28px 28px;
overflow: auto;
overscroll-behavior: contain;
scrollbar-gutter: stable;
@@ -6985,12 +6723,9 @@ iframe.preview-frame {
}
.game-workbench-chat-title {
- position: absolute;
- left: 50%;
- transform: translateX(-50%);
- justify-items: center;
- text-align: center;
- pointer-events: none;
+ min-width: 0;
+ justify-items: start;
+ text-align: left;
}
.game-workbench-chat-wallet {
@@ -7308,18 +7043,22 @@ iframe.preview-frame {
}
.game-workbench-chat .project-supervisor-conversation {
+ position: relative;
+ display: block;
height: 100%;
min-height: 0;
overflow: hidden;
}
.game-workbench-chat .project-supervisor-message-list {
- flex: 1 1 auto;
+ height: 100%;
min-height: 96px;
max-height: none;
overflow-y: auto;
border: 1px solid var(--platform-subpanel-border);
background: var(--platform-input-fill);
+ padding-bottom: 196px;
+ scroll-padding-bottom: 196px;
scrollbar-gutter: stable;
}
@@ -7352,31 +7091,6 @@ iframe.preview-frame {
width: 100%;
}
-/* 审批卡是这一列里唯一没有天花板的成员:Fast GDD 的决定项越多它越高,能把消息列表和运行
- 时面板一起挤出可视区。和 `.agent-runtime-status` 一样给它自己的上限加内部滚动,而不是
- 让它去挤别人。 */
-/* 这个面不参与 flex 压缩。它自己不会长高:标题栏是固定几行,审批卡下面有自己的
- max-height 和内滚,所以高度天然有界。让它可压缩的话,`overflow: hidden`(画圆角
- 要的)会把底部切掉——批准后交付行正好在那儿,表现是路径和两个按钮凭空消失。 */
-.game-workbench-chat .plan-gdd-surface {
- flex: 0 0 auto;
-}
-
-/* 只有审批卡在场时才需要「标题栏定高 + 正文占余下」的两行轨道。批准后卡片收掉,
- 面里只剩标题栏,这条声明会给它套上一个不存在的第二行。 */
-.game-workbench-chat .plan-gdd-surface--with-card {
- min-height: 0;
- grid-template-rows: auto minmax(0, 1fr);
-}
-
-.game-workbench-chat .gdd-approval-card {
- min-height: 0;
- max-height: clamp(240px, 52dvh, 640px);
- overflow-y: auto;
- overscroll-behavior: contain;
- scrollbar-gutter: stable;
-}
-
.game-workbench-chat .agent-runtime-status {
max-height: clamp(120px, 24dvh, 240px);
min-height: 0;
@@ -7388,13 +7102,6 @@ iframe.preview-frame {
scrollbar-gutter: stable;
}
-/* 上面 24dvh/240px 的天花板是给做游戏链路常驻的调试面板设的。策划链路的窄条只在
- 需要用户动手时出现(澄清卡 / 失败恢复),内容是要读完再回答的题面,给它和审批
- 卡同级的空间。 */
-.game-workbench-chat .planning-lane-runtime-strip {
- max-height: clamp(240px, 52dvh, 640px);
-}
-
.game-workbench-chat .project-runtime-summary {
position: sticky;
top: -10px;
@@ -8083,11 +7790,6 @@ iframe.preview-frame {
flex-direction: column;
}
- .game-workbench-view-actions .game-workbench-play-button {
- position: static;
- transform: none;
- }
-
.game-workbench-tabs {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
diff --git a/apps/ai-game-creator-shell/tests/appSurface/project-development.suite.ts b/apps/ai-game-creator-shell/tests/appSurface/project-development.suite.ts
index 5eb599eec..d969f858e 100644
--- a/apps/ai-game-creator-shell/tests/appSurface/project-development.suite.ts
+++ b/apps/ai-game-creator-shell/tests/appSurface/project-development.suite.ts
@@ -2,18 +2,15 @@ import type {
ProjectResourceCanvasLayout,
ProjectResourceCanvasPosition,
} from '../../../../packages/shared/src/contracts/gameCreationApp';
-import { consumeInitialGameChatMessage } from '../../src/App';
+import {
+ consumeInitialGameChatMessage,
+ latestGameChatPlayableRevision,
+} from '../../src/App';
import type {
AgentRuntimeEventRecord,
AgentRuntimeState,
} from '../../src/app/types';
-import {
- AgentRuntimeStatusPanel,
- latestGameChatPlayableRevision as projectLatestGameChatPlayableRevision,
- MUD_POINT_INSUFFICIENT_INTERRUPTION_MESSAGE,
- projectCurrentGameChatRuntimeLineage,
- ProjectSupervisorRuntimePanel,
-} from '../../src/features/agent-runtime';
+import { MUD_POINT_INSUFFICIENT_INTERRUPTION_MESSAGE } from '../../src/features/agent-runtime/model';
import {
buildGameChatProgressEvidence,
collectGameChatResultImages,
@@ -183,7 +180,6 @@ function gameChatRuntimeEvent({
updatedAt,
eventId = `${agentId}-${runId}-${updatedAt}-${eventType}`,
publicText = summary,
- source,
}: {
agentId?: string;
taskId?: string;
@@ -196,7 +192,6 @@ function gameChatRuntimeEvent({
detail?: string | null;
eventId?: string;
publicText?: string | null;
- source?: string;
updatedAt: number;
}): AgentRuntimeEventRecord {
return {
@@ -206,10 +201,9 @@ function gameChatRuntimeEvent({
sessionId,
runId,
source:
- source ??
- (agentId === 'project-supervisor'
+ agentId === 'project-supervisor'
? 'project-supervisor-game-chat'
- : 'agent-delegate'),
+ : 'agent-delegate',
eventId,
eventType,
status,
@@ -249,43 +243,21 @@ function gameChatRuntimeState(
};
}
-function latestGameChatPlayableRevision(
- root: AgentRuntimeState,
- runtimeByAgentId: Record
,
-) {
- return projectLatestGameChatPlayableRevision(
- projectCurrentGameChatRuntimeLineage(root, runtimeByAgentId),
- );
+const GAME_CHAT_STAGE_TASK_IDS = [
+ 'design-director',
+ 'art-director',
+ 'art-asset-plan',
+ 'code-director',
+ 'code-prototype',
+ 'preview-readiness',
+ 'preview-playtest',
+] as const;
+
+function isGameChatStageTask(taskId: string) {
+ return (GAME_CHAT_STAGE_TASK_IDS as readonly string[]).includes(taskId);
}
-function gameChatMainRuntimeWithEvents(
- root: AgentRuntimeState,
- events: AgentRuntimeEventRecord[],
-) {
- const main = gameChatRuntimeState({
- agentId: 'code-prototype',
- taskId: 'code-prototype',
- sessionId: `${root.runId}-main-session`,
- runId: `${root.runId}-main-run`,
- source: 'agent-ready-task-scheduler',
- parentAgentId: 'project-supervisor',
- parentRunId: root.runId,
- status: 'running',
- phase: 'execution',
- updatedAt: root.updatedAt,
- });
- main.recentEvents = events.map((event) => ({
- ...event,
- agentId: main.agentId,
- taskId: main.taskId,
- sessionId: main.sessionId,
- runId: main.runId,
- source: main.source,
- }));
- return main;
-}
-
-function gameChatPlayableMainRuntime({
+function gameChatPreviewPlaytestRuntime({
parentRunId,
revision,
updatedAt,
@@ -294,11 +266,11 @@ function gameChatPlayableMainRuntime({
revision: number;
updatedAt: number;
}): AgentRuntimeState {
- const runId = `code-prototype-${parentRunId}-${revision}`;
- const sessionId = `code-prototype-session-${revision}`;
+ const runId = `preview-playtest-${parentRunId}-${revision}`;
+ const sessionId = `preview-playtest-session-${revision}`;
return gameChatRuntimeState({
- agentId: 'code-prototype',
- taskId: 'code-prototype',
+ agentId: 'preview-playtest',
+ taskId: 'preview-playtest',
sessionId,
runId,
source: 'agent-ready-task-scheduler',
@@ -306,28 +278,15 @@ function gameChatPlayableMainRuntime({
parentRunId,
status: 'completed',
phase: 'completed',
- currentTask: '完成单主静态自检与浏览器试玩验证',
+ currentTask: '执行浏览器试玩验证',
currentGoal: '确认当前 revision 可以试玩',
- currentAction: '静态自检与浏览器试玩验证已通过',
+ currentAction: '浏览器试玩验证已通过',
recentEvents: [
gameChatRuntimeEvent({
- agentId: 'code-prototype',
- taskId: 'code-prototype',
+ agentId: 'preview-playtest',
+ taskId: 'preview-playtest',
sessionId,
runId,
- source: 'agent-ready-task-scheduler',
- eventType: 'observation',
- status: 'completed',
- phase: 'tool-observation',
- summary: 'command.run_limited:ok · game.static_smoke 已完成',
- updatedAt: Math.max(0, updatedAt - 1),
- }),
- gameChatRuntimeEvent({
- agentId: 'code-prototype',
- taskId: 'code-prototype',
- sessionId,
- runId,
- source: 'agent-ready-task-scheduler',
eventType: 'observation',
status: 'completed',
phase: 'tool-observation',
@@ -542,7 +501,7 @@ async function renderGameChatAutoPreviewDriver({
};
const emitValidation = async (revision: number, validatedAt: number) => {
harness.setProjectRevision(revision);
- const runtime = gameChatPlayableMainRuntime({
+ const runtime = gameChatPreviewPlaytestRuntime({
parentRunId,
revision,
updatedAt: validatedAt,
@@ -761,9 +720,7 @@ export function registerProjectWorkbenchFoundationTests() {
expect(screen.getByLabelText('陶泥儿 Agent 对话')).not.toBeNull();
expect(screen.getByLabelText('测试项目总控')).not.toBeNull();
expect(screen.getByLabelText('子 Agent 状态栏')).not.toBeNull();
- expect(
- screen.getByRole('article', { name: /设计实现 Agent/ }),
- ).not.toBeNull();
+ expect(screen.getByRole('article', { name: /策划 Agent/ })).not.toBeNull();
expect(screen.getByRole('article', { name: /美术 Agent/ })).not.toBeNull();
expect(screen.getByRole('article', { name: /程序 Agent/ })).not.toBeNull();
@@ -772,10 +729,7 @@ export function registerProjectWorkbenchFoundationTests() {
}) as HTMLButtonElement;
expect(runTab.disabled).toBe(false);
expect(runTab.getAttribute('data-unavailable')).toBe('true');
- const playButton = screen.getByRole('button', {
- name: '播放',
- }) as HTMLButtonElement;
- expect(playButton.disabled).toBe(true);
+ expect(screen.queryByRole('button', { name: '播放' })).toBeNull();
fireEvent.click(runTab);
expect(runTab.getAttribute('aria-selected')).toBe('false');
expect(
@@ -2291,6 +2245,7 @@ export function registerProjectWorkbenchFoundationTests() {
expect(screen.queryByText('不应显示的项目标题')).toBeNull();
expect(screen.queryByRole('button', { name: '回首页' })).toBeNull();
expect(screen.queryByRole('button', { name: '项目组' })).toBeNull();
+ expect(screen.getByText('仅完成计划')).not.toBeNull();
const resourceSortControl = screen.getByRole('group', {
name: '资源排列方式',
});
@@ -2302,7 +2257,6 @@ export function registerProjectWorkbenchFoundationTests() {
.getAllByRole('button')
.map((button) => button.textContent?.trim()),
).toEqual(['依赖', '类型']);
- expect(screen.getByText('仅完成计划')).not.toBeNull();
expect(
screen.getByText('美术资源计划已完成,尚未生成或登记图片'),
).not.toBeNull();
@@ -4317,7 +4271,7 @@ export function registerProjectWorkbenchFoundationTests() {
).not.toBeNull();
});
- it('keeps the landscape workbench inside the viewport with internal chat scrolling', () => {
+ it('keeps the landscape workbench edge-to-edge with internal chat scrolling', () => {
const styles = readFileSync(
resolve(process.cwd(), 'apps/ai-game-creator-shell/src/styles.css'),
'utf8',
@@ -4335,49 +4289,29 @@ export function registerProjectWorkbenchFoundationTests() {
expect(styles).toMatch(
/@media \(min-width: 761px\)[\s\S]*?\.game-project-workbench\s*\{[^}]*grid-template-rows:\s*minmax\(0, 1fr\) auto[^}]*height:\s*100dvh/,
);
- // 这一列的成员数量随链路变化(做方案会多出阶段进度条与审批卡),所以「谁伸缩」必须由
- // 类名决定而不是由出现顺序决定:一旦回到按位置分配轨道,审批卡就会落到能被压到 0 的
- // 那条上、末尾成员溢出后被 overflow: hidden 裁掉。
expect(styles).toMatch(
- /\.project-supervisor-conversation\s*\{[^}]*display:\s*flex[^}]*flex-direction:\s*column/s,
- );
- expect(styles).not.toMatch(
- /\.project-supervisor-conversation\s*\{[^}]*grid-template-rows/s,
+ /\.game-workbench-chat \.project-supervisor-conversation\s*\{[^}]*grid-template-rows:\s*minmax\(96px, 1fr\) minmax\(0, auto\) auto auto auto[^}]*height:\s*100%[^}]*overflow:\s*hidden/s,
);
expect(styles).toMatch(
- /\.game-workbench-chat \.project-supervisor-conversation\s*\{[^}]*height:\s*100%[^}]*min-height:\s*0[^}]*overflow:\s*hidden/s,
+ /\.game-project-workbench\s*\{[^}]*width:\s*calc\(100vw - var\(--launcher-sidebar-width\)\)[^}]*gap:\s*0[^}]*margin:\s*0[^}]*padding:\s*0[^}]*overflow:\s*hidden/s,
);
expect(styles).toMatch(
- /\.game-workbench-chat \.project-supervisor-message-list\s*\{[^}]*flex:\s*1 1 auto[^}]*min-height:\s*96px[^}]*overflow-y:\s*auto/s,
- );
- // 审批卡与运行状态各自带上限并内部滚动,不能靠挤别人来容纳自己的内容。锁的是
- // 「有 clamp 上限 + 自己滚」这个不变量,不锁具体数值——三个断点是随排版调整的
- // 设计取值,钉死它们只会让每次调高度都顺带改一次测试,却挡不住真正的回归(去掉
- // 上限或去掉内部滚动)。
- expect(styles).toMatch(
- /\.game-workbench-chat \.gdd-approval-card\s*\{[^}]*max-height:\s*clamp\([^)]*\)[^}]*overflow-y:\s*auto/s,
+ /\.game-workbench-layout\s*\{[^}]*grid-template-columns:\s*minmax\(0, 1fr\) minmax\(360px, 0\.36fr\)[^}]*gap:\s*0/s,
);
expect(styles).toMatch(
- /\.game-workbench-chat \.agent-runtime-status\s*\{[^}]*max-height:\s*clamp\([^)]*\)[^}]*overflow-y:\s*auto/s,
+ /\.game-workbench-stage,\s*\.game-workbench-chat\s*\{[^}]*border:\s*0[^}]*border-radius:\s*0/s,
);
- // 策划窄条是 `.agent-runtime-status` 的一种,但它只在需要用户动手时出现,用的是
- // 自己那条更宽的上限;这条断言保证它没有退回去继承调试面板那个 240px 天花板。
expect(styles).toMatch(
- /\.game-workbench-chat \.planning-lane-runtime-strip\s*\{[^}]*max-height:\s*clamp\([^)]*\)/s,
+ /\.game-workbench-chat\s*\{[^}]*border-left:\s*1px solid var\(--platform-line-soft\)[^}]*box-shadow:\s*none/s,
);
- // 策划面画圆角要 `overflow: hidden`,所以它绝不能参与 flex 压缩:一旦可压缩,被压到
- // 内容高度以下时裁掉的正是最底下那行——批准后的交付出口(路径 + 两个按钮)就这么
- // 凭空消失过一次。它也不需要可压缩,审批卡自己有上限和内滚,高度天然有界。
- const planGddSurfaceRule = styles.match(
- /\.game-workbench-chat \.plan-gdd-surface\s*\{[^}]*\}/s,
- );
- expect(planGddSurfaceRule).not.toBeNull();
- expect(planGddSurfaceRule?.[0]).toMatch(/flex:\s*0 0 auto/);
- // 「标题栏定高 + 正文占余下」的两行轨道只在审批卡在场时成立;批准后面里只剩标题栏,
- // 套上不存在的第二行只会让它错位。
- expect(planGddSurfaceRule?.[0]).not.toMatch(/grid-template-rows/);
expect(styles).toMatch(
- /\.game-workbench-chat \.plan-gdd-surface--with-card\s*\{[^}]*grid-template-rows:\s*auto minmax\(0, 1fr\)/s,
+ /\.game-workbench-editor-shell\s*\{[^}]*border:\s*0[^}]*border-radius:\s*0[^}]*box-shadow:\s*none/s,
+ );
+ expect(styles).toMatch(
+ /@media \(max-width: 760px\)[\s\S]*?\.game-workbench-chat\s*\{[^}]*border-top:\s*1px solid var\(--platform-line-soft\)[^}]*border-left:\s*0/s,
+ );
+ expect(styles).toMatch(
+ /\.game-workbench-chat \.agent-runtime-status\s*\{[^}]*max-height:\s*clamp\(120px, 24dvh, 240px\)[^}]*overflow-y:\s*auto/s,
);
expect(styles).toMatch(
/\.game-workbench-chat \.project-runtime-summary\s*\{[^}]*position:\s*sticky[^}]*top:\s*-10px/s,
@@ -4388,7 +4322,62 @@ export function registerProjectWorkbenchFoundationTests() {
const composerRule = styles.match(
/\.game-workbench-chat \.project-supervisor-composer\s*\{([^}]*)\}/s,
);
+ const workbenchToolbarRule = styles.match(
+ /\.game-workbench-toolbar\s*\{([^}]*)\}/s,
+ );
+ expect(workbenchToolbarRule?.[1]).not.toContain('border-bottom:');
+ expect(workbenchToolbarRule?.[1]).toContain('background: transparent;');
expect(composerRule?.[1]).not.toContain('border-top:');
+ expect(styles).toMatch(
+ /\.game-resource-sort-tabs\s*\{[^}]*flex-wrap:\s*nowrap[^}]*gap:\s*0[^}]*padding:\s*0[^}]*overflow:\s*hidden/s,
+ );
+ expect(styles).toMatch(
+ /\.game-workbench-tabs\.game-resource-sort-tabs button\s*\{[^}]*border:\s*0[^}]*border-radius:\s*0/s,
+ );
+ const workbenchStageRule = styles.match(
+ /\.game-workbench-stage\s*\{([^}]*)\}/s,
+ );
+ expect(workbenchStageRule?.[1]).toContain(
+ 'background: var(--game-workbench-stage-fill);',
+ );
+ const workbenchChatRule = styles.match(
+ /\.game-workbench-chat\s*\{([^}]*)\}/s,
+ );
+ expect(workbenchChatRule?.[1]).toContain(
+ 'background: var(--game-workbench-agent-fill);',
+ );
+ const chatHeaderRule = styles.match(
+ /\.game-workbench-chat > header\s*\{([^}]*)\}/s,
+ );
+ expect(chatHeaderRule?.[1]).not.toContain('border-bottom:');
+ expect(chatHeaderRule?.[1]).toContain('background: transparent;');
+ const chatTitleRule = styles.match(
+ /\.game-workbench-chat-title\s*\{([^}]*)\}/s,
+ );
+ expect(chatTitleRule?.[1]).not.toContain('position: absolute;');
+ expect(chatTitleRule?.[1]).toContain('justify-items: start;');
+ expect(chatTitleRule?.[1]).toContain('text-align: left;');
+ const chatComposerRule = styles.match(
+ /\.game-workbench-chat \.project-supervisor-composer\s*\{([^}]*)\}/s,
+ );
+ expect(chatComposerRule?.[1]).toContain(
+ 'grid-template-columns: minmax(0, 1fr) 82px;',
+ );
+ expect(chatComposerRule?.[1]).toContain('z-index: 2;');
+ expect(chatComposerRule?.[1]).not.toContain('border-top:');
+ expect(chatComposerRule?.[1]).toContain('background: transparent;');
+ expect(styles).toMatch(
+ /\.supervisor-chat-only-runtime-controls \.pending-command\s*\{[^}]*display:\s*grid[^}]*grid-template-columns:\s*minmax\(0, 1fr\) auto/s,
+ );
+ expect(styles).toMatch(
+ /\.game-workbench-chat \.pending-command-actions button\s*\{[^}]*min-width:\s*52px[^}]*flex:\s*0 0 auto/s,
+ );
+ expect(styles).toMatch(
+ /\.game-workbench-chat\s+\.agent-runtime-status\s+\.project-runtime-pending-command\s*\{[^}]*grid-template-columns:\s*minmax\(0, 1fr\) 136px/s,
+ );
+ expect(styles).toMatch(
+ /\.game-workbench-chat \.project-runtime-pending-command\s*\{[^}]*grid-template-columns:\s*minmax\(0, 1fr\) 136px/s,
+ );
expect(styles).toMatch(
/\.game-workbench-chat \.project-supervisor-composer button\s*\{[^}]*min-height:\s*72px[^}]*white-space:\s*nowrap/s,
);
@@ -4413,6 +4402,9 @@ export function registerProjectWorkbenchFoundationTests() {
expect(agentDockRule?.[1]).not.toContain('overflow-x:');
expect(agentDockRule?.[1]).toContain('z-index: 30;');
expect(agentDockRule?.[1]).toContain('isolation: isolate;');
+ expect(styles).toMatch(
+ /\.game-run-panels > section\s*\{[^}]*min-height:\s*156px/s,
+ );
expect(agentDockRule?.[1]).toContain('min-height: 58px;');
expect(styles).toMatch(
/\.game-agent-dock-item\s*\{[^}]*flex:\s*1 1 128px[^}]*min-width:\s*0[^}]*max-width:\s*170px/s,
@@ -4618,8 +4610,18 @@ export function registerProjectWorkbenchFoundationTests() {
'allow-scripts allow-same-origin allow-forms allow-pointer-lock',
);
expect(screen.queryByLabelText('测试切片控件')).toBeNull();
- expect(screen.getByLabelText('资源信息面板')).not.toBeNull();
- expect(screen.getByLabelText('数值微调面板')).not.toBeNull();
+ const resourceInfoPanel = screen.getByLabelText('资源信息面板');
+ expect(resourceInfoPanel).not.toBeNull();
+ expect(
+ within(resourceInfoPanel).queryByText('暂停后选择资源可查看已登记信息'),
+ ).toBeNull();
+ const tuningPanel = screen.getByLabelText('数值微调面板');
+ expect(tuningPanel).not.toBeNull();
+ expect(tuningPanel.querySelector('label')).toBeNull();
+ expect(tuningPanel.querySelector('input')).toBeNull();
+ const chat = screen.getByLabelText('陶泥儿 Agent 对话');
+ expect(within(chat).getByText('与陶泥儿的对话')).not.toBeNull();
+ expect(chat.querySelector(':scope > header img')).toBeNull();
fireEvent.click(screen.getByRole('tab', { name: '资源管理' }));
fireEvent.click(screen.getByRole('button', { name: '按类型' }));
@@ -5827,7 +5829,7 @@ export function registerProjectSupervisorSurfaceTests() {
await waitFor(() => {
expect(invoke).toHaveBeenCalledWith('init_local_game_project', {
projectPath,
- projectId: expect.stringMatching(/^local-project-/),
+ projectId: 'local-project-draft',
name: 'game-chat-non-empty',
});
});
@@ -6130,54 +6132,30 @@ export function registerProjectSupervisorSurfaceTests() {
duplicate,
],
});
- const currentMain = gameChatRuntimeState({
- agentId: 'code-prototype',
- taskId: 'code-prototype',
- sessionId: 'code-session',
- runId: 'code-run',
+ const currentChild = gameChatRuntimeState({
+ agentId: 'design-foundation',
+ taskId: 'design-foundation',
+ sessionId: 'design-session',
+ runId: 'design-run',
source: 'agent-ready-task-scheduler',
parentAgentId: 'project-supervisor',
parentRunId: 'active-parent-run',
updatedAt: 30,
recentEvents: [
gameChatRuntimeEvent({
- agentId: 'code-prototype',
- sessionId: 'code-session',
- runId: 'code-run',
- source: 'agent-ready-task-scheduler',
- summary: '当前主 Agent 事件',
+ agentId: 'design-foundation',
+ runId: 'design-run',
+ summary: '当前子 Agent 事件',
updatedAt: 30,
}),
gameChatRuntimeEvent({
- agentId: 'code-prototype',
- sessionId: 'code-session',
- runId: 'previous-code-run',
- source: 'agent-ready-task-scheduler',
- summary: '主 Agent 旧 run 事件',
+ agentId: 'design-foundation',
+ runId: 'previous-design-run',
+ summary: '子 Agent 旧 run 事件',
updatedAt: 60,
}),
],
});
- const currentArtChild = gameChatRuntimeState({
- agentId: 'art-asset-plan',
- taskId: 'art-asset-plan',
- sessionId: 'current-art-session',
- runId: 'current-art-run',
- source: 'agent-delegate',
- parentAgentId: 'code-prototype',
- parentRunId: 'code-run',
- updatedAt: 25,
- recentEvents: [
- gameChatRuntimeEvent({
- agentId: 'art-asset-plan',
- sessionId: 'current-art-session',
- runId: 'current-art-run',
- source: 'agent-delegate',
- summary: '当前美术 child 事件',
- updatedAt: 25,
- }),
- ],
- });
const staleChild = gameChatRuntimeState({
agentId: 'art-asset-plan',
taskId: 'art-asset-plan',
@@ -6205,20 +6183,17 @@ export function registerProjectSupervisorSurfaceTests() {
]);
const events = collectGameChatRuntimeEvents(supervisor, {
- 'code-prototype': currentMain,
- 'current-art-asset-plan': currentArtChild,
- 'stale-art-asset-plan': staleChild,
+ 'design-foundation': currentChild,
+ 'art-asset-plan': staleChild,
});
expect(events.map((item) => item.event.summary)).toEqual([
- '当前主 Agent 事件',
- '当前美术 child 事件',
+ '当前子 Agent 事件',
'总控事件 2',
'总控事件 1',
]);
expect(events.map((item) => item.agentLabel)).toEqual([
- '程序原型 Agent',
- '美术资源计划 Agent',
+ '玩法策划 Agent',
'项目总控 Agent',
'项目总控 Agent',
]);
@@ -6291,22 +6266,20 @@ export function registerProjectSupervisorSurfaceTests() {
}),
],
});
- const mainRuntime = gameChatRuntimeState({
- agentId: 'code-prototype',
- taskId: 'code-prototype',
- sessionId: 'code-prototype-session',
- runId: 'code-prototype-main-run',
- source: 'agent-ready-task-scheduler',
+ const designRuntime = gameChatRuntimeState({
+ agentId: 'design-director',
+ taskId: 'design-director',
+ sessionId: 'design-director-session',
+ runId: 'design-director-child-run',
+ source: 'agent-delegate',
parentAgentId: 'project-supervisor',
parentRunId: runId,
recentEvents: [
gameChatRuntimeEvent({
- agentId: 'code-prototype',
- sessionId: 'code-prototype-session',
- runId: 'code-prototype-main-run',
- source: 'agent-ready-task-scheduler',
+ agentId: 'design-director',
+ runId: 'design-director-child-run',
eventType: 'turn.started',
- summary: 'Code prototype Agent started and must stay visible',
+ summary: 'Design Agent started and must stay visible',
updatedAt: 32,
}),
],
@@ -6314,16 +6287,14 @@ export function registerProjectSupervisorSurfaceTests() {
});
const messages = gameChatRuntimeEventMessages(runtime, {
- 'code-prototype': mainRuntime,
+ 'design-director': designRuntime,
});
expect(messages.map((message) => message.updatedAt)).toEqual([
32, 40, 60, 80,
]);
expect(messages.map((message) => message.text)).toEqual([
- expect.stringContaining(
- 'Code prototype Agent started and must stay visible',
- ),
+ expect.stringContaining('Design Agent started and must stay visible'),
expect.stringContaining('Generated prototype progress'),
expect.stringContaining('code agent repair collision'),
expect.stringContaining('preview.validate:ok'),
@@ -6374,7 +6345,14 @@ export function registerProjectSupervisorSurfaceTests() {
gameChatRuntimeEventMessages(supervisorContinuation, {}).map(
(message) => message.text,
),
- ).toEqual([]);
+ ).toEqual([
+ expect.stringContaining(
+ `Supervisor ${continuation.kind} continuation started`,
+ ),
+ expect.stringContaining(
+ `Supervisor ${continuation.kind} continuation failed`,
+ ),
+ ]);
}
});
@@ -6414,13 +6392,17 @@ export function registerProjectSupervisorSurfaceTests() {
requestKind: 'tool-plan',
},
]);
- expect(messages).toHaveLength(3);
+ expect(messages).toHaveLength(7);
expect(messages.map((message) => message.text)).toEqual([
+ expect.stringContaining('玩法方向已完成'),
expect.stringContaining('视觉方向已完成'),
expect.stringContaining('平台美术图集已生成并登记'),
+ expect.stringContaining('程序方案已完成'),
expect.stringContaining('代码原型已完成'),
+ expect.stringContaining('预览就绪检查已完成'),
+ expect.stringContaining('试玩验证已完成'),
]);
- expect(messages[0]?.messageId).toContain('art-director');
+ expect(messages[0]?.messageId).toContain('design-director');
expect(messages[0]?.messageId).toContain('game-chat-final-reply:');
expect(messages.every((message) => message.agentId)).toBe(true);
const hydrated = mergeGameChatFinalReplyMessagesIntoHistory(
@@ -6434,145 +6416,6 @@ export function registerProjectSupervisorSurfaceTests() {
).toHaveLength(1);
});
- it('hides generic retry for exact game-chat dynamic art children and shows cross-round recovery guidance', () => {
- const root = gameChatRuntimeState({
- runId: 'game-chat-dynamic-art-retry-root',
- status: 'running',
- phase: 'waiting-for-delegate-receipts',
- });
- const main = gameChatRuntimeState({
- agentId: 'code-prototype',
- taskId: 'code-prototype',
- sessionId: 'game-chat-dynamic-art-main-session',
- runId: 'game-chat-dynamic-art-main-run',
- source: 'agent-ready-task-scheduler',
- parentAgentId: 'project-supervisor',
- parentRunId: root.runId,
- status: 'running',
- phase: 'waiting-for-delegate-receipts',
- });
- const artDirector = gameChatRuntimeState({
- agentId: 'art-director',
- taskId: 'art-director',
- sessionId: 'game-chat-dynamic-art-director-session',
- runId: 'game-chat-dynamic-art-director-run',
- source: 'agent-delegate',
- parentAgentId: 'code-prototype',
- parentRunId: main.runId,
- status: 'failed',
- phase: 'failed',
- currentAction: '动态美术委派失败',
- });
- const legacyRetry = gameChatRuntimeState({
- agentId: 'art-asset-plan',
- taskId: 'art-asset-plan',
- sessionId: 'game-chat-dynamic-art-retry-session',
- runId: 'game-chat-dynamic-art-retry-run',
- source: 'agent-delegate-retry',
- parentAgentId: 'code-prototype',
- parentRunId: main.runId,
- status: 'failed',
- phase: 'failed',
- currentAction: '遗留 retry 只作诊断展示',
- });
- const wrongParentArt = gameChatRuntimeState({
- agentId: 'art-director',
- taskId: 'art-director',
- sessionId: 'game-chat-wrong-parent-art-session',
- runId: 'game-chat-wrong-parent-art-run',
- source: 'agent-delegate',
- parentAgentId: 'project-supervisor',
- parentRunId: root.runId,
- status: 'failed',
- phase: 'failed',
- currentAction: '旧 root 直属美术不得显示',
- });
- const callbacks = {
- onProfessionalRetry: vi.fn(async () => 'unexpected retry'),
- onProfessionalToolAction: vi.fn(async () => {}),
- onSupervisorRetry: vi.fn(async () => 'unexpected supervisor retry'),
- onToolAction: vi.fn(),
- onUserInput: vi.fn(),
- };
- const renderPanel = (runtime: AgentRuntimeState) =>
- React.createElement(ProjectSupervisorRuntimePanel, {
- runtime,
- error: '',
- runtimeByAgentId: {
- 'code-prototype': main,
- 'art-director': artDirector,
- 'art-asset-plan': legacyRetry,
- 'wrong-parent-art': wrongParentArt,
- },
- controlBusy: false,
- professionalResultsByAgentId: {},
- ...callbacks,
- });
- const rendered = render(renderPanel(root));
- const list = screen.getByLabelText('专业 Agent 实时状态');
-
- expect(
- within(list).queryByRole('button', { name: '在当前项目重试' }),
- ).toBeNull();
- expect(list.textContent).not.toContain('旧 root 直属美术不得显示');
- expect(callbacks.onProfessionalRetry).not.toHaveBeenCalled();
-
- rendered.rerender(
- renderPanel({ ...root, status: 'completed', phase: 'completed' }),
- );
- expect(
- within(screen.getByLabelText('专业 Agent 实时状态')).getAllByText(
- '请继续 game-chat 对话,由下一轮程序原型 Agent 重新审计缺口后委派',
- ),
- ).toHaveLength(2);
- });
-
- it('disables generic retry when a selected runtime claims nested game-chat art lineage', () => {
- const onRetryRuntimeTask = vi.fn();
- const nestedArt = gameChatRuntimeState({
- agentId: 'art-director',
- taskId: 'art-director',
- sessionId: 'selected-game-chat-art-session',
- runId: 'selected-game-chat-art-run',
- source: 'agent-delegate',
- parentAgentId: 'code-prototype',
- parentRunId: 'selected-game-chat-main-run',
- status: 'failed',
- phase: 'failed',
- });
- const rendered = render(
- React.createElement(AgentRuntimeStatusPanel, {
- runtime: nestedArt,
- onRetryRuntimeTask,
- }),
- );
- const retryButton = within(
- screen.getByLabelText('Agent Runtime 操作'),
- ).getByRole('button', { name: '重试' }) as HTMLButtonElement;
- expect(retryButton.disabled).toBe(true);
- fireEvent.click(retryButton);
- expect(onRetryRuntimeTask).not.toHaveBeenCalled();
-
- rendered.rerender(
- React.createElement(AgentRuntimeStatusPanel, {
- runtime: {
- ...nestedArt,
- parentAgentId: 'project-supervisor',
- parentRunId: 'full-dag-root-run',
- },
- onRetryRuntimeTask,
- }),
- );
- expect(
- (
- within(screen.getByLabelText('Agent Runtime 操作')).getByRole(
- 'button',
- { name: '重试' },
- ) as HTMLButtonElement
- ).disabled,
- ).toBe(false);
- });
-
it('persists professional final-reply streams with stable ids and does not duplicate them after hydration', async () => {
const projectPath = '/tmp/game-chat-final-reply-hydration';
const runId = 'game-chat-final-reply-hydration-run';
@@ -6583,31 +6426,20 @@ export function registerProjectSupervisorSurfaceTests() {
phase: 'execution',
updatedAt: 100,
});
- const mainRunId = `code-prototype-${runId}`;
const makeRuntimeResult = (
agentId: string,
status: 'ready' | 'committed',
text: string,
updatedAt: number,
) => {
- const currentMain = agentId === 'code-prototype';
- const currentDynamicArt = ['art-director', 'art-asset-plan'].includes(
- agentId,
- );
const state = gameChatRuntimeState({
agentId,
taskId: agentId,
- sessionId: `${agentId}-session-active`,
- runId: currentMain ? mainRunId : `${agentId}-${runId}`,
- source: currentMain
- ? 'agent-ready-task-scheduler'
- : currentDynamicArt
- ? 'agent-delegate'
- : 'agent-ready-task-scheduler',
- parentAgentId: currentDynamicArt
- ? 'code-prototype'
- : 'project-supervisor',
- parentRunId: currentDynamicArt ? mainRunId : runId,
+ sessionId: 'supervisor-session-active',
+ runId: `${agentId}-${runId}`,
+ source: 'agent-delegate',
+ parentAgentId: 'project-supervisor',
+ parentRunId: runId,
status: 'completed',
phase: 'completed',
updatedAt,
@@ -6620,7 +6452,7 @@ export function registerProjectSupervisorSurfaceTests() {
schemaVersion: 'game-creator-runtime-response-stream.v1',
agentId,
taskId: agentId,
- sessionId: state.sessionId,
+ sessionId: 'supervisor-session-active',
runId: state.runId,
requestKind: 'final-reply',
requestSlot: 'final-reply-loop-1-revision-1',
@@ -6694,8 +6526,8 @@ export function registerProjectSupervisorSurfaceTests() {
expect(screen.getByText(/视觉方向已完成/)).not.toBeNull();
expect(screen.getByText(/平台美术图集已生成并登记/)).not.toBeNull();
expect(screen.getByText(/代码原型已完成/)).not.toBeNull();
- expect(screen.queryByText(/预览就绪已完成/)).toBeNull();
- expect(screen.queryByText(/试玩验证已完成/)).toBeNull();
+ expect(screen.getByText(/预览就绪已完成/)).not.toBeNull();
+ expect(screen.getByText(/试玩验证已完成/)).not.toBeNull();
});
const finalReplyAppends = () =>
invoke.mock.calls.filter(
@@ -6704,14 +6536,14 @@ export function registerProjectSupervisorSurfaceTests() {
String(args?.messageId ?? '').startsWith('game-chat-final-reply:'),
);
await waitFor(() => {
- expect(finalReplyAppends()).toHaveLength(3);
+ expect(finalReplyAppends()).toHaveLength(7);
});
rendered.unmount();
rendered = renderRelease();
await waitFor(() => {
expect(screen.getByText(/代码原型已完成/)).not.toBeNull();
});
- expect(finalReplyAppends()).toHaveLength(3);
+ expect(finalReplyAppends()).toHaveLength(7);
rendered.unmount();
});
@@ -6743,7 +6575,6 @@ export function registerProjectSupervisorSurfaceTests() {
initialRuntime: {
sessionId: 'supervisor-session-active',
runId,
- source: 'project-supervisor-game-chat',
status: 'running',
phase: 'execution',
recentEvents: events,
@@ -6861,16 +6692,7 @@ export function registerProjectSupervisorSurfaceTests() {
],
});
- const progress = buildGameChatProgressEvidence(
- runtime,
- {
- 'code-prototype': gameChatMainRuntimeWithEvents(
- runtime,
- runtime.recentEvents ?? [],
- ),
- },
- null,
- );
+ const progress = buildGameChatProgressEvidence(runtime, {}, null);
expect(progress?.evidence).toEqual([
expect.objectContaining({
@@ -6897,16 +6719,7 @@ export function registerProjectSupervisorSurfaceTests() {
],
});
- const progress = buildGameChatProgressEvidence(
- runtime,
- {
- 'code-prototype': gameChatMainRuntimeWithEvents(
- runtime,
- runtime.recentEvents ?? [],
- ),
- },
- null,
- );
+ const progress = buildGameChatProgressEvidence(runtime, {}, null);
expect(progress?.evidence).toEqual([
expect.objectContaining({
@@ -6938,16 +6751,7 @@ export function registerProjectSupervisorSurfaceTests() {
});
expect(
- buildGameChatProgressEvidence(
- runtime,
- {
- 'code-prototype': gameChatMainRuntimeWithEvents(
- runtime,
- runtime.recentEvents ?? [],
- ),
- },
- null,
- )?.evidence,
+ buildGameChatProgressEvidence(runtime, {}, null)?.evidence,
).toEqual([expect.objectContaining({ label, tone })]);
}
});
@@ -6974,16 +6778,7 @@ export function registerProjectSupervisorSurfaceTests() {
],
});
- const progress = buildGameChatProgressEvidence(
- runtime,
- {
- 'code-prototype': gameChatMainRuntimeWithEvents(
- runtime,
- runtime.recentEvents ?? [],
- ),
- },
- null,
- );
+ const progress = buildGameChatProgressEvidence(runtime, {}, null);
expect(progress?.evidence).toEqual([
expect.objectContaining({
@@ -7010,16 +6805,7 @@ export function registerProjectSupervisorSurfaceTests() {
],
});
- const progress = buildGameChatProgressEvidence(
- runtime,
- {
- 'code-prototype': gameChatMainRuntimeWithEvents(
- runtime,
- runtime.recentEvents ?? [],
- ),
- },
- null,
- );
+ const progress = buildGameChatProgressEvidence(runtime, {}, null);
expect(progress?.evidence).toEqual([
expect.objectContaining({
@@ -7031,18 +6817,17 @@ export function registerProjectSupervisorSurfaceTests() {
it('derives a playable revision only from a passed preview validation with a positive integer revision', () => {
const runId = 'game-chat-playable-revision-run';
- const invalidPlaytest = gameChatPlayableMainRuntime({
+ const invalidPlaytest = gameChatPreviewPlaytestRuntime({
parentRunId: runId,
revision: 1,
updatedAt: 3000,
});
const invalidEvidence = [
gameChatRuntimeEvent({
- agentId: 'code-prototype',
- taskId: 'code-prototype',
+ agentId: 'preview-playtest',
+ taskId: 'preview-playtest',
sessionId: invalidPlaytest.sessionId,
runId: invalidPlaytest.runId,
- source: 'agent-ready-task-scheduler',
eventType: 'observation',
summary: 'preview.validate:failed',
detail: JSON.stringify({
@@ -7053,11 +6838,10 @@ export function registerProjectSupervisorSurfaceTests() {
updatedAt: 8000,
}),
gameChatRuntimeEvent({
- agentId: 'code-prototype',
- taskId: 'code-prototype',
+ agentId: 'preview-playtest',
+ taskId: 'preview-playtest',
sessionId: invalidPlaytest.sessionId,
runId: invalidPlaytest.runId,
- source: 'agent-ready-task-scheduler',
eventType: 'observation',
summary: 'preview.validate:ok · 验证未通过',
detail: JSON.stringify({
@@ -7068,22 +6852,20 @@ export function registerProjectSupervisorSurfaceTests() {
updatedAt: 7000,
}),
gameChatRuntimeEvent({
- agentId: 'code-prototype',
- taskId: 'code-prototype',
+ agentId: 'preview-playtest',
+ taskId: 'preview-playtest',
sessionId: invalidPlaytest.sessionId,
runId: invalidPlaytest.runId,
- source: 'agent-ready-task-scheduler',
eventType: 'observation',
summary: 'preview.validate:ok · 缺少 revision',
detail: JSON.stringify({ passed: true, playtestPassed: true }),
updatedAt: 6000,
}),
gameChatRuntimeEvent({
- agentId: 'code-prototype',
- taskId: 'code-prototype',
+ agentId: 'preview-playtest',
+ taskId: 'preview-playtest',
sessionId: invalidPlaytest.sessionId,
runId: invalidPlaytest.runId,
- source: 'agent-ready-task-scheduler',
eventType: 'observation',
summary: 'preview.validate:ok · revision 非正整数',
detail: JSON.stringify({
@@ -7094,11 +6876,10 @@ export function registerProjectSupervisorSurfaceTests() {
updatedAt: 5000,
}),
gameChatRuntimeEvent({
- agentId: 'code-prototype',
- taskId: 'code-prototype',
+ agentId: 'preview-playtest',
+ taskId: 'preview-playtest',
sessionId: invalidPlaytest.sessionId,
runId: invalidPlaytest.runId,
- source: 'agent-ready-task-scheduler',
eventType: 'observation',
summary: 'preview.validate:ok · revision 不是整数',
detail: JSON.stringify({
@@ -7116,22 +6897,21 @@ export function registerProjectSupervisorSurfaceTests() {
expect(
latestGameChatPlayableRevision(runtimeWithOnlyInvalidEvidence, {
- 'code-prototype': invalidPlaytest,
+ 'preview-playtest': invalidPlaytest,
}),
).toBeNull();
- const previewPlaytest = gameChatPlayableMainRuntime({
+ const previewPlaytest = gameChatPreviewPlaytestRuntime({
parentRunId: runId,
revision: 9,
- updatedAt: 8000,
+ updatedAt: 9000,
});
previewPlaytest.recentEvents?.push(
gameChatRuntimeEvent({
- agentId: 'code-prototype',
- taskId: 'code-prototype',
+ agentId: 'preview-playtest',
+ taskId: 'preview-playtest',
sessionId: previewPlaytest.sessionId,
runId: previewPlaytest.runId,
- source: 'agent-ready-task-scheduler',
eventType: 'observation',
status: 'completed',
phase: 'tool-observation',
@@ -7145,11 +6925,10 @@ export function registerProjectSupervisorSurfaceTests() {
updatedAt: 8500,
}),
gameChatRuntimeEvent({
- agentId: 'code-prototype',
- taskId: 'code-prototype',
+ agentId: 'preview-playtest',
+ taskId: 'preview-playtest',
sessionId: previewPlaytest.sessionId,
runId: previewPlaytest.runId,
- source: 'agent-ready-task-scheduler',
eventType: 'observation',
status: 'completed',
phase: 'tool-observation',
@@ -7165,25 +6944,23 @@ export function registerProjectSupervisorSurfaceTests() {
);
expect(
latestGameChatPlayableRevision(runtimeWithOnlyInvalidEvidence, {
- 'code-prototype': previewPlaytest,
+ 'preview-playtest': previewPlaytest,
}),
- ).toEqual(
- expect.objectContaining({ runId, revision: 10, validatedAt: 8800 }),
- );
+ ).toEqual({ runId, revision: 10, validatedAt: 8800 });
- const staleParentPlaytest = gameChatPlayableMainRuntime({
+ const staleParentPlaytest = gameChatPreviewPlaytestRuntime({
parentRunId: 'different-parent-run',
revision: 11,
updatedAt: 11000,
});
expect(
latestGameChatPlayableRevision(runtimeWithOnlyInvalidEvidence, {
- 'code-prototype': staleParentPlaytest,
+ 'preview-playtest': staleParentPlaytest,
}),
).toBeNull();
const wrongSourcePlaytest = {
- ...gameChatPlayableMainRuntime({
+ ...gameChatPreviewPlaytestRuntime({
parentRunId: runId,
revision: 12,
updatedAt: 12000,
@@ -7192,22 +6969,26 @@ export function registerProjectSupervisorSurfaceTests() {
};
expect(
latestGameChatPlayableRevision(runtimeWithOnlyInvalidEvidence, {
- 'code-prototype': wrongSourcePlaytest,
+ 'preview-playtest': wrongSourcePlaytest,
}),
).toBeNull();
- const higherFailedRevision = gameChatPlayableMainRuntime({
+ const passedRevision = gameChatPreviewPlaytestRuntime({
parentRunId: runId,
revision: 20,
updatedAt: 20000,
});
- higherFailedRevision.recentEvents?.push(
+ const higherFailedRevision = gameChatPreviewPlaytestRuntime({
+ parentRunId: runId,
+ revision: 21,
+ updatedAt: 21000,
+ });
+ higherFailedRevision.recentEvents = [
gameChatRuntimeEvent({
- agentId: 'code-prototype',
- taskId: 'code-prototype',
+ agentId: 'preview-playtest',
+ taskId: 'preview-playtest',
sessionId: higherFailedRevision.sessionId,
runId: higherFailedRevision.runId,
- source: 'agent-ready-task-scheduler',
eventType: 'observation',
status: 'failed',
phase: 'tool-observation',
@@ -7219,25 +7000,25 @@ export function registerProjectSupervisorSurfaceTests() {
}),
updatedAt: 21000,
}),
- );
+ ];
expect(
latestGameChatPlayableRevision(runtimeWithOnlyInvalidEvidence, {
- 'code-prototype': higherFailedRevision,
+ 'preview-playtest-passed': passedRevision,
+ 'preview-playtest-higher-failed': higherFailedRevision,
}),
).toBeNull();
- const sameRevisionLaterFailed = gameChatPlayableMainRuntime({
+ const sameRevisionLaterFailed = gameChatPreviewPlaytestRuntime({
parentRunId: runId,
revision: 22,
updatedAt: 22000,
});
sameRevisionLaterFailed.recentEvents?.push(
gameChatRuntimeEvent({
- agentId: 'code-prototype',
- taskId: 'code-prototype',
+ agentId: 'preview-playtest',
+ taskId: 'preview-playtest',
sessionId: sameRevisionLaterFailed.sessionId,
runId: sameRevisionLaterFailed.runId,
- source: 'agent-ready-task-scheduler',
eventType: 'observation',
status: 'failed',
phase: 'tool-observation',
@@ -7252,7 +7033,7 @@ export function registerProjectSupervisorSurfaceTests() {
);
expect(
latestGameChatPlayableRevision(runtimeWithOnlyInvalidEvidence, {
- 'code-prototype': sameRevisionLaterFailed,
+ 'preview-playtest': sameRevisionLaterFailed,
}),
).toBeNull();
});
@@ -7409,8 +7190,8 @@ export function registerProjectSupervisorSurfaceTests() {
],
});
const childRuntime = gameChatRuntimeState({
- agentId: 'code-prototype',
- taskId: 'code-prototype',
+ agentId: 'code-director',
+ taskId: 'code-director',
sessionId: 'game-chat-stalled-code-session',
runId: 'game-chat-stalled-code-run',
source: 'agent-ready-task-scheduler',
@@ -7419,8 +7200,8 @@ export function registerProjectSupervisorSurfaceTests() {
updatedAt: now - 5 * 60 * 1000 - 10 * 1000,
recentEvents: [
gameChatRuntimeEvent({
- agentId: 'code-prototype',
- taskId: 'code-prototype',
+ agentId: 'code-director',
+ taskId: 'code-director',
sessionId: 'game-chat-stalled-code-session',
runId: 'game-chat-stalled-code-run',
summary: '程序 Agent 最近进度',
@@ -7557,9 +7338,9 @@ export function registerProjectSupervisorSurfaceTests() {
label: 'child provider retry',
runtime: baseRuntime,
runtimeByAgentId: {
- 'code-prototype': gameChatRuntimeState({
- agentId: 'code-prototype',
- taskId: 'code-prototype',
+ 'code-director': gameChatRuntimeState({
+ agentId: 'code-director',
+ taskId: 'code-director',
sessionId: 'game-chat-waiting-code-session',
runId: 'game-chat-waiting-code-run',
source: 'agent-ready-task-scheduler',
@@ -7595,9 +7376,9 @@ export function registerProjectSupervisorSurfaceTests() {
taskId: 'art-director',
sessionId: 'game-chat-waiting-art-session',
runId: 'game-chat-waiting-art-run',
- source: 'agent-delegate',
- parentAgentId: 'code-prototype',
- parentRunId: 'game-chat-stalled-code-run',
+ source: 'agent-ready-task-scheduler',
+ parentAgentId: 'project-supervisor',
+ parentRunId: runId,
phase: 'waiting-for-provider-retry',
updatedAt: now - 10 * 1000,
});
@@ -7670,7 +7451,6 @@ export function registerProjectSupervisorSurfaceTests() {
taskId: 'code-director',
sessionId: 'game-chat-terminal-duration-code-session',
runId: 'game-chat-terminal-duration-code-run',
- source: 'agent-ready-task-scheduler',
eventType: 'turn.completed',
status: 'completed',
phase: 'completed',
@@ -7682,7 +7462,7 @@ export function registerProjectSupervisorSurfaceTests() {
renderGameChatStatus({
runtime,
- runtimeByAgentId: { 'code-prototype': childRuntime },
+ runtimeByAgentId: { 'code-director': childRuntime },
});
expect(screen.getByLabelText('最新状态').textContent).toContain(
'已持续 9 分 0 秒',
@@ -7724,58 +7504,6 @@ export function registerProjectSupervisorSurfaceTests() {
expect(document.body.textContent).not.toMatch(/第\s*\d+\s*轮/u);
});
- it('fails closed when the game-chat surface hydrates a non-game-chat Supervisor root', () => {
- const guiRuntime = gameChatRuntimeState({
- source: 'project-supervisor-gui',
- recentEvents: [
- gameChatRuntimeEvent({
- summary: '不应进入 game-chat 当前投影',
- updatedAt: 100,
- }),
- ],
- });
-
- renderGameChatStatus({ runtime: guiRuntime });
-
- expect(screen.getByLabelText('最新状态').textContent).toContain('未运行');
- expect(screen.queryByText('不应进入 game-chat 当前投影')).toBeNull();
- expect(buildGameChatProgressEvidence(guiRuntime, {}, null)).toBeNull();
- expect(collectGameChatRuntimeEvents(guiRuntime, {})).toEqual([]);
- });
-
- it('keeps game-chat running while an exact descendant is still active after root terminal projection', () => {
- const root = gameChatRuntimeState({
- runId: 'terminal-root-active-main',
- status: 'completed',
- phase: 'completed',
- updatedAt: 200,
- });
- const main = gameChatRuntimeState({
- agentId: 'code-prototype',
- taskId: 'code-prototype',
- sessionId: 'terminal-root-active-main-session',
- runId: 'terminal-root-active-main-run',
- source: 'agent-ready-task-scheduler',
- parentAgentId: 'project-supervisor',
- parentRunId: root.runId,
- status: 'running',
- phase: 'waiting-for-delegate-receipts',
- currentAction: '等待动态美术回执',
- updatedAt: 201,
- });
-
- renderGameChatStatus({
- runtime: root,
- runtimeByAgentId: { 'code-prototype': main },
- });
-
- const status = screen.getByLabelText('最新状态').textContent ?? '';
- expect(status).toContain('运行中');
- expect(status).toContain('主阶段 0/1');
- expect(status).toContain('正在等待专业 Agent 回执');
- expect(status).not.toContain('本轮已完成');
- });
-
it('shows and archives the explicit mud point interruption from a failed art child runtime', () => {
const rootRunId = 'game-chat-mud-point-root-run';
const runtime = gameChatRuntimeState({
@@ -7785,36 +7513,21 @@ export function registerProjectSupervisorSurfaceTests() {
error: '专业 Agent 执行失败',
updatedAt: 9100,
});
- const mainRuntime = gameChatRuntimeState({
- agentId: 'code-prototype',
- taskId: 'code-prototype',
- sessionId: 'game-chat-mud-point-main-session',
- runId: 'game-chat-mud-point-main-run',
- source: 'agent-ready-task-scheduler',
- parentAgentId: 'project-supervisor',
- parentRunId: rootRunId,
- status: 'failed',
- phase: 'failed',
- updatedAt: 9050,
- });
const artRuntime = gameChatRuntimeState({
agentId: 'art-asset-plan',
taskId: 'art-asset-plan',
sessionId: 'game-chat-mud-point-art-session',
runId: 'game-chat-mud-point-art-run',
source: 'agent-delegate',
- parentAgentId: 'code-prototype',
- parentRunId: mainRuntime.runId,
+ parentAgentId: 'project-supervisor',
+ parentRunId: rootRunId,
status: 'failed',
phase: 'failed',
error:
'平台图片生成任务失败:可消费泥点不足:需要 10,扣除退款占用后可用 2;operationId=private-operation-id',
updatedAt: 9000,
});
- const runtimeByAgentId = {
- 'code-prototype': mainRuntime,
- 'art-asset-plan': artRuntime,
- };
+ const runtimeByAgentId = { 'art-asset-plan': artRuntime };
renderGameChatStatus({ runtime, runtimeByAgentId });
@@ -7860,13 +7573,13 @@ export function registerProjectSupervisorSurfaceTests() {
expect(stageRecord).not.toContain('本轮失败');
});
- it('counts only the current single main stage in game-chat progress', () => {
+ it('counts only the seven first-playable tasks in game-chat progress', () => {
const manifest = createGameCreationAppManifest(
'game-chat-progress-total',
'game-chat-progress-total',
);
manifest.tasks = manifest.tasks.map((task) =>
- task.id === 'code-prototype'
+ isGameChatStageTask(task.id)
? { ...task, status: 'completed' as const }
: task,
);
@@ -7876,43 +7589,23 @@ export function registerProjectSupervisorSurfaceTests() {
phase: 'execution',
updatedAt: 9000,
});
- const mainRuntime = gameChatRuntimeState({
- agentId: 'code-prototype',
- taskId: 'code-prototype',
- sessionId: 'game-chat-progress-main-session',
- runId: 'game-chat-progress-main-run',
- source: 'agent-ready-task-scheduler',
- parentAgentId: 'project-supervisor',
- parentRunId: runtime.runId,
- status: 'completed',
- phase: 'completed',
- updatedAt: 8999,
- });
- renderGameChatStatus({
- runtime,
- runtimeByAgentId: { 'code-prototype': mainRuntime },
- manifest,
- });
+ renderGameChatStatus({ runtime, manifest });
const status = screen.getByLabelText('最新状态');
- expect(status.textContent).toContain('主阶段 1/1');
+ expect(status.textContent).toContain('任务图 7/7');
expect(status.textContent).not.toContain('publish-strategy');
expect(status.textContent).not.toContain('publish-package');
fireEvent.click(within(status).getByRole('button', { name: '运行详情' }));
const progress = screen.getByLabelText('Supervisor 进度播报');
- expect(progress.textContent).toContain('主阶段 1/1');
+ expect(progress.textContent).toContain('任务图 7/7');
});
it('keeps one live progress card while persisting every public game-chat output as a message', async () => {
const projectPath = '/tmp/game-chat-progress-broadcast';
const supervisorRunId = 'game-chat-progress-run';
const previewFailure = gameChatRuntimeEvent({
- agentId: 'code-prototype',
- taskId: 'code-prototype',
- sessionId: 'code-prototype-progress-session',
- runId: 'code-prototype-progress-run',
- source: 'agent-ready-task-scheduler',
+ runId: supervisorRunId,
eventType: 'observation',
status: 'failed',
phase: 'tool-observation',
@@ -7945,7 +7638,7 @@ export function registerProjectSupervisorSurfaceTests() {
{ index: 2, title: '修复并复测', status: 'pending' },
],
activePlanStepIndex: 1,
- recentEvents: [],
+ recentEvents: [previewFailure],
updatedAt: 7200,
};
let professionalRuntimes: Array> = [];
@@ -7979,7 +7672,6 @@ export function registerProjectSupervisorSurfaceTests() {
},
],
activePlanStepIndex: 0,
- recentEvents: [previewFailure],
updatedAt: 7300,
}) as AgentRuntimeState;
professionalRuntimes = [codePrototypeRuntime];
@@ -8079,7 +7771,7 @@ export function registerProjectSupervisorSurfaceTests() {
);
const status = await screen.findByLabelText('最新状态');
- expect(status.textContent).toContain('主阶段 0/1 · 计划 1/3');
+ expect(status.textContent).toContain('任务图 3/7 · 进行中 1 · 计划 1/3');
expect(status.textContent).toContain('核对首版试玩诊断');
expect(status.textContent).toContain('1 个专业 Agent 活跃');
const runtimeEventAppends = () =>
@@ -8099,7 +7791,9 @@ export function registerProjectSupervisorSurfaceTests() {
expect(progress.getAttribute('data-run-id')).toBe(supervisorRunId);
expect(within(progress).getByText('本轮生成进度')).not.toBeNull();
expect(within(progress).queryByText(/第 4 轮/u)).toBeNull();
- expect(within(progress).getByText('主阶段 0/1 · 计划 1/3')).not.toBeNull();
+ expect(
+ within(progress).getByText('任务图 3/7 · 进行中 1 · 计划 1/3'),
+ ).not.toBeNull();
expect(within(progress).getByText('核对首版试玩诊断')).not.toBeNull();
expect(within(progress).getByText('活跃专业 Agent')).not.toBeNull();
expect(
@@ -8112,11 +7806,7 @@ export function registerProjectSupervisorSurfaceTests() {
),
).not.toBeNull();
const delegateDecision = gameChatRuntimeEvent({
- agentId: 'code-prototype',
- taskId: 'code-prototype',
- sessionId: codePrototypeRuntime.sessionId,
- runId: codePrototypeRuntime.runId,
- source: 'agent-ready-task-scheduler',
+ runId: supervisorRunId,
eventType: 'action',
phase: 'tool-action',
summary: '调用工具 agent.delegate',
@@ -8154,20 +7844,13 @@ export function registerProjectSupervisorSurfaceTests() {
},
},
});
- const updatedMainRuntime = {
- ...codePrototypeRuntime,
- recentEvents: [previewFailure, delegateDecision],
- updatedAt: 7501,
- };
- professionalRuntimes = [updatedMainRuntime];
- harness.emitAgentRuntime(updatedMainRuntime);
});
await waitFor(() => {
expect(within(progress).getByText('本轮生成进度')).not.toBeNull();
expect(within(progress).queryByText(/第 5 轮/u)).toBeNull();
expect(
- within(progress).getByText('主阶段 0/1 · 计划 2/3'),
+ within(progress).getByText('任务图 3/7 · 进行中 1 · 计划 2/3'),
).not.toBeNull();
expect(within(progress).getByText('安排程序 Agent 返工')).not.toBeNull();
expect(within(progress).getByText('返工决定')).not.toBeNull();
@@ -8395,168 +8078,15 @@ export function registerProjectSupervisorSurfaceTests() {
);
});
- it('freezes a terminal game-chat stage before a delayed conversation refresh and fast next turn', async () => {
- const projectPath = '/tmp/game-chat-stage-record-fast-next-turn';
- let professionalRuntimes: AgentRuntimeState[] = [];
- const harness = createProjectSupervisorRuntimeHarness({
- projectPath,
- runtimeMapLoader: async () => professionalRuntimes,
- });
- const manifest = createGameCreationAppManifest(
- 'game-chat-stage-record-fast-next-turn',
- 'game-chat-stage-record-fast-next-turn',
- );
- manifest.tasks = manifest.tasks.map((task) =>
- task.id === 'code-prototype'
- ? { ...task, status: 'completed' as const }
- : task,
- );
- const conversationGate = createDeferred();
- const manifestGate = createDeferred();
- let delayConversationRefresh = false;
- let delayManifestCapture = false;
- let delayedConversationReads = 0;
- let delayedManifestReads = 0;
- const invoke = vi.fn(
- async (command: string, args?: Record) => {
- if (command === 'inspect_local_project_directory') {
- return {
- projectPath,
- exists: true,
- isDirectory: true,
- isGameCreatorProject: true,
- projectName: 'game-chat-stage-record-fast-next-turn',
- recentRunStatus: null,
- recentRunStopReason: null,
- };
- }
- if (command === 'init_local_game_project') {
- return {
- projectPath,
- manifestPath: `${projectPath}/.agent/manifest.json`,
- manifest,
- };
- }
- if (command === 'get_local_game_preview_status') {
- return { status: 'stopped', url: null, port: null, root: null };
- }
- if (command === 'get_local_game_manifest') {
- if (delayManifestCapture) {
- delayedManifestReads += 1;
- await manifestGate.promise;
- }
- return manifest;
- }
- if (command === 'read_local_conversation' && delayConversationRefresh) {
- delayedConversationReads += 1;
- await conversationGate.promise;
- }
- return harness.invoke(command, args);
- },
- );
- window.__TAURI__ = {
- core: { invoke },
- event: { listen: harness.listen },
- };
- render(
- React.createElement(App, {
- initialProjectPath: projectPath,
- projectSupervisorOnly: true,
- gameChatOnly: true,
- }),
- );
-
- const surface = await screen.findByLabelText('游戏创作聊天');
- const composer = within(surface).getByLabelText(
- '项目总控对话内容',
- ) as HTMLTextAreaElement;
- await waitFor(() => expect(composer.disabled).toBe(false));
- fireEvent.change(composer, { target: { value: '完成旧轮' } });
- fireEvent.click(within(surface).getByRole('button', { name: '发送' }));
- await waitFor(() => {
- expect(
- invoke.mock.calls.filter(
- ([command]) =>
- command === 'start_game_creator_supervisor_runtime_task',
- ),
- ).toHaveLength(1);
- });
- const firstStart = invoke.mock.calls.find(
- ([command]) => command === 'start_game_creator_supervisor_runtime_task',
- );
- const firstRunId = String(firstStart?.[1]?.runId ?? '');
- const main = gameChatPlayableMainRuntime({
- parentRunId: firstRunId,
- revision: 17,
- updatedAt: 1700,
- });
- professionalRuntimes = [main];
- act(() => harness.emitAgentRuntime(main));
-
- delayConversationRefresh = true;
- delayManifestCapture = true;
- act(() => {
- harness.emitRuntime(
- harness.runtimeState({
- runId: firstRunId,
- source: 'project-supervisor-game-chat',
- status: 'completed',
- phase: 'completed',
- updatedAt: 4000,
- }),
- );
- });
- await waitFor(() => {
- expect(delayedConversationReads).toBeGreaterThan(0);
- expect(delayedManifestReads).toBeGreaterThan(0);
- });
-
- fireEvent.change(composer, { target: { value: '立即开始下一轮' } });
- fireEvent.click(within(surface).getByRole('button', { name: '发送' }));
- await waitFor(() => expect(delayedManifestReads).toBeGreaterThan(1));
- expect(
- invoke.mock.calls.filter(
- ([command]) => command === 'start_game_creator_supervisor_runtime_task',
- ),
- ).toHaveLength(1);
- manifestGate.resolve(undefined);
- await waitFor(() => {
- expect(
- invoke.mock.calls.filter(
- ([command]) =>
- command === 'start_game_creator_supervisor_runtime_task',
- ),
- ).toHaveLength(2);
- });
-
- conversationGate.resolve(undefined);
- await waitFor(() => {
- expect(
- invoke.mock.calls.filter(
- ([command, args]) =>
- command === 'append_local_conversation_message' &&
- String(
- (args?.message as { content?: string } | undefined)?.content ??
- '',
- ).startsWith('【Supervisor 阶段记录】'),
- ),
- ).toHaveLength(1);
- });
- });
-
it('persists one terminal game-chat stage record and keeps it in the next round', async () => {
const projectPath = '/tmp/game-chat-stage-record';
- let professionalRuntimes: AgentRuntimeState[] = [];
- const harness = createProjectSupervisorRuntimeHarness({
- projectPath,
- runtimeMapLoader: async () => professionalRuntimes,
- });
+ const harness = createProjectSupervisorRuntimeHarness({ projectPath });
const manifest = createGameCreationAppManifest(
'game-chat-stage-record',
'game-chat-stage-record',
);
manifest.tasks = manifest.tasks.map((task) =>
- task.id === 'code-prototype'
+ isGameChatStageTask(task.id)
? { ...task, status: 'completed' as const }
: task,
);
@@ -8674,28 +8204,28 @@ export function registerProjectSupervisorSurfaceTests() {
);
const firstRunId = String(startCall?.[1]?.runId ?? '');
expect(firstRunId).not.toBe('');
- const mainRuntime = gameChatPlayableMainRuntime({
- parentRunId: firstRunId,
- revision: 9,
+ const previewPassed = gameChatRuntimeEvent({
+ runId: firstRunId,
+ eventType: 'observation',
+ summary: 'preview.validate:ok · 浏览器验证已通过',
+ detail: JSON.stringify({
+ diagnosticsCount: 0,
+ passed: true,
+ playtestPassed: true,
+ revision: 9,
+ }),
updatedAt: 9100,
});
const rework = gameChatRuntimeEvent({
- agentId: 'code-prototype',
- taskId: 'code-prototype',
- sessionId: mainRuntime.sessionId,
- runId: mainRuntime.runId,
- source: 'agent-ready-task-scheduler',
+ runId: firstRunId,
eventType: 'action',
summary: '调用工具 agent.delegate',
detail: '根据上一版试玩诊断安排程序 Agent 完成返工',
updatedAt: 9000,
});
- mainRuntime.recentEvents = [rework, ...(mainRuntime.recentEvents ?? [])];
- professionalRuntimes = [mainRuntime];
const terminalRuntime = harness.runtimeState({
runId: firstRunId,
runProfile: 'autonomous-game-build',
- source: 'project-supervisor-game-chat',
status: 'idle',
phase: 'completed',
loopIteration: 6,
@@ -8705,7 +8235,7 @@ export function registerProjectSupervisorSurfaceTests() {
{ index: 1, title: '通过试玩', status: 'completed' },
],
activePlanStepIndex: null,
- recentEvents: [],
+ recentEvents: [rework, previewPassed],
updatedAt: 9200,
});
harness.appendSupervisorMessage({
@@ -8721,7 +8251,6 @@ export function registerProjectSupervisorSurfaceTests() {
throw new Error('missing game creator runtime update listener');
}
act(() => {
- harness.emitAgentRuntime(mainRuntime);
updateHandler({
payload: {
projectPath,
@@ -8731,7 +8260,7 @@ export function registerProjectSupervisorSurfaceTests() {
phase: 'completed',
runtime: {
...harness.runtimeResult(terminalRuntime),
- recentEvents: [],
+ recentEvents: [rework, previewPassed],
},
},
});
@@ -8780,11 +8309,7 @@ export function registerProjectSupervisorSurfaceTests() {
it('defers the terminal game-chat stage record until the refreshed manifest is terminal', async () => {
const projectPath = '/tmp/game-chat-stage-record-manifest-race';
- let professionalRuntimes: AgentRuntimeState[] = [];
- const harness = createProjectSupervisorRuntimeHarness({
- projectPath,
- runtimeMapLoader: async () => professionalRuntimes,
- });
+ const harness = createProjectSupervisorRuntimeHarness({ projectPath });
const pendingManifest = createGameCreationAppManifest(
'game-chat-stage-record-manifest-race',
'game-chat-stage-record-manifest-race',
@@ -8794,9 +8319,18 @@ export function registerProjectSupervisorSurfaceTests() {
'game-chat-stage-record-manifest-race',
);
terminalManifest.tasks = terminalManifest.tasks.map((task) =>
- task.id === 'code-prototype'
+ task.id === 'preview-playtest'
? { ...task, status: 'failed' as const }
- : task,
+ : [
+ 'design-director',
+ 'art-director',
+ 'art-asset-plan',
+ 'code-director',
+ 'code-prototype',
+ 'preview-readiness',
+ ].includes(task.id)
+ ? { ...task, status: 'completed' as const }
+ : task,
);
let manifestReady = false;
const invoke = vi.fn(
@@ -8881,28 +8415,13 @@ export function registerProjectSupervisorSurfaceTests() {
);
const runId = String(startCall?.[1]?.runId ?? '');
expect(runId).not.toBe('');
- const failedMain = gameChatRuntimeState({
- agentId: 'code-prototype',
- taskId: 'code-prototype',
- sessionId: 'manifest-race-main-session',
- runId: 'manifest-race-main-run',
- source: 'agent-ready-task-scheduler',
- parentAgentId: 'project-supervisor',
- parentRunId: runId,
- status: 'failed',
- phase: 'failed',
- updatedAt: 9199,
- });
- professionalRuntimes = [failedMain];
act(() => {
- harness.emitAgentRuntime(failedMain);
harness.emitRuntime(
harness.runtimeState({
runId,
- source: 'project-supervisor-game-chat',
status: 'failed',
phase: 'failed',
- currentAction: 'code-prototype failed',
+ currentAction: 'preview-playtest failed',
recentEvents: [
gameChatRuntimeEvent({
runId,
@@ -8947,37 +8466,43 @@ export function registerProjectSupervisorSurfaceTests() {
(stageRecordAppends()[0]?.[1] as { message?: { content?: string } })
?.message?.content ?? '',
);
- expect(stageRecord).toContain('主阶段失败');
+ expect(stageRecord).toContain('6/7');
});
it('archives a terminal game-chat run restored during initial hydration exactly once', async () => {
const projectPath = '/tmp/game-chat-stage-record-initial-terminal';
- const runId = 'game-chat-stage-record-initial-terminal-run';
- const mainRuntime = gameChatPlayableMainRuntime({
- parentRunId: runId,
- revision: 11,
- updatedAt: 9100,
- });
- const harness = createProjectSupervisorRuntimeHarness({
- projectPath,
- runtimeMapLoader: async () => [mainRuntime],
- });
+ const harness = createProjectSupervisorRuntimeHarness({ projectPath });
const manifest = createGameCreationAppManifest(
'game-chat-stage-record-initial-terminal',
'game-chat-stage-record-initial-terminal',
);
manifest.tasks = manifest.tasks.map((task) =>
- task.id === 'code-prototype'
+ isGameChatStageTask(task.id)
? { ...task, status: 'completed' as const }
: task,
);
+ const runId = 'game-chat-stage-record-initial-terminal-run';
const terminalRuntime = harness.runtimeState({
runId,
- source: 'project-supervisor-game-chat',
status: 'completed',
phase: 'completed',
currentAction: 'preview complete',
- recentEvents: [],
+ recentEvents: [
+ gameChatRuntimeEvent({
+ runId,
+ eventType: 'observation',
+ status: 'completed',
+ phase: 'tool-observation',
+ summary: 'preview.validate:ok · 试玩验证已通过',
+ detail: JSON.stringify({
+ diagnosticsCount: 0,
+ passed: true,
+ playtestPassed: true,
+ revision: 11,
+ }),
+ updatedAt: 9100,
+ }),
+ ],
updatedAt: 9200,
});
const invoke = vi.fn(
@@ -9061,11 +8586,6 @@ export function registerProjectSupervisorSurfaceTests() {
it('does not duplicate a historical terminal game-chat stage record during restart hydration', async () => {
const projectPath = '/tmp/game-chat-stage-record-restart-hydration';
const runId = 'game-chat-stage-record-restart-run';
- const mainRuntime = gameChatPlayableMainRuntime({
- parentRunId: runId,
- revision: 12,
- updatedAt: 9100,
- });
const terminalRuntime = gameChatRuntimeState({
sessionId: 'supervisor-session-active',
runId,
@@ -9079,13 +8599,13 @@ export function registerProjectSupervisorSurfaceTests() {
'game-chat-stage-record-restart-hydration',
);
manifest.tasks = manifest.tasks.map((task) =>
- task.id === 'code-prototype'
+ isGameChatStageTask(task.id)
? { ...task, status: 'completed' as const }
: task,
);
const progress = buildGameChatProgressEvidence(
terminalRuntime,
- { 'code-prototype': mainRuntime },
+ {},
manifest,
);
if (!progress) {
@@ -9099,7 +8619,6 @@ export function registerProjectSupervisorSurfaceTests() {
const harness = createProjectSupervisorRuntimeHarness({
projectPath,
initialRuntime: terminalRuntime,
- runtimeMapLoader: async () => [mainRuntime],
projectMessages: [
{
role: 'assistant',
@@ -9181,7 +8700,6 @@ export function registerProjectSupervisorSurfaceTests() {
harness.runtimeState({
sessionId: terminalRuntime.sessionId,
runId,
- source: 'project-supervisor-game-chat',
status: 'completed',
phase: 'completed',
currentAction: 'preview complete',
@@ -9305,7 +8823,7 @@ export function registerProjectSupervisorSurfaceTests() {
playableVersionReady = true;
harness.setProjectRevision(1);
- const firstPlayableRevision = gameChatPlayableMainRuntime({
+ const firstPlayableRevision = gameChatPreviewPlaytestRuntime({
parentRunId: acceptedRunId,
revision: 1,
updatedAt: 3900,
@@ -9315,7 +8833,6 @@ export function registerProjectSupervisorSurfaceTests() {
harness.runtimeState({
runId: acceptedRunId,
runProfile: 'autonomous-game-build',
- source: 'project-supervisor-game-chat',
status: 'running',
phase: 'verification',
currentTask: '验证首个可玩版本',
@@ -9364,13 +8881,12 @@ export function registerProjectSupervisorSurfaceTests() {
initialRuntime: {
sessionId: 'supervisor-session-active',
runId,
- source: 'project-supervisor-game-chat',
status: 'completed',
phase: 'completed',
updatedAt: 9200,
},
runtimeMapLoader: async () => [
- gameChatPlayableMainRuntime({
+ gameChatPreviewPlaytestRuntime({
parentRunId: runId,
revision,
updatedAt: 9100,
@@ -9579,7 +9095,7 @@ export function registerProjectSupervisorSurfaceTests() {
expect(acceptedRunIds).toHaveLength(1);
});
harness.setProjectRevision(1);
- const firstPreviewPassed = gameChatPlayableMainRuntime({
+ const firstPreviewPassed = gameChatPreviewPlaytestRuntime({
parentRunId: acceptedRunIds[0],
revision: 1,
updatedAt: 3900,
@@ -9589,7 +9105,6 @@ export function registerProjectSupervisorSurfaceTests() {
harness.runtimeState({
runId: acceptedRunIds[0],
runProfile: 'autonomous-game-build',
- source: 'project-supervisor-game-chat',
status: 'running',
phase: 'verification',
currentTask: '验证第一版可玩原型',
@@ -9619,7 +9134,6 @@ export function registerProjectSupervisorSurfaceTests() {
harness.runtimeState({
runId: acceptedRunIds[0],
runProfile: 'autonomous-game-build',
- source: 'project-supervisor-game-chat',
status: 'completed',
phase: 'completed',
currentTask: '第一版已经完成',
@@ -9656,7 +9170,7 @@ export function registerProjectSupervisorSurfaceTests() {
expect(firstFrame.src).toBe(firstFrameUrl);
harness.setProjectRevision(2);
- const secondPreviewPassed = gameChatPlayableMainRuntime({
+ const secondPreviewPassed = gameChatPreviewPlaytestRuntime({
parentRunId: acceptedRunIds[1],
revision: 2,
updatedAt: 7900,
@@ -9666,7 +9180,6 @@ export function registerProjectSupervisorSurfaceTests() {
harness.runtimeState({
runId: acceptedRunIds[1],
runProfile: 'autonomous-game-build',
- source: 'project-supervisor-game-chat',
status: 'completed',
phase: 'completed',
currentTask: '第二版已经完成',
@@ -9826,7 +9339,7 @@ export function registerProjectSupervisorSurfaceTests() {
);
await act(async () => {
driver.harness.emitAgentRuntime(
- gameChatPlayableMainRuntime({
+ gameChatPreviewPlaytestRuntime({
parentRunId: driver.parentRunId,
revision: 1,
updatedAt: 1000,
@@ -10018,7 +9531,7 @@ export function registerProjectSupervisorSurfaceTests() {
expect(acceptedRunId).toMatch(/^project-supervisor-task-/);
});
harness.setProjectRevision(3);
- const playableRevision = gameChatPlayableMainRuntime({
+ const playableRevision = gameChatPreviewPlaytestRuntime({
parentRunId: acceptedRunId,
revision: 3,
updatedAt: 3900,
@@ -10028,7 +9541,6 @@ export function registerProjectSupervisorSurfaceTests() {
harness.runtimeState({
runId: acceptedRunId,
runProfile: 'autonomous-game-build',
- source: 'project-supervisor-game-chat',
status: 'running',
phase: 'verification',
updatedAt: 4000,
@@ -10063,7 +9575,6 @@ export function registerProjectSupervisorSurfaceTests() {
);
const previousIdleRuntime = harness.runtimeState({
runId: 'game-chat-previous-idle',
- source: 'project-supervisor-game-chat',
status: 'idle',
phase: 'idle',
updatedAt: 1000,
@@ -10129,7 +9640,6 @@ export function registerProjectSupervisorSurfaceTests() {
const state = harness.runtimeState({
runId: acceptedRunId,
runProfile: 'autonomous-game-build',
- source: 'project-supervisor-game-chat',
status: allowCompletion ? 'completed' : 'running',
phase: allowCompletion ? 'completed' : 'execution',
currentTask: '生成 accepted run 首版游戏',
@@ -10203,7 +9713,7 @@ export function registerProjectSupervisorSurfaceTests() {
harness.setProjectRevision(4);
await act(async () => {
harness.emitAgentRuntime(
- gameChatPlayableMainRuntime({
+ gameChatPreviewPlaytestRuntime({
parentRunId: acceptedRunId,
revision: 4,
updatedAt: 4100,
@@ -10329,7 +9839,7 @@ export function registerProjectSupervisorSurfaceTests() {
expect(acceptedRunId).toMatch(/^project-supervisor-task-/);
});
harness.setProjectRevision(5);
- const deniedPlayableRevision = gameChatPlayableMainRuntime({
+ const deniedPlayableRevision = gameChatPreviewPlaytestRuntime({
parentRunId: acceptedRunId,
revision: 5,
updatedAt: 3900,
@@ -10339,7 +9849,6 @@ export function registerProjectSupervisorSurfaceTests() {
harness.runtimeState({
runId: acceptedRunId,
runProfile: 'autonomous-game-build',
- source: 'project-supervisor-game-chat',
status: 'running',
phase: 'verification',
updatedAt: 4000,
@@ -11316,7 +10825,7 @@ export function registerProjectSupervisorSurfaceTests() {
const surface = await screen.findByLabelText('陶泥儿项目对话');
expect(invoke).toHaveBeenCalledWith('import_local_godot_project', {
projectPath,
- projectId: expect.stringMatching(/^local-project-/),
+ projectId: 'local-project-draft',
name: 'existing-godot-project',
});
expect(
diff --git a/deploy/container/README.md b/deploy/container/README.md
index 2bc16efd6..1de8a3489 100644
--- a/deploy/container/README.md
+++ b/deploy/container/README.md
@@ -60,11 +60,11 @@ Linux Docker Engine 若要从宿主机 CLI 连到容器内服务,直接用 `ht
### Jenkins 预览 secrets 镜像边界
-Jenkins 分支预览构建固定从宿主 `/data/jenkins/preview-secrets/.env.local` 与 `/data/jenkins/preview-secrets/.env.secrets.local` 读取运行时配置。目录由 Jenkins 运行账号所有且权限为 `0700`,两个文件由同一账号所有且权限为 `0600`;构建入口对缺失、链接、非普通文件、owner 不匹配和过宽权限均失败关闭。两个文件都包含敏感配置,不要把真实值写入本 README、仓库示例或 Jenkins 参数。
+Jenkins 分支预览构建固定从宿主 `/data/jenkins/preview-secrets/.env.secrets.local` 读取 secrets。目录由 Jenkins 运行账号所有且权限为 `0700`,文件由同一账号所有且权限为 `0600`;构建入口对缺失、链接、非普通文件、owner 不匹配和过宽权限均失败关闭。不要把真实值写入本 README、仓库示例或 Jenkins 参数。
-两个文件都不复制到源码 checkout 和 Docker build context,而是分别以 BuildKit `secret` mount 只提供给 `api-runtime` stage。构建会把它们安装到 API 运行镜像的 `/srv/genarrative/.env.local` 与 `/srv/genarrative/.env.secrets.local`,owner 为 `genarrative`、权限为 `0400`。Web builder、`nginx-runtime`、SpacetimeDB 和其它运行镜像不得获得这些 mount 或目标文件;构建日志和 artifact 也不得回显或保存文件内容。容器的显式运行环境变量优先于这两个内置文件,可按预览实例覆盖其中的值。
+该文件不复制到源码 checkout 和 Docker build context,而是以 BuildKit `secret` mount 只提供给 `api-runtime` stage。构建会把它安装到 API 运行镜像的 `/srv/genarrative/.env.secrets.local`,owner 为 `genarrative`、权限为 `0400`。Web builder、`nginx-runtime`、SpacetimeDB 和其它运行镜像不得获得该 mount 或目标文件;构建日志和 artifact 也不得回显或保存文件内容。容器的显式运行环境变量优先于该内置文件,可按预览实例覆盖其中的值。
-修改任一宿主固定文件后必须重新构建并替换 API 与 worker 镜像;重启旧容器不会读取宿主新内容。这个镜像不是可公开分发的无密钥产物:镜像持有者可以提取 `/srv/genarrative/.env.local` 与 `/srv/genarrative/.env.secrets.local`。只允许在当前受信任内网 Docker 主机使用,禁止 push 或 `docker save`、artifact 导出到跨信任边界的 registry、主机或存储。
+修改宿主固定文件后必须重新构建并替换 API 镜像;重启旧容器不会读取宿主新内容。这个镜像不是可公开分发的无密钥产物:镜像持有者可以提取 `/srv/genarrative/.env.secrets.local`。只允许在当前受信任内网 Docker 主机使用,禁止 push 或 `docker save`、artifact 导出到跨信任边界的 registry、主机或存储。
### Gitea CI 预构建 Job 镜像
diff --git a/deploy/container/api-server.Dockerfile b/deploy/container/api-server.Dockerfile
index e61d73eae..d97732bce 100644
--- a/deploy/container/api-server.Dockerfile
+++ b/deploy/container/api-server.Dockerfile
@@ -24,20 +24,12 @@ RUN mkdir -p /var/lib/genarrative/auth /var/lib/genarrative/tracking-outbox /var
chown -R genarrative:genarrative /srv/genarrative /var/lib/genarrative
ARG GENARRATIVE_PREVIEW_SECRETS_SHA256=
-ARG GENARRATIVE_PREVIEW_ENV_LOCAL_SHA256=
RUN --mount=type=secret,id=genarrative_preview_secrets,required=false \
- --mount=type=secret,id=genarrative_preview_env_local,required=false \
if [ -n "${GENARRATIVE_PREVIEW_SECRETS_SHA256}" ]; then \
test -f /run/secrets/genarrative_preview_secrets; \
test "$(sha256sum /run/secrets/genarrative_preview_secrets | cut -d ' ' -f 1)" = "${GENARRATIVE_PREVIEW_SECRETS_SHA256}"; \
install -o genarrative -g genarrative -m 0400 \
/run/secrets/genarrative_preview_secrets /srv/genarrative/.env.secrets.local; \
- fi; \
- if [ -n "${GENARRATIVE_PREVIEW_ENV_LOCAL_SHA256}" ]; then \
- test -f /run/secrets/genarrative_preview_env_local; \
- test "$(sha256sum /run/secrets/genarrative_preview_env_local | cut -d ' ' -f 1)" = "${GENARRATIVE_PREVIEW_ENV_LOCAL_SHA256}"; \
- install -o genarrative -g genarrative -m 0400 \
- /run/secrets/genarrative_preview_env_local /srv/genarrative/.env.local; \
fi
USER genarrative
diff --git a/docs/prd/【AI游戏创作】项目开发工作台PRD-2026-07-20.md b/docs/prd/【AI游戏创作】项目开发工作台PRD-2026-07-20.md
index 788cf40be..2258a1341 100644
--- a/docs/prd/【AI游戏创作】项目开发工作台PRD-2026-07-20.md
+++ b/docs/prd/【AI游戏创作】项目开发工作台PRD-2026-07-20.md
@@ -70,6 +70,7 @@
- 运行视窗必须占满中央工作区为游戏保留的可用区域。loopback 预览页通过客户端本地 preview server 注入的只读尺寸桥上报文档实际宽高;宿主只接受当前 iframe、当前 loopback origin 的固定版本消息,并将完整游戏文档等比缩放、居中放入视窗。iframe 首次适配后发生的真实内容增高或缩短仍必须被接受;仅浏览上下文宽高回灌或内容宽高未变化时保持当前状态,不触发重复渲染。
- 窗口或中央区域尺寸变化后必须重新测量和适配;内容已经放得下时保持 `1:1`,不得无故放大。游戏文档宽高超过视窗时缩小整体画面,不显示 iframe 横向或纵向滚动条,也不得用单纯裁切替代完整展示。尺寸桥以根布局 `ResizeObserver` 为主,并在页面可见时每 `500ms` 至多探测 `512` 个元素作为绝对定位溢出的低频兜底;探测截断时不得用部分样本下调尺寸,viewport 耦合的 `100vh / 100% / bottom / right` 布局也不得形成自反馈。相同测量结果去重,不监听整页属性、文本或子节点突变;桥不读取项目正文、不修改 manifest、游戏文件或运行业务状态。桥脚本只能注入到真实 HTML 标签上下文,不能把脚本、样式、模板或注释中的 `