收口AGC单窗口启动

取消开发态自动打开 Agent 聊天窗口

统一客户端可见标题为陶泥儿

同步启动守卫、测试与技术文档
This commit is contained in:
kdletters
2026-08-17 14:32:13 +08:00
parent 08bca001a2
commit 5ea453d5d1
14 changed files with 52 additions and 49 deletions
+1 -1
View File
@@ -4,7 +4,7 @@
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="icon" href="data:," />
<title>AI 游戏创作</title>
<title>陶泥儿</title>
</head>
<body>
<div id="root"></div>
@@ -312,7 +312,9 @@ function assertContractRecordsMatch(label, leftRecords, rightRecords) {
function parseAppInvokeCommandNames(source) {
return Array.from(
source.matchAll(/(?:invoke|directInvoke)(?:<[^>]*>)?\(\s*['"]([a-z0-9_]+)['"]/g),
source.matchAll(
/(?:invoke|directInvoke)(?:<[^>]*>)?\(\s*['"]([a-z0-9_]+)['"]/g,
),
([, command]) => command,
);
}
@@ -1164,6 +1166,7 @@ if (
!Array.isArray(tauriConfig.app?.windows) ||
tauriConfig.app.windows.length !== 1 ||
tauriConfig.app.windows[0]?.label !== 'client' ||
tauriConfig.app.windows[0]?.title !== '陶泥儿' ||
tauriConfig.app.windows[0]?.url !== 'index.html'
) {
throw new Error(
@@ -1540,7 +1543,7 @@ if (
for (const snippet of [
'resolveAgcDevEndpoint',
'withAgcDevEndpointEnv',
"response.body.includes('<title>AI 游戏创作</title>')",
"response.body.includes('<title>陶泥儿</title>')",
'function isPortListening()',
'cannot be safely reused',
'non-HTTP or unrecognized server',
@@ -1667,10 +1670,6 @@ if (
for (const snippet of [
'import.meta.env.DEV',
'#[cfg(all(debug_assertions, not(test)))]',
'open_developer_window(app.handle())?',
'tauri::WebviewWindowBuilder::new(app, "developer", developer_window_url())',
'index.html?agent-chat',
'supervisorChatMode',
'supervisorChatOnly',
'open_project_supervisor_chat_window',
@@ -1683,6 +1682,12 @@ for (const snippet of [
}
}
if (tauriHandlerSource.includes('open_developer_window(app.handle())?')) {
throw new Error(
'AI game creator normal startup must not automatically open the developer window',
);
}
const smokeAgentRunSource = fs.readFileSync(
new URL('./smoke-agent-run-local-provider.mjs', import.meta.url),
'utf8',
@@ -47,7 +47,7 @@ function isAiGameCreatorServer(response) {
response &&
response.statusCode >= 200 &&
response.statusCode < 500 &&
response.body.includes('<title>AI 游戏创作</title>') &&
response.body.includes('<title>陶泥儿</title>') &&
response.body.includes('/src/main.tsx')
);
}
@@ -158,7 +158,7 @@ function isAiGameCreatorServer(response) {
response &&
response.statusCode >= 200 &&
response.statusCode < 500 &&
response.body.includes('<title>AI 游戏创作</title>') &&
response.body.includes('<title>陶泥儿</title>') &&
response.body.includes('/src/main.tsx')
);
}
@@ -2174,10 +2174,6 @@ fn main() {
if let Some(path) = setup_log.as_deref() {
let _ = append_bounded_diagnostic_line(path, "startup.runner.start.complete");
}
#[cfg(all(debug_assertions, not(test)))]
if game_chat_launch.is_none() {
open_developer_window(app.handle())?;
}
if let Some(path) = setup_log.as_deref() {
let _ = append_bounded_diagnostic_line(path, "startup.setup.complete");
}
@@ -2936,11 +2936,6 @@ fn launcher_window_uses_launcher_route() {
assert_eq!(launcher_window_url().to_string(), "index.html?launcher");
}
#[test]
fn developer_window_uses_agent_chat_route() {
assert_eq!(developer_window_url().to_string(), "index.html?agent-chat");
}
#[test]
fn workspace_window_url_carries_encoded_project_path() {
assert_eq!(
@@ -469,10 +469,6 @@ pub(crate) fn launcher_window_url() -> tauri::WebviewUrl {
tauri::WebviewUrl::App(PathBuf::from("index.html?launcher"))
}
pub(crate) fn developer_window_url() -> tauri::WebviewUrl {
tauri::WebviewUrl::App(PathBuf::from("index.html?agent-chat"))
}
pub(crate) fn supervisor_chat_window_url(project_path: &str) -> tauri::WebviewUrl {
tauri::WebviewUrl::App(PathBuf::from(format!(
"index.html?supervisor-chat&projectPath={}",
@@ -559,7 +555,7 @@ pub(crate) fn open_game_creator_workspace_window(
existing.close().map_err(|error| error.to_string())?;
}
tauri::WebviewWindowBuilder::new(&app, "main", workspace_window_url(project_path))
.title("AI 游戏创作")
.title("陶泥儿")
.inner_size(1180.0, 820.0)
.min_inner_size(760.0, 560.0)
.build()
@@ -580,7 +576,7 @@ pub(crate) fn open_game_creator_launcher_window(
existing.set_focus().map_err(|error| error.to_string())?;
} else {
tauri::WebviewWindowBuilder::new(&app, "launcher", launcher_window_url())
.title("AI 游戏创作")
.title("陶泥儿")
.inner_size(820.0, 640.0)
.min_inner_size(720.0, 520.0)
.build()
@@ -637,18 +633,3 @@ pub(crate) fn open_project_supervisor_chat_window(
Ok(())
}
}
#[cfg(all(debug_assertions, not(test)))]
pub(crate) fn open_developer_window(app: &tauri::AppHandle) -> Result<(), String> {
if let Some(existing) = app.get_webview_window("developer") {
existing.set_focus().map_err(|error| error.to_string())?;
return Ok(());
}
tauri::WebviewWindowBuilder::new(app, "developer", developer_window_url())
.title("AI 游戏创作开发")
.inner_size(1040.0, 760.0)
.min_inner_size(780.0, 560.0)
.build()
.map_err(|error| error.to_string())?;
Ok(())
}
@@ -14,7 +14,7 @@
"windows": [
{
"label": "client",
"title": "AI 游戏创作",
"title": "陶泥儿",
"url": "index.html",
"width": 1280,
"height": 800,
@@ -282,7 +282,7 @@ export function ProjectWorkspaceChatPane({
<section className="chat-pane" aria-label="聊天">
<header className="chat-header">
<div className="chat-header-title">
<h1>AI </h1>
<h1></h1>
<span>{devMode ? '开发模式' : currentProjectTitle}</span>
<small>run: {agentRunStatus}</small>
<small>preview: {previewStatus}</small>
@@ -1382,7 +1382,7 @@ export function registerSupervisorRuntimeTests() {
'run: 实时状态不可用:core:event:allow-listen denied',
),
).not.toBeNull();
expect(screen.getByText('AI 游戏创作')).not.toBeNull();
expect(screen.getByText('陶泥儿')).not.toBeNull();
expect(listen).toHaveBeenCalledWith(
'game-creator-agent-progress',
expect.any(Function),
@@ -263,7 +263,7 @@ describe('AI 游戏创作动态端口启动前预检', () => {
};
const agcHtml = {
statusCode: 200,
body: '<html><title>AI 游戏创作</title><script src="/src/main.tsx"></script></html>',
body: '<html><title>陶泥儿</title><script src="/src/main.tsx"></script></html>',
};
test('旧 Vite marker 指向其它 API 时在启动后端前失败', async () => {
@@ -0,0 +1,20 @@
# AGC 开发态单窗口启动收口计划
日期:`2026-08-17`
## 目标
`npm run agc` 启动时只打开标题为“陶泥儿”的正式客户端窗口,不再自动额外打开 Agent 聊天开发窗口。
## 范围与边界
- 删除 Tauri setup 中仅 debug 生效的自动 developer 窗口调用,以及已无调用方的 developer 窗口构造代码与专属路由测试。
- 保留普通 `client` 窗口、`--game-chat` 独立入口和显式 `supervisor-chat` 开发调试入口。
- 不删除前端 `?agent-chat` 调试页面;它不再是 `npm run agc` 的自动入口。
- 同步原生壳静态门禁、技术方案和长期决策记录,防止自动双窗口回归。
## 验收
1. Tauri 定向 Rust 测试和前端类型检查通过。
2. 配置门禁、编码检查和差异检查通过。
3. 实际运行 `npm run agc` 后,仅存在标题为“陶泥儿”的客户端窗口,不存在 Agent 聊天开发窗口。
@@ -14205,3 +14205,9 @@
- 直连 Runtime 的美术生成、代码生成和版本登记仍保持单次原子回合,不拆回 Supervisor 或 harness。为避免用户等待数分钟只看到“思考中”,Runtime 在每个用户可理解的安全阶段复用 `game-creator-agent-progress`:需求接收、陶泥儿美术包检查、规范图、背景图、图集及切片、代码生成、版本登记和预览刷新;普通直连工作台仅接受当前项目的事件并显示 `message`,不把内部执行协议、路径或凭据暴露到聊天。
- 资源画布不能把 `IntersectionObserver` 作为首屏唯一预览触发器。工作台进入资源管理时主动预取最多 12 个可预览的非音频、非版本、非占位资源;保留现有按可见性懒加载、队列优先级、并发上限、缓存上限、权限失败提示与详情/播放升级机制。这样不会一次读取大项目全部资源,但陶泥儿标准三张 PNG 和游戏代码能在正常首屏项目中无需打开详情直接显示。
- 验收:定向 hook 测试证明预取会直接调用本地 preview command 并进入 loadedAppSurface 证明 direct 提交即时显示接收文案并消费阶段事件;2026-08-17 在当前 checkout 的普通 AGC 客户端打开 `gameagent-3291e57c`,未打开任何资源详情即观察到三份游戏代码摘要和三张陶泥儿 PNG(规范图、16:9 背景、核心图集)缩略图。
## 2026-08-17 AGC 开发态默认单窗口启动
- 决策:`npm run agc` 及普通 debug 启动只打开标题为“陶泥儿”的 `client` 客户端窗口,不再由 Tauri setup 自动创建 Agent 聊天开发窗口。普通创作、直连 Codex Runtime 和项目工作台行为均不受影响。
- 保留:`?agent-chat` 仅作为显式前端调试路由;`supervisor-chat``--game-chat` 继续是独立的显式开发验收入口,不会随着普通启动同时弹出。
- 守卫:原生壳配置检查必须拒绝恢复 `open_developer_window(app.handle())?` 自动调用;真实开发 smoke 以普通客户端单窗口为准。
@@ -27,11 +27,11 @@ Supervisor 认领该回执后,由父 run 自己为每个原 delivery 逐一创
## 目标
在 Genarrative 内建设独立桌面 App:普通用户通过项目开发工作台中的陶泥儿对话、资源画布、运行状态和确认操作,让平台生成保存在本地的可运行 Web 游戏原型,并通过本地 HTTP server 预览;主窗口提供运行时配置入口,用于保存发布版 AppData / Tauri 配置目录里的 LLM 配置及受控开发者 External Editor 配置。普通客户素材画布使用平台登录态调用内部编辑器 API,不展示或要求填写画板 Base URL / API Key。任务明细、原始文件、命令日志和专业 Agent 调试控制仍放到开发构建的独立开发窗口。v1 的生成闭环仍以 Web 小游戏为主,同时允许用户打开已有 Godot 项目:用户选择的目录始终作为工作区根,`.agent/`、Session、Runtime、文件工具和外围资料都留在该根;客户端检查根目录及一层直接子目录中的普通文件 `project.godot`,将唯一命中的实际目录以工作区相对 `godotProjectRoot` 记录到 manifest。Agent 使用标准运行档继续修改,不创建 `game/``assets/``memory/``exports/` 平行目录;本期不扩展 Unity、Godot 内嵌预览、云同步或插件市场。
在 Genarrative 内建设独立桌面 App:普通用户通过项目开发工作台中的陶泥儿对话、资源画布、运行状态和确认操作,让平台生成保存在本地的可运行 Web 游戏原型,并通过本地 HTTP server 预览;主窗口提供运行时配置入口,用于保存发布版 AppData / Tauri 配置目录里的 LLM 配置及受控开发者 External Editor 配置。普通客户素材画布使用平台登录态调用内部编辑器 API,不展示或要求填写画板 Base URL / API Key。任务明细、原始文件、命令日志和专业 Agent 调试控制只通过显式开发调试入口查看,不随普通客户端启动额外打开窗口。v1 的生成闭环仍以 Web 小游戏为主,同时允许用户打开已有 Godot 项目:用户选择的目录始终作为工作区根,`.agent/`、Session、Runtime、文件工具和外围资料都留在该根;客户端检查根目录及一层直接子目录中的普通文件 `project.godot`,将唯一命中的实际目录以工作区相对 `godotProjectRoot` 记录到 manifest。Agent 使用标准运行档继续修改,不创建 `game/``assets/``memory/``exports/` 平行目录;本期不扩展 Unity、Godot 内嵌预览、云同步或插件市场。
## 技术选择
- 桌面壳:新建 `apps/ai-game-creator-shell`,与现有 `apps/desktop-shell` 分离,避免把游戏创作本地能力塞进主站宿主壳;启动时先检查平台登录态,未登录只展示登录页,登录后进入单窗口客户端首页;正式用户窗口常驻左侧栏和顶部栏,并在首页、项目组、指南 / 反馈和项目开发页之间切换。发布配置只登记 `client` 用户窗口;debug 构建可由 Tauri setup 额外打开 `developer` 开发窗口,当前开发窗口路由到 `index.html?agent-chat` 承载单 Agent 对话,后续再扩展任务、文件、预览和日志面板
- 桌面壳:新建 `apps/ai-game-creator-shell`,与现有 `apps/desktop-shell` 分离,避免把游戏创作本地能力塞进主站宿主壳;启动时先检查平台登录态,未登录只展示登录页,登录后进入单窗口客户端首页;正式用户窗口常驻左侧栏和顶部栏,并在首页、项目组、指南 / 反馈和项目开发页之间切换。发布和 debug 启动都只登记并打开 `client` 用户窗口;`index.html?agent-chat` 仅保留为显式前端调试路由,不是 Tauri 自动启动入口
- 平台后端:继续使用 `server-rs + Axum + SpacetimeDB`;本地开发启动独立客户端时,`agc` / Tauri dev 会先启动或复用配套 SpacetimeDB 与 `api-server`,再启动固定端口 Vite,并通过 `/api` 代理访问实际后端端口。
- 本地能力:使用 Tauri Rust command;正式用户 App 在项目运行工作台内承载 `127.0.0.1` 本地 HTTP preview,不再调用系统外部浏览器。运行容器只接受当前授权项目由 `PreviewRegistry` 返回的 loopback URLrelease / dev CSP 都只为 `http://127.0.0.1:*` 开放 `frame-src`,并使用受限 iframe sandbox 隔离游戏脚本;远程 URL、`file://` 和任意手填地址均不得进入该容器。
- Agent Runtime:扩展 `server-rs/crates/platform-agent`,不引入 LangChain、AutoGen、Microsoft Agent Framework 或 OpenAI Agents SDK sidecar 作为核心。
@@ -43,7 +43,7 @@ Supervisor 认领该回执后,由父 run 自己为每个原 delivery 逐一创
## 开发态 Project Supervisor 纯聊天独立窗口
- 入口边界:当前仅 Tauri dev 提供独立窗口,路由为 `index.html?supervisor-chat&projectPath=...`,其中 `projectPath` 传入 URL 编码后的项目绝对路径。窗口从现有 `index.html?agent-chat` 开发入口打开,不增加正式用户入口;重复打开同一项目只恢复并聚焦原窗口,切换项目时在同一窗口导航。
- 入口边界:当前仅 Tauri dev 提供独立窗口,路由为 `index.html?supervisor-chat&projectPath=...`,其中 `projectPath` 传入 URL 编码后的项目绝对路径。窗口仅通过显式开发调试动作打开,不增加正式用户入口,也不随 `npm run agc` 自动弹出;重复打开同一项目只恢复并聚焦原窗口,切换项目时在同一窗口导航。
- Runtime 边界:窗口固定对话 Agent 为 `project-supervisor`,新 Run 使用 `standard` profile,复用现有 active Session、External Runner、Agent Runtime、AppData 配置和持久 conversation;不新建平行会话库、Runner 或配置存储,也不把该测试入口隐式切成自动修改项目的自主构建模式。
- 界面边界:只显示持久消息区、输入框、必要的等待 / 错误状态、工具确认 / 用户追问卡片和设置入口;不显示 Agent picker、Session 面板、Goal 面板、完整 Runtime 面板或专业 Agent 协作栏。会话历史仍绑定 `project-supervisor` 的 active Session 持久化,不因隐藏 Session 控制面而变为临时聊天;`supervisor-chat` 窗口必须具备只读 Tauri event listen / unlisten capability,以实时接收 Runtime 更新。
- 产品边界:正式用户 `client` 窗口、登录后首页和项目开发流程保持不变,不暴露该开发验证面。
@@ -266,7 +266,7 @@ Agent Runtime 负责:
## Agent 能力清单
- 用户能力:项目开发工作台、陶泥儿聊天、上传文件、manifest 资源视图、客户端内运行表现层和专业 Agent 紧凑状态;正式用户窗口仍不展示原始任务 / 文件 / 日志、开发预览调试面板、能力清单或开发专用单 Agent 聊天入口。
- 开发窗口能力:debug 构建额外打开 `developer` 窗口,走 `index.html?agent-chat`;开发者可选择 Agent、授权本地项目路径,并通过 `read_local_conversation` / `append_local_conversation_message` 读写 `.agent/conversations/agents/<agentId>.jsonl`,通过 `agentLlm.<agentId>` 调用该 Agent 的独立 LLM 路由做真实对话,用于单独调试某个 Agent 的长期对话上下文。这里的 `<agentId>` 以 manifest taskId 为规范值,旧 `group-role` 别名只作为兼容输入映射到 taskId。
- 开发调试能力:`index.html?agent-chat` 可作为显式前端调试路由;开发者可选择 Agent、授权本地项目路径,并通过 `read_local_conversation` / `append_local_conversation_message` 读写 `.agent/conversations/agents/<agentId>.jsonl`,通过 `agentLlm.<agentId>` 调用该 Agent 的独立 LLM 路由做真实对话,用于单独调试某个 Agent 的长期对话上下文。这里的 `<agentId>` 以 manifest taskId 为规范值,旧 `group-role` 别名只作为兼容输入映射到 taskId。
- 命令能力:内置命令调用、权限 gate、执行日志;v1 只允许白名单受限命令,不执行任意 shell。
- 编排能力:任务拆分、任务图依赖、专业组调度、多智能体协作;Runtime V1 会为单 Agent 对话和生成 loop 中的角色 brief 写入独立 runtime state / event,先解决“每个 Agent 正在做什么、跑到哪一步、最近一次 task/run 是什么”的可观测性。
- 历史边界说明:下一条“后台任务能力”保留 V1.1 前的进程内演进记录,其中 App 内 tokio task、进程内 drain、旧工具箱和“不是独立 OS 进程”的描述均已失效;当前执行边界以上文独立 Runner 说明为准。
@@ -832,8 +832,8 @@ game-project/
- 开发模式可执行 `canvas.asset_import`,将项目内已有文件按画板来源导入 manifest;也可执行 `canvas.export_import`,把现有画板素材导出 ZIP 回流为本地项目资产。`game.generate_draft` 在配置 `editorApi.apiKey` 时会自动复用 External Editor API 的图片生成接口生成首版美术素材,不新增平行资产模型。
- 普通模式已在登录后的单窗口项目开发页渲染 GameAgent 工作台、`project-supervisor` 主聊天和专业 Agent 协作只读状态,不提供专业 Agent / child 单 Agent 对话;首页首条需求直接投递 active Supervisor Session,全新项目没有该 Session 时先通过既有 Session 命令创建并设为 active,已有项目恢复该 Session 与历史后继续交互。Tauri 主窗口承载当前授权项目的 loopback 游戏画面,但不承载开发 Agent picker、调度工具台或任意地址预览调试面板。
- 聊天生成草案后会尝试启动只读 `127.0.0.1:<port>` 静态 HTTP server,把预览地址回写客户端项目运行工作台并切换到运行视图;不再调用系统外部浏览器。
- 开发模式仅在 Vite dev 环境响应 `?dev``#dev`Tauri dev 可额外打开 `developer` 窗口显示专业组、本地项目、预览 iframe 和内置命令日志;正式构建忽略 dev 参数,release 配置只登记一个用户窗口,登录后同窗口进入首页并在项目组 / 项目开发页之间切换。
- `check:native-shells` 会运行 `ai-game-creator-shell:check``ai-game-creator-shell:build -- --no-bundle`,并静态检查 release 只登记 `client / index.html` 这一个用户窗口、开发窗口只在 debug 下打开,开发面板必须挂在 `devMode` 分支内,正式用户 App 的运行容器只接受 `http://127.0.0.1:*`release / dev CSP 都只为该 loopback origin 开放 `frame-src`,Tauri 预览激活命令不得调用 opener,用户主流程不得调用旧工作区窗口切换 command。
- 开发模式仅在 Vite dev 环境响应 `?dev``#dev``npm run agc` 与 release 都只打开 `client` 用户窗口,显式 `?agent-chat``supervisor-chat` 调试入口不随启动自动弹出。正式构建忽略 dev 参数,release 配置只登记一个用户窗口,登录后同窗口进入首页并在项目组 / 项目开发页之间切换。
- `check:native-shells` 会运行 `ai-game-creator-shell:check``ai-game-creator-shell:build -- --no-bundle`,并静态检查 release 与 debug 启动都只登记 `client / index.html` 这一个默认窗口、禁止 Tauri setup 自动打开 developer 窗口、开发面板必须挂在 `devMode` 分支内,正式用户 App 的运行容器只接受 `http://127.0.0.1:*`release / dev CSP 都只为该 loopback origin 开放 `frame-src`,Tauri 预览激活命令不得调用 opener,用户主流程不得调用旧工作区窗口切换 command。
- 共享契约提供 `GAME_CREATION_AGENT_CAPABILITIES` 和内置命令权限枚举;开发模式会展示能力列表。
- 共享契约提供 manifest task schema 和 ready-task 选择器,用于记录任务拆分、专业组、角色模板、依赖、产物、验收条件和当前可执行任务。
- 开发模式可读取、保存、删除短期记忆和长期记忆文件;普通用户通过聊天命令完成同类能力。