From 876529e6681c49f5277274b874925cdde0eed1e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E5=BE=B7=E5=AE=87?= Date: Sat, 19 Sep 2026 21:41:37 +0800 Subject: [PATCH] =?UTF-8?q?=E5=88=A0=E9=99=A4=20Project=20Supervisor=20?= =?UTF-8?q?=E5=89=8D=E7=AB=AF=E9=93=BE=E8=B7=AF=E5=B9=B6=E5=B0=86=E9=A1=B9?= =?UTF-8?q?=E7=9B=AE=E5=AF=B9=E8=AF=9D=E6=94=B6=E6=95=9B=E5=88=B0=20Direct?= =?UTF-8?q?Project=20=E4=B8=8E=E7=AB=8B=E9=A1=B9=E7=AD=96=E5=88=92?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 删除 ProjectSupervisorView、SupervisorChatOnlyView、ProjectWorkspaceChatPane、AgentConversationOverlay、DeveloperProjectPanels、DeveloperRuntimePanels 与 features/agent-runtime/panels.tsx - 删除 Supervisor 独立调试窗口:windows.rs 的 supervisor_chat_window_url / open_project_supervisor_chat_window、main.rs 的 invoke 注册、?supervisor-chat 与 ?agent-chat 前端入口、developer.json capability 和对应 Rust 用例 - 删除工作台壳的开发者 Agent 面板:DeveloperAgentPanel、useDeveloperAgentPanel、useDeveloperAgentState、developerAgentControls - App.tsx 删除只服务退役面板的 state/ref/effect/handler(agentConversation*、文件/记忆/资产/画板/预览面板处理、commandLog、llmConfigStatus、editorBaseUrl、回放历史与 trace 面板状态、selectedAgent 等)以及由此产生的空分支,并把 requestRuntimeConfigOpen 接回本地 RuntimeConfigDialog - 立项策划独立成模块:Design Agent 与 Planning V2 的容器和表现移到 view/project-development/planning/(PlanningChatView、PlanningUserInputCard、GddApprovalCard、DesignAgentSurface、PlanningLaneRuntimeStrip、planningLane、planningSessionV2、planningSessionContract) - 改名到中性概念:ProjectSupervisorComponentProps→ProjectChatComponentProps、WorkspaceLauncherShellProps.ProjectSupervisor→ProjectChat、ProjectDevelopmentView.supervisor→chat、orchestrationMode→agentDockVisible、initialSupervisorMessageClaims→initialTurnClaims、ProjectManifestSnapshotSource 'supervisor'→'chat'、CSS project-supervisor-*/project-planning-*→project-chat-* - 删除 PROJECT_SUPERVISOR_AGENT_ID 与 PROJECT_SUPERVISOR_PLAN_SOURCE 常量 - 工作台壳自己订阅 game-creator-manifest-invalidated,用 revision→清单→revision 配对读(source: asset-event)并入 currentProjectContext,且不重新检查项目目录;测试夹具改为按监听器集合广播该事件 - 删除只钉住退役界面的 appSurface 用例(旧调试窗口、开发者面板、Agent 对话浮层、运行历史面板) - 同步文档:ADR、DirectProject 聊天模块抽离实施计划与里程碑、Provider 推理里程碑、策划会话 RuntimeV2、AI 游戏创作实施计划,并在 decision-log 新增 2026-09-19 条 - 删除随退役面板失去调用方的模块与导出:projectSummaryCommands.ts(1054 行旧 Supervisor 斜杠命令处理器)、AGENT_RUN_HISTORY_* 常量、agent-runtime/model.ts 与 project-summary/agentPresentation.ts 里的死导出,以及 agentRunTrace / memoryCommands / projectCommandPolicy 中无调用方的校验与解析函数 - 更新 scripts/check-config.mjs 与 scripts/check-native-shells.mjs 的窗口与命令守卫 --- .../scripts/check-config.mjs | 77 +- .../src-tauri/capabilities/developer.json | 7 - .../src-tauri/capabilities/events.json | 2 +- .../src-tauri/capabilities/window-chrome.json | 2 +- .../src-tauri/src/main.rs | 1 - .../tests/collaboration/static_deliveries.rs | 12 - .../src-tauri/src/windows.rs | 56 - apps/ai-game-creator-shell/src/App.tsx | 8133 +---------------- .../src/app/constants.ts | 13 - .../src/app/initialSupervisorMessageClaims.ts | 25 - .../src/app/initialTurnClaims.ts | 22 + .../src/features/agent-runtime/index.ts | 1 - .../src/features/agent-runtime/model.ts | 1013 +- .../src/features/agent-runtime/panels.tsx | 1328 --- .../app-shell/DeveloperAgentPanel.tsx | 734 -- .../features/app-shell/WorkspaceLauncher.tsx | 121 +- .../app-shell/developerAgentControls.ts | 983 -- .../src/features/app-shell/model.ts | 59 +- .../app-shell/useDeveloperAgentPanel.ts | 1892 ---- .../app-shell/useDeveloperAgentState.ts | 165 - .../app-shell/useHomeProjectCreation.ts | 3 - .../project-summary/agentPresentation.ts | 353 +- .../AgentConversationOverlay.tsx | 440 - .../DeveloperProjectPanels.tsx | 419 - .../DeveloperRuntimePanels.tsx | 306 - .../PlanningLaneRuntimeStrip.tsx | 171 - .../ProjectWorkspaceChatPane.tsx | 1137 --- .../SupervisorChatOnlyView.tsx | 356 - .../project-workspace/agentRunTrace.ts | 10 - .../project-workspace/memoryCommands.ts | 27 - .../project-workspace/projectCommandPolicy.ts | 66 +- .../projectSummaryCommands.ts | 1054 --- apps/ai-game-creator-shell/src/main.tsx | 34 +- apps/ai-game-creator-shell/src/styles.css | 1270 +-- .../ai-game-creator-shell/src/view/layout.tsx | 1 - .../view/project-development/approvalMode.ts | 2 +- .../chat/DirectProjectChatView.tsx | 17 +- .../DirectProjectChatHeader.tsx | 8 +- .../ComposerControls.tsx | 32 +- .../DirectProjectComposer.tsx | 15 +- .../DirectProjectConversation.tsx | 6 +- .../DirectProjectSettingsDialog.tsx | 20 +- .../useDirectProjectChatController.ts | 4 +- .../conversation/directCodexConversation.ts | 1 + .../src/view/project-development/index.tsx | 20 +- .../planning}/DesignAgentSurface.tsx | 5 +- .../planning}/GddApprovalCard.tsx | 4 +- .../planning/PlanningChatView.tsx} | 236 +- .../planning/PlanningLaneRuntimeStrip.tsx | 62 + .../planning/PlanningUserInputCard.tsx | 104 + .../planning}/planningLane.ts | 6 +- .../planning/planningSessionContract.ts | 9 + .../planning}/planningSessionV2.ts | 20 +- .../projectResourceLiveUpdateModel.ts | 2 +- .../tests/AgentMessageContent.test.tsx | 6 +- .../tests/agentRuntimeModel.test.ts | 384 +- .../tests/appSurface.test.ts | 10 - .../tests/appSurface/chat-composer.suite.ts | 18 +- .../tests/appSurface/design-agent.suite.ts | 22 +- .../developer-agent-window.suite.ts | 6017 ------------ .../tests/appSurface/harness.ts | 160 +- .../tests/appSurface/home.suite.ts | 247 +- .../tests/appSurface/plan-gdd.suite.ts | 146 +- .../tests/appSurface/project-assets.suite.ts | 502 - .../appSurface/project-commands.suite.ts | 547 -- .../appSurface/project-conversation.suite.ts | 1282 +-- .../appSurface/project-development.suite.ts | 3956 +------- .../project-preview/run-history.suite.ts | 586 -- .../project-preview/run-status.suite.ts | 286 +- .../project-preview/run-trace.suite.ts | 238 +- .../appSurface/runtime-settings.suite.ts | 346 +- .../appSurface/supervisor-runtime.suite.ts | 2052 ----- .../tests/chatDialogFrameLayout.test.ts | 112 +- .../tests/conversationModelSelect.test.tsx | 4 +- .../tests/directActiveTurns.test.tsx | 2 +- .../tests/errorReporting.test.ts | 4 +- ...est.tsx => planningUserInputCard.test.tsx} | 10 +- .../projectResourceLiveIntegration.test.tsx | 10 +- .../projectResourceLiveUpdateModel.test.ts | 4 +- .../resourceCanvasCodeOnlyProject.test.tsx | 2 +- ...nvasGenerationTasksSidebarDismiss.test.tsx | 2 +- .../tests/resourceCanvasManualLayout.test.tsx | 2 +- ...esourceCardPreviewAlphaBackground.test.tsx | 2 +- .../tests/resourceJsonCanvas.test.tsx | 2 +- .../tests/resourceRename.test.tsx | 4 +- .../tests/resourceTagStatsRefresh.test.tsx | 13 +- .../tests/resourceVersionReplacement.test.tsx | 2 +- .../tests/resourceVersionSwitch.test.tsx | 4 +- ...runVersionSwitchEventSubscription.test.tsx | 8 +- .../workspaceLauncherManifestMerge.test.tsx | 36 +- .../tests/workspaceWindowSync.test.tsx | 4 +- ...ct独立聊天容器与工作台钱包布局-2026-09-18.md | 6 +- ...实施计划】AGC模板库客户端接入-2026-09-17.md | 4 + ...施计划】AGC画布交互稳定性修复-2026-09-16.md | 18 +- ...】DirectProject Skill提及输入提示-2026-09-15.md | 8 +- ...rectProject Thread Manager事件订阅-2026-09-15.md | 8 +- ...DirectProject用户ResponseItem输入-2026-09-15.md | 8 +- ...计划】DirectProject聊天模块抽离-2026-09-18.md | 56 +- ...划】DirectProject聊天真相源收敛-2026-09-16.md | 8 +- ...施计划】图集切片模式显式决策-2026-09-17.md | 8 +- ...施计划】平台会话身份与凭据分离-2026-09-16.md | 8 +- ...DirectProject Skill提及输入提示-2026-09-15.md | 10 +- ...ectProject Thread Manager事件订阅-2026-09-15.md | 10 +- ...DirectProject用户ResponseItem输入-2026-09-15.md | 10 +- ...碑】DirectProject聊天模块抽离-2026-09-18.md | 20 +- ...碑】DirectProject聊天真相源收敛-2026-09-16.md | 10 +- ...ider推理与正文分离及策划Agent展示-2026-09-14.md | 50 +- ...里程碑】图集切片模式显式决策-2026-09-17.md | 10 +- ...程碑】平台会话身份与凭据分离-2026-09-16.md | 10 +- ...程碑】资源画布支持引擎资源预览-2026-09-17.md | 10 +- .../shared-memory/decision-log.md | 18 +- .../shared-memory/document-map.md | 18 +- docs/project-memory/shared-memory/pitfalls.md | 5 + ...规范驱动开发主规范与里程碑模板-2026-09-12.md | 28 +- ...案】AI游戏创作智能体App实施计划-2026-06-24.md | 17 +- ...策划会话RuntimeV2接入与旧链路退役-2026-09-03.md | 10 +- scripts/check-native-shells.mjs | 91 +- 117 files changed, 1772 insertions(+), 36585 deletions(-) delete mode 100644 apps/ai-game-creator-shell/src-tauri/capabilities/developer.json delete mode 100644 apps/ai-game-creator-shell/src/app/initialSupervisorMessageClaims.ts create mode 100644 apps/ai-game-creator-shell/src/app/initialTurnClaims.ts delete mode 100644 apps/ai-game-creator-shell/src/features/agent-runtime/panels.tsx delete mode 100644 apps/ai-game-creator-shell/src/features/app-shell/DeveloperAgentPanel.tsx delete mode 100644 apps/ai-game-creator-shell/src/features/app-shell/developerAgentControls.ts delete mode 100644 apps/ai-game-creator-shell/src/features/app-shell/useDeveloperAgentPanel.ts delete mode 100644 apps/ai-game-creator-shell/src/features/app-shell/useDeveloperAgentState.ts delete mode 100644 apps/ai-game-creator-shell/src/features/project-workspace/AgentConversationOverlay.tsx delete mode 100644 apps/ai-game-creator-shell/src/features/project-workspace/DeveloperProjectPanels.tsx delete mode 100644 apps/ai-game-creator-shell/src/features/project-workspace/DeveloperRuntimePanels.tsx delete mode 100644 apps/ai-game-creator-shell/src/features/project-workspace/PlanningLaneRuntimeStrip.tsx delete mode 100644 apps/ai-game-creator-shell/src/features/project-workspace/ProjectWorkspaceChatPane.tsx delete mode 100644 apps/ai-game-creator-shell/src/features/project-workspace/SupervisorChatOnlyView.tsx delete mode 100644 apps/ai-game-creator-shell/src/features/project-workspace/projectSummaryCommands.ts rename apps/ai-game-creator-shell/src/{features/project-workspace => view/project-development/planning}/DesignAgentSurface.tsx (98%) rename apps/ai-game-creator-shell/src/{features/project-workspace => view/project-development/planning}/GddApprovalCard.tsx (99%) rename apps/ai-game-creator-shell/src/{features/project-workspace/ProjectSupervisorView.tsx => view/project-development/planning/PlanningChatView.tsx} (63%) create mode 100644 apps/ai-game-creator-shell/src/view/project-development/planning/PlanningLaneRuntimeStrip.tsx create mode 100644 apps/ai-game-creator-shell/src/view/project-development/planning/PlanningUserInputCard.tsx rename apps/ai-game-creator-shell/src/{features/project-workspace => view/project-development/planning}/planningLane.ts (94%) create mode 100644 apps/ai-game-creator-shell/src/view/project-development/planning/planningSessionContract.ts rename apps/ai-game-creator-shell/src/{features/project-workspace => view/project-development/planning}/planningSessionV2.ts (98%) delete mode 100644 apps/ai-game-creator-shell/tests/appSurface/developer-agent-window.suite.ts delete mode 100644 apps/ai-game-creator-shell/tests/appSurface/supervisor-runtime.suite.ts rename apps/ai-game-creator-shell/tests/{agentRuntimeUserInputCard.test.tsx => planningUserInputCard.test.tsx} (88%) diff --git a/apps/ai-game-creator-shell/scripts/check-config.mjs b/apps/ai-game-creator-shell/scripts/check-config.mjs index fe8943608..0d64fb0a9 100644 --- a/apps/ai-game-creator-shell/scripts/check-config.mjs +++ b/apps/ai-game-creator-shell/scripts/check-config.mjs @@ -86,10 +86,6 @@ const appInvokeSources = readSourceFiles( new URL('../src/', import.meta.url), new Set(['.ts', '.tsx']), ); -const appEntrypointSource = fs.readFileSync( - new URL('../src/main.tsx', import.meta.url), - 'utf8', -); const tauriHandlerSource = fs.readFileSync( new URL('../src-tauri/src/main.rs', import.meta.url), 'utf8', @@ -114,6 +110,48 @@ const rustSharedContractSource = fs.readFileSync( ); const allowedUncalledTauriCommands = [ 'append_direct_project_conversation_message', + // Supervisor 调试窗口、开发者面板、专业 Agent 对话与旧命令聊天的前端调用方已随 + // Supervisor 前端链路整体删除;命令本身仍注册在 Rust 侧并由 native Runtime、CLI + // swarm 与 Rust 测试使用,保留 present,仅不再出现在 App 前端源码里。 + 'answer_game_creator_agent_runtime_user_input', + 'cancel_game_creator_agent_runtime_task', + 'chat_with_game_creator_role_agent', + 'chat_with_game_creator_role_agent_stream', + 'check_game_creator_llm_config', + 'confirm_game_creator_agent_runtime_task', + 'delete_local_game_memory', + 'delete_local_project_file', + 'diff_local_project_checkpoint', + 'get_game_creation_agent_capabilities', + 'get_limited_local_commands', + 'list_local_project_export_packages', + 'pick_local_file', + 'read_game_creator_agent_runtime', + 'read_local_agent_memory', + 'read_local_game_memory', + 'reject_game_creator_agent_runtime_task', + 'retry_game_creator_agent_runtime_task', + 'schedule_game_creator_agent_ready_tasks', + 'start_game_creator_agent_runtime_task', + 'steer_game_creator_agent_runtime_task', + 'write_local_agent_memory', + 'write_local_game_memory', + 'write_local_project_file', + // Agent 运行时会话 / 目标 / 协作命令由 native 侧与 CLI swarm 驱动,前端没有调用方。 + 'archive_game_creator_agent_session', + 'clear_game_creator_agent_goal', + 'compact_game_creator_agent_runtime_context', + 'confirm_retry_game_creator_agent_runtime_task', + 'create_game_creator_agent_session', + 'edit_game_creator_agent_goal', + 'fork_game_creator_agent_session', + 'list_game_creator_agent_sessions', + 'pause_game_creator_agent_goal', + 'read_game_creator_agent_goal', + 'resume_game_creator_agent_goal', + 'set_active_game_creator_agent_session', + 'start_game_creator_agent_goal', + 'start_game_creator_supervisor_runtime_task', // TODO: Remove the retired binding command after the legacy runtime path is removed. 'bind_components', 'chat_with_game_creator_agent', @@ -1412,13 +1450,7 @@ const eventCapability = JSON.parse( ); const eventCapabilityWindows = new Set(eventCapability.windows ?? []); const eventCapabilityPermissions = new Set(eventCapability.permissions ?? []); -for (const windowLabel of [ - 'client', - 'developer', - 'main', - 'launcher', - 'supervisor-chat', -]) { +for (const windowLabel of ['client', 'main', 'launcher']) { if (!eventCapabilityWindows.has(windowLabel)) { throw new Error( `AI game creator shell event capability missing window: ${windowLabel}`, @@ -1801,20 +1833,6 @@ if ( ); } -for (const snippet of [ - 'import.meta.env.DEV', - 'supervisorChatMode', - 'supervisorChatOnly', - 'open_project_supervisor_chat_window', - 'index.html?supervisor-chat&projectPath=', -]) { - if (!`${appEntrypointSource}\n${tauriRustSource}`.includes(snippet)) { - throw new Error( - `AI game creator shell developer window guardrail drifted: ${snippet}`, - ); - } -} - if (tauriHandlerSource.includes('open_developer_window(app.handle())?')) { throw new Error( 'AI game creator normal startup must not automatically open the developer window', @@ -1853,25 +1871,16 @@ for (const snippet of [ 'function needsInitializedChatProject', 'function resolvePendingCommandProjectPath', 'resolveChatProjectPath(localProject) ?? draftProjectPath', - '`permission.cancel ${command.id} missing-project`', "'/remember [short|long|blackboard] 内容:追加短期、长期或黑板记忆'", "'/memory-set [short|long|blackboard] 内容:覆盖保存对应记忆'", 'function parseRememberInput', "'/trace 或 /loop:查看最近一次 Agent loop trace'", - 'async function executeAgentTraceChat', - "relativePath: '.agent/logs/command.log'", "'permission.pending'", "'permission.confirm'", "'permission.cancel'", "'command.auto'", "'agent.run_status'", 'function summarizeAgentRunTrace', - '工具调用:${agentRunTrace.toolCallCount}/${agentRunTrace.maxToolCalls}', - 'agentRunTrace.error ?', - 'className="trace-error"', - 'agentRunTrace.taskGraph.repairRoutes.map', - "in: ${step.inputPaths.join(', ') || 'none'}", - "out: ${step.outputPaths.join(', ') || 'none'}", ]) { if (!appSource.includes(snippet)) { throw new Error( diff --git a/apps/ai-game-creator-shell/src-tauri/capabilities/developer.json b/apps/ai-game-creator-shell/src-tauri/capabilities/developer.json deleted file mode 100644 index ca9fdae42..000000000 --- a/apps/ai-game-creator-shell/src-tauri/capabilities/developer.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "$schema": "../gen/schemas/desktop-schema.json", - "identifier": "developer", - "description": "开发窗口允许打开本地素材选择对话框。", - "windows": ["developer"], - "permissions": ["dialog:allow-open"] -} diff --git a/apps/ai-game-creator-shell/src-tauri/capabilities/events.json b/apps/ai-game-creator-shell/src-tauri/capabilities/events.json index 8e31538b8..fa5b4f1d3 100644 --- a/apps/ai-game-creator-shell/src-tauri/capabilities/events.json +++ b/apps/ai-game-creator-shell/src-tauri/capabilities/events.json @@ -2,6 +2,6 @@ "$schema": "../gen/schemas/desktop-schema.json", "identifier": "events", "description": "允许客户端窗口订阅并取消订阅 Rust Runtime 事件。", - "windows": ["client", "developer", "main", "launcher", "supervisor-chat"], + "windows": ["client", "main", "launcher"], "permissions": ["core:event:allow-listen", "core:event:allow-unlisten"] } diff --git a/apps/ai-game-creator-shell/src-tauri/capabilities/window-chrome.json b/apps/ai-game-creator-shell/src-tauri/capabilities/window-chrome.json index a23607f6f..f7d700272 100644 --- a/apps/ai-game-creator-shell/src-tauri/capabilities/window-chrome.json +++ b/apps/ai-game-creator-shell/src-tauri/capabilities/window-chrome.json @@ -2,7 +2,7 @@ "$schema": "../gen/schemas/desktop-schema.json", "identifier": "window-chrome", "description": "自绘标题栏允许执行当前窗口的基础控制和拖拽。", - "windows": ["client", "developer", "main", "launcher", "supervisor-chat"], + "windows": ["client", "main", "launcher"], "permissions": [ "core:window:allow-close", "core:window:allow-is-maximized", diff --git a/apps/ai-game-creator-shell/src-tauri/src/main.rs b/apps/ai-game-creator-shell/src-tauri/src/main.rs index 48657522a..0164db7b5 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/main.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/main.rs @@ -2665,7 +2665,6 @@ fn main() { write_project_permission_policy, open_game_creator_workspace_window, open_game_creator_launcher_window, - open_project_supervisor_chat_window, start_local_game_preview, activate_local_game_preview, stop_local_game_preview, diff --git a/apps/ai-game-creator-shell/src-tauri/src/tests/collaboration/static_deliveries.rs b/apps/ai-game-creator-shell/src-tauri/src/tests/collaboration/static_deliveries.rs index ea991326f..4b145e22b 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/tests/collaboration/static_deliveries.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/tests/collaboration/static_deliveries.rs @@ -451,18 +451,6 @@ fn dispatch_static_delegate_plain_repair( ) } -#[test] -fn supervisor_chat_window_carries_encoded_project_path() { - assert_eq!( - supervisor_chat_window_url("/tmp/AI Game 项目").to_string(), - "index.html?supervisor-chat&projectPath=%2Ftmp%2FAI%20Game%20%E9%A1%B9%E7%9B%AE" - ); - assert_eq!( - supervisor_chat_window_url("/tmp/a&b?#%+c").to_string(), - "index.html?supervisor-chat&projectPath=%2Ftmp%2Fa%26b%3F%23%25%2Bc" - ); -} - #[test] fn project_supervisor_runtime_id_is_normalized_and_collected() { let root = unique_project_path(); diff --git a/apps/ai-game-creator-shell/src-tauri/src/windows.rs b/apps/ai-game-creator-shell/src-tauri/src/windows.rs index fc146f387..38488aa41 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/windows.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/windows.rs @@ -119,13 +119,6 @@ pub(crate) fn launcher_window_url() -> tauri::WebviewUrl { tauri::WebviewUrl::App(PathBuf::from("index.html?launcher")) } -pub(crate) fn supervisor_chat_window_url(project_path: &str) -> tauri::WebviewUrl { - tauri::WebviewUrl::App(PathBuf::from(format!( - "index.html?supervisor-chat&projectPath={}", - percent_encode_query_value(project_path) - ))) -} - pub(crate) fn validate_workspace_window_project_path(project_path: &str) -> Result<&str, String> { let project_path = project_path.trim(); if project_path.is_empty() { @@ -193,52 +186,3 @@ pub(crate) fn open_game_creator_launcher_window( window.close().map_err(|error| error.to_string())?; Ok(()) } - -#[tauri::command] -pub(crate) fn open_project_supervisor_chat_window( - app: tauri::AppHandle, - project_path: String, -) -> Result<(), String> { - let project_path = validate_workspace_window_project_path(&project_path)?; - #[cfg(not(debug_assertions))] - { - let _ = app; - let _ = project_path; - return Err("项目总控对话窗口仅在开发构建中可用".to_string()); - } - #[cfg(debug_assertions)] - { - if let Some(existing) = app.get_webview_window("supervisor-chat") { - let mut current_url = existing.url().map_err(|error| error.to_string())?; - let current_project_path = current_url - .query_pairs() - .find_map(|(key, value)| (key == "projectPath").then(|| value.into_owned())); - if current_project_path.as_deref() != Some(project_path) { - current_url.set_query(Some(&format!( - "supervisor-chat&projectPath={}", - percent_encode_query_value(project_path) - ))); - current_url.set_fragment(None); - existing - .navigate(current_url) - .map_err(|error| error.to_string())?; - } - existing.show().map_err(|error| error.to_string())?; - existing.unminimize().map_err(|error| error.to_string())?; - existing.set_focus().map_err(|error| error.to_string())?; - return Ok(()); - } - tauri::WebviewWindowBuilder::new( - &app, - "supervisor-chat", - supervisor_chat_window_url(project_path), - ) - .title("项目总控 Agent 对话") - .decorations(false) - .inner_size(820.0, 720.0) - .min_inner_size(560.0, 480.0) - .build() - .map_err(|error| error.to_string())?; - Ok(()) - } -} diff --git a/apps/ai-game-creator-shell/src/App.tsx b/apps/ai-game-creator-shell/src/App.tsx index 98631759d..ae832a991 100644 --- a/apps/ai-game-creator-shell/src/App.tsx +++ b/apps/ai-game-creator-shell/src/App.tsx @@ -1,7 +1,6 @@ /* eslint-disable react-refresh/only-export-components -- Testable pure helpers currently share this legacy app module. */ import { - type ChangeEvent, type FormEvent, type UIEvent, useCallback, @@ -12,75 +11,41 @@ import { } from 'react'; import { - GAME_CREATION_AGENT_CAPABILITIES, - GAME_CREATION_APP_COMMANDS, - GAME_CREATION_APP_LIMITED_RUN_COMMANDS, - type GameCreationAgentCapabilityDescriptor, - type GameCreationAgentRunTrace, - type GameCreationAgentToolCallTrace, type GameCreationAppCommandDescriptor, - type GameCreationAppLimitedRunCommandDescriptor, type GameCreationAppManifest, - type GameCreationAppPermission, type GameCreationAppPreviewState, type GameCreationAppPreviewStatus, } from '../../../packages/shared/src/contracts/gameCreationApp'; import { AGENT_CHAT_SCROLL_BOTTOM_THRESHOLD, - AGENT_RUN_HISTORY_INITIAL_VISIBLE_COUNT, - AGENT_RUN_HISTORY_MAX_COUNT, - AGENT_RUN_HISTORY_VISIBLE_STEP, CONVERSATION_INITIAL_VISIBLE_COUNT, CONVERSATION_VISIBLE_STEP, createLocalProjectId, - PROJECT_SUPERVISOR_AGENT_ID, - PROJECT_SUPERVISOR_PLAN_SOURCE, seedManifest, } from './app/constants'; import { useEscapeToClose } from './app/dialogs'; -import { claimInitialSupervisorMessageForPage } from './app/initialSupervisorMessageClaims'; +import { claimInitialTurnForPage } from './app/initialTurnClaims'; import { resolveTauriInvoke } from './app/tauri'; import type { - AgentBackgroundSubmitMode, - AgentProgressEvent, AgentRunControlResult, - AgentRunHistoryItem, - AgentRuntimeResponseStream, AgentRuntimeResult, AgentRuntimeState, - AgentRuntimeSteerResult, AgentRuntimeUserInputRequest, - AgentStatusCard, ChatMessage, DesignAgentInput, DesignClarificationRequest, - DesignEvent, DesignView, - GameCreatorAgentRuntimeUpdateEvent, - GameCreatorChatAgentReply, - GameCreatorLlmConfigStatus, - GameCreatorManifestInvalidatedEvent, - GameCreatorRoleAgentChatStreamEvent, GenerateLocalGameDraftResult, ImportCanvasExportResult, InitLocalProjectResult, LauncherImportedAttachment, LimitedLocalCommandResult, - ListLocalProjectFilesResult, - LocalAgentMemoryResult, - LocalConversationMessageRecord, LocalConversationResult, - LocalGameMemoryResult, LocalGameProjectRevisionStatus, LocalPreviewResult, LocalPreviewStatus, LocalProjectCheckpointResult, - LocalProjectCheckpointSummary, - LocalProjectDiffResult, LocalProjectExportPackageResult, - LocalProjectExportPackagesResult, - LocalProjectFileEntry, - LocalProjectFileMutationResult, LocalProjectFileResult, LocalProjectIndexResult, LocalProjectKind, @@ -98,156 +63,58 @@ import type { TauriInvoke, UploadLocalAssetResult, } from './app/types'; -import { useWindowChrome } from './components/windowChromeContext'; import { agentConversationId, - agentRuntimeCancelStatus, - agentRuntimeConversationStatus, agentRuntimeNeedsUserInput, - agentRuntimeStartedRunId, - agentRuntimeStartStatus, agentRuntimeStateFromResult, - agentRuntimeSteerStatus, - conversationContainsProjectSupervisorResponseStream, createAgentChatRunId, createDefaultChatMessages, - createLocalConversationDraftMessage, - ensureProjectSupervisorActiveSessionId, isAgentFinalizationMessageId, - isAgentRuntimeTerminalState, isMissingAgentRuntimeResumeCommandError, isRuntimeConfigMissingError, - matchingAgentRuntimeForSteer, mergeAgentRuntimeStateIntoMap, - mergeProjectSupervisorConversation, - mergeProjectSupervisorResponseStream, normalizeAgentRuntimeState, - projectNameFromPath, projectProfessionalAgentLabel, - projectSupervisorPendingRepairMatchesProfessional, - readProjectSupervisorActiveSessionId, - resolveProjectSupervisorRuntimeSubmission, - sameAgentRuntimeRun, - submitProjectSupervisorRuntimeTask, taskRowsFromManifest, } from './features/agent-runtime'; import { - isDeveloperMode, isTransientProjectOpenMessage, latestVisibleItems, - persistSupervisorChatDraft, - type ProjectSupervisorComponentProps, + type ProjectChatComponentProps, readInitialProjectPath, - readSupervisorChatDraft, type WorkspaceLauncherProps, writeRecentWorkspace, } from './features/app-shell/model'; import { WorkspaceLauncherShell } from './features/app-shell/WorkspaceLauncher'; import { - agentConversationReadDraftsFromManifest, - agentMemoryReadDraftsFromManifest, - commandDraftFromSuggestedToolCall, - deriveAgentStatusCards, - formatAgentDialogLlmStatus, - formatAgentLlmConfigWarning, formatAgentRunControlError, - formatCodexRuntimeCapabilities, - formatLlmAgentStatusLine, - formatLlmRouteEndpoint, - isMissingAgentRunTraceError, projectAgentRuntimeSummaries, - readablePassArtifactsFromAgentRunTrace, - sameAgentStatusCard, - summarizeAgentAudit, - summarizeAgentCapabilities, - summarizeAgentConversationReadDrafts, - summarizeAgentLlmRoutes, - summarizeAgentMemoryReadDrafts, - summarizeAgentPassArtifactReadDrafts, summarizeAgentRunCompletionForChat, - summarizeAgentRunHistoryReadDrafts, - summarizeAgentStatusCardsForChat, - summarizeLimitedLocalCommands, - summarizeRunArtifactReadDrafts, } from './features/project-summary/agentPresentation'; import { - chatCommandHelp, - checkpointSummaryFromManifest, - commonAgentRunSupportReadDrafts, - commonProjectArtifactReadDrafts, - commonProjectInternalReadDrafts, - commonProjectLogReadDrafts, - firstReadableProjectAssetPath, - formatAgentRunStatus, formatCanvasAssetSource, isAbsoluteProjectPath, - isSafeProjectRelativePath, - missingChatCommandArgumentMessage, - projectFileActionDrafts, projectPathHasControlCharacter, - projectPathsMatchForInvalidation, - readableArtifactsFromAgentRunTrace, - sortCheckpointManifestFiles, - summarizeAgentRunSupportFileReadDrafts, - summarizeCommonProjectArtifactReadDrafts, - summarizeCommonProjectLogReadDrafts, - summarizeMainProjectHeader, - summarizeProjectAssetCredits, - summarizeProjectAssets, - summarizeProjectAudioAssets, summarizeProjectCheckpoint, - summarizeProjectCheckpoints, - summarizeProjectDiff, summarizeProjectExportPackage, - summarizeProjectExportPackages, - summarizeProjectFileContent, - summarizeProjectFiles, summarizeProjectIndex, - summarizeProjectInternalReadDrafts, summarizeProjectPolicy, - summarizeProjectStatus, - summarizeProjectTasks, - summarizeProjectVisualAssets, } from './features/project-summary/projectSummary'; -import { AgentConversationOverlay } from './features/project-workspace/AgentConversationOverlay'; import { gameDraftStartedMessage, - isMissingProjectFileError, parseAgentRunTrace, } from './features/project-workspace/agentRunTrace'; -import { DeveloperProjectPanels } from './features/project-workspace/DeveloperProjectPanels'; -import { DeveloperRuntimePanels } from './features/project-workspace/DeveloperRuntimePanels'; import { - appendMemoryContent, memoryScopeLabel, - memoryScopePath, - parseOptionalMemoryScope, parseRememberInput, } from './features/project-workspace/memoryCommands'; import { pendingCommandDetail } from './features/project-workspace/pendingCommandPresentation'; -import { planningStateNeedsRuntimeRefresh } from './features/project-workspace/planningLane'; -import { - type PlanningApprovalCommandResultV2, - planningMessagesToChatMessages, - planningResultDisplayText, - type PlanningSessionCommandResultV2, - type PlanningSessionStreamEventV2, - planningSessionToPlanGddState, - planningSessionToRuntime, -} from './features/project-workspace/planningSessionV2'; import { isAgentTraceFilePath, - isProjectPolicyConfirmableCommandId, - isRegisteredGameCreationCommandId, - isSafeCanvasProjectId, - isSafeCheckpointId, needsInitializedChatProject, resolveChatProjectPath, resolvePendingCommandProjectPath, } from './features/project-workspace/projectCommandPolicy'; -import { handleProjectSummaryChatCommand } from './features/project-workspace/projectSummaryCommands'; -import { ProjectSupervisorView } from './features/project-workspace/ProjectSupervisorView'; -import { ProjectWorkspaceChatPane } from './features/project-workspace/ProjectWorkspaceChatPane'; import type { ResourceReferenceInputHandle } from './features/project-workspace/ResourceReferenceInput'; import type { ChatComposerDraft, @@ -260,26 +127,17 @@ import { resourceReferenceFromAsset, type ResourceReferenceInsertEventDetail, } from './features/project-workspace/resourceReferences'; -import { SupervisorChatOnlyView } from './features/project-workspace/SupervisorChatOnlyView'; import { RuntimeConfigDialog } from './features/runtime-config/RuntimeConfigDialog'; -import { captureAgentRuntimeError } from './services/errorReporting'; import { setAgcPluginProjectPath, startAvailableAgcPlugin, } from './services/pluginHost'; -import { - canSubscribeTauriEvents, - subscribeTauriEvent, -} from './services/tauriEventSubscription'; import type { HomeCreationType } from './view/home'; import { type ProjectAgentResultSummary, type ProjectAgentRuntimeSummary, } from './view/project-development'; -import { - createDirectProjectTurnId, - DIRECT_CODEX_CONVERSATION_MESSAGE_ID_PREFIX, -} from './view/project-development/chat/conversation/directCodexConversation'; +import { DIRECT_CODEX_CONVERSATION_MESSAGE_ID_PREFIX } from './view/project-development/chat/conversation/directCodexConversation'; import { directCodexAttachmentContentParts, toDirectCodexTurnAttachments, @@ -290,6 +148,16 @@ import { type DirectProjectInitialTurn, } from './view/project-development/chat/DirectProjectChatView'; import type { DirectCodexUserContentPart } from './view/project-development/chat/generated/DirectCodexUserContentPart'; +import { PlanningChatView } from './view/project-development/planning/PlanningChatView'; +import { planningStateNeedsRuntimeRefresh } from './view/project-development/planning/planningLane'; +import { + type PlanningApprovalCommandResultV2, + planningMessagesToChatMessages, + planningResultDisplayText, + type PlanningSessionCommandResultV2, + planningSessionToPlanGddState, + planningSessionToRuntime, +} from './view/project-development/planning/planningSessionV2'; import type { ProjectManifestSnapshotMetadata } from './view/project-development/projectResourceLiveUpdateModel'; function isPersistableDirectCodexConversationMessage(message: ChatMessage) { @@ -319,32 +187,6 @@ function isPersistableDirectCodexConversationMessage(message: ChatMessage) { * 就会被冲掉(界面上看不到初始需求,但回合其实已经跑起来了)。 * 这里把当前 messages 里"运行时拥有、且回读结果里没有"的消息保留在末尾(它们是最新的)。 */ -function mergeLoadedConversationWithPendingRuntimeMessages( - loaded: ChatMessage[], - current: ChatMessage[], -): ChatMessage[] { - if (current.length === 0) { - return loaded; - } - const loadedIds = new Set( - loaded - .map((message) => message.messageId) - .filter((id): id is string => Boolean(id)), - ); - const loadedTexts = new Set( - loaded.map((message) => `${message.role}\u0000${message.text}`), - ); - const pending = current.filter((message) => { - if (!message.runtimeOwned) { - return false; - } - if (message.messageId) { - return !loadedIds.has(message.messageId); - } - return !loadedTexts.has(`${message.role}\u0000${message.text}`); - }); - return pending.length > 0 ? [...loaded, ...pending] : loaded; -} export { AuthenticatedClient } from './app/AuthenticatedClient'; export type { PendingCommand } from './app/types'; @@ -363,24 +205,21 @@ export { }; export function WorkspaceLauncher(props: WorkspaceLauncherProps) { - return ; + return ; } type AppProps = { initialProjectPath?: string; initialProjectManifest?: GameCreationAppManifest; initialProjectKind?: LocalProjectKind; - orchestrationMode?: 'single-supervisor' | 'professional-dag'; - projectSupervisorOnly?: boolean; planningStartMode?: boolean; - activeVersionId?: ProjectSupervisorComponentProps['activeVersionId']; - supervisorChatOnly?: boolean; - initialSupervisorMessage?: string; - initialSupervisorMessageClaimScope?: string; + activeVersionId?: ProjectChatComponentProps['activeVersionId']; + initialPlanningPrompt?: string; + initialPlanningPromptClaimScope?: string; initialCreationType?: HomeCreationType | null; initialAttachments?: LauncherImportedAttachment[]; - playRequest?: ProjectSupervisorComponentProps['playRequest']; - onPlayRequestHandled?: ProjectSupervisorComponentProps['onPlayRequestHandled']; + playRequest?: ProjectChatComponentProps['playRequest']; + onPlayRequestHandled?: ProjectChatComponentProps['onPlayRequestHandled']; onManifestChange?: ( projectPath: string, manifest: GameCreationAppManifest, @@ -395,7 +234,7 @@ type AppProps = { }; /** - * 工作台壳里仍由 Supervisor / Design Agent / Planning V2 接管的提交入参。 + * 工作台壳里由立项策划链路接管的提交入参。 * * DirectProject 的回合入参(`@` 引用、附件、权限确认重跑)归聊天容器自己的 * `DirectProjectTurnInput`,不再从这里穿过。 @@ -409,13 +248,10 @@ export function App({ initialProjectPath: initialProjectPathOverride = '', initialProjectManifest, initialProjectKind = 'web', - orchestrationMode = 'professional-dag', - projectSupervisorOnly = false, planningStartMode = false, activeVersionId = null, - supervisorChatOnly = false, - initialSupervisorMessage = '', - initialSupervisorMessageClaimScope = '', + initialPlanningPrompt = '', + initialPlanningPromptClaimScope = '', initialCreationType = null, initialAttachments = [], playRequest = null, @@ -426,7 +262,6 @@ export function App({ onAgentResultsChange, onMakeGameFromApprovedGdd, }: AppProps = {}) { - const { setTitle: setWindowTitle } = useWindowChrome(); const [planningV2Active, setPlanningV2Active] = useState(planningStartMode); const planningV2ActiveRef = useRef(planningStartMode); planningV2ActiveRef.current = planningV2Active; @@ -441,30 +276,19 @@ export function App({ const designAgentEventSubscriptionReadyRef = useRef | null>( null, ); - const designAgentEventSubscriptionResolveRef = useRef<(() => void) | null>( - null, - ); - // 项目开发工作台的普通项目固定使用 DirectProject;Supervisor 调试、Design Agent - // 和 Planning V2 是各自独立的入口,不通过 DirectProject 的兼容分支承载。 - const directProjectMode = - projectSupervisorOnly && - !supervisorChatOnly && - !planningStartMode && - !planningV2Active; - const [devMode] = useState(() => - projectSupervisorOnly ? false : isDeveloperMode(), - ); + // 项目开发工作台的普通项目固定使用 DirectProject,立项策划项目走策划聊天;两条 + // 链路各自独立,不通过兼容分支互相承载。 + const directProjectMode = !planningV2Active; const [initialProjectPath] = useState( () => initialProjectPathOverride || readInitialProjectPath(), ); - const eagerSupervisorProject = - projectSupervisorOnly && Boolean(initialProjectPath); + const eagerProject = Boolean(initialProjectPath); const [projectPath, setProjectPath] = useState(initialProjectPath); const [workspaceProjectKind, setWorkspaceProjectKind] = useState(initialProjectKind); const [localProject, setLocalProject] = useState(() => - eagerSupervisorProject + eagerProject ? { projectPath: initialProjectPath, manifestPath: `${initialProjectPath.replace(/[\\/]+$/, '')}/.agent/manifest.json`, @@ -475,7 +299,6 @@ export function App({ const localProjectPathRef = useRef(null); useEffect(() => { - if (supervisorChatOnly) return; const nextProjectPath = localProject?.projectPath ?? null; const previousProjectPath = localProjectPathRef.current; localProjectPathRef.current = nextProjectPath; @@ -503,7 +326,7 @@ export function App({ } localProjectPathRef.current = null; }; - }, [localProject?.projectPath, supervisorChatOnly, workspaceProjectKind]); + }, [localProject?.projectPath, workspaceProjectKind]); const manifestRefreshMountedRef = useRef(true); const manifestRefreshStatesRef = useRef( @@ -542,15 +365,10 @@ export function App({ if (JSON.stringify(snapshot) === JSON.stringify(current)) return; setManifest(snapshot); }, [initialProjectManifest]); - const [projectStatus, setProjectStatus] = useState( - eagerSupervisorProject ? '已初始化' : '未初始化', - ); - const [preview, setPreview] = useState(null); - const [previewStatus, setPreviewStatus] = useState('未启动'); - const initialSupervisorMessageLatchRef = useRef({ + const initialPlanningPromptLatchRef = useRef({ projectPath: initialProjectPath, - prompt: initialSupervisorMessage.trim(), - claimScope: initialSupervisorMessageClaimScope, + prompt: initialPlanningPrompt.trim(), + claimScope: initialPlanningPromptClaimScope, creationType: initialCreationType, attachments: toDirectCodexTurnAttachments(initialAttachments), }); @@ -560,7 +378,6 @@ export function App({ nextPreview: LocalPreviewResult | null, status: GameCreationAppPreviewStatus = nextPreview ? 'running' : 'stopped', ) { - setPreview(nextPreview); onPreviewChange?.( nextPreview ? { @@ -572,11 +389,7 @@ export function App({ ); } - const [chatInput, setChatInput] = useState(() => - supervisorChatOnly && initialProjectPath - ? readSupervisorChatDraft(initialProjectPath) - : '', - ); + const [chatInput, setChatInput] = useState(''); const [chatReferences, setChatReferences] = useState([]); const [chatContent, setChatContent] = useState( [], @@ -586,16 +399,9 @@ export function App({ setChatContent([]); }, [localProject?.projectPath]); const [chatAgentBusy, setChatAgentBusy] = useState(false); - const [projectSupervisorSessionId, setProjectSupervisorSessionId] = useState< - string | null - >(null); - - const [projectSupervisorRuntime, setProjectSupervisorRuntime] = + const [planningRuntime, setPlanningRuntime] = useState(null); - const [projectSupervisorResponseStream, setProjectSupervisorResponseStream] = - useState(null); - const [projectSupervisorRuntimeError, setProjectSupervisorRuntimeError] = - useState(''); + const [projectChatError, setProjectChatError] = useState(''); const [planGddState, setPlanGddState] = useState( null, ); @@ -617,15 +423,6 @@ export function App({ view: DesignView; } | null>(null); const [planningV2Reasoning, setPlanningV2Reasoning] = useState(''); - const designAgentReasoningTurnRef = useRef<{ - projectPath: string; - clientTurnId: string; - text: string; - } | null>(null); - const planningV2TurnRef = useRef<{ - projectPath: string; - clientTurnId: string; - } | null>(null); const planGddHydrateSequenceRef = useRef(0); const [planGddHydrateBusy, setPlanGddHydrateBusy] = useState(false); const [planGddDecisionBusy, setPlanGddDecisionBusy] = useState(false); @@ -655,22 +452,6 @@ export function App({ return designAgentEventSubscriptionReadyRef.current ?? Promise.resolve(); } - function createDesignAgentEventSubscriptionReady() { - if (!designAgentEventSubscriptionReadyRef.current) { - designAgentEventSubscriptionReadyRef.current = new Promise( - (resolve) => { - designAgentEventSubscriptionResolveRef.current = resolve; - }, - ); - } - return designAgentEventSubscriptionReadyRef.current; - } - - function resolveDesignAgentEventSubscriptionReady() { - designAgentEventSubscriptionResolveRef.current?.(); - designAgentEventSubscriptionResolveRef.current = null; - } - useEffect(() => { const timer = window.setInterval(() => { const target = planningV2TransientReplyTargetRef.current; @@ -702,12 +483,10 @@ export function App({ setPlanningV2Active(true); planningV2SessionRef.current = result; setPlanningV2Session(result); - projectSupervisorSessionIdRef.current = result.session.sessionId; - setProjectSupervisorSessionId(result.session.sessionId); setPlanGddState( planningSessionToPlanGddState(result.session, result.currentArtifact), ); - updateProjectSupervisorRuntime(planningSessionToRuntime(result.session)); + updatePlanningRuntime(planningSessionToRuntime(result.session)); const resultError = result.result?.kind === 'error' ? (() => { @@ -723,14 +502,14 @@ export function App({ })() : ''; const sessionError = result.session.lastError?.summary ?? resultError; - setProjectSupervisorRuntimeError(sessionError); + setProjectChatError(sessionError); if (result.conversation) { let conversationMessages = planningMessagesToChatMessages( result.conversation, ); // 创建项目后的首条需求可能先于规划会话快照到达;不能让后到的空快照 // 把用户刚发出的内容覆盖掉。 - const initialPrompt = initialSupervisorMessageLatchRef.current.prompt; + const initialPrompt = initialPlanningPromptLatchRef.current.prompt; if ( initialPrompt && !conversationMessages.some( @@ -841,7 +620,7 @@ export function App({ reasoningText: reasoningByMessageId.get(message.id)?.join('\n\n'), updatedAt: Date.now(), })); - const initialPrompt = initialSupervisorMessageLatchRef.current.prompt; + const initialPrompt = initialPlanningPromptLatchRef.current.prompt; if ( initialPrompt && !messages.some( @@ -959,22 +738,17 @@ export function App({ ) { const invoke = resolveTauriInvoke(); if (!invoke) { - setProjectSupervisorRuntimeError('需要在 Tauri App 内运行。'); + setProjectChatError('需要在 Tauri App 内运行。'); return; } designAgentTurnRef.current = { projectPath: nextProjectPath, clientTurnId, }; - designAgentReasoningTurnRef.current = { - projectPath: nextProjectPath, - clientTurnId, - text: '', - }; designAgentPendingViewRef.current = null; await designAgentEventSubscriptionReady(); setChatAgentBusy(true); - setProjectSupervisorRuntimeError(''); + setProjectChatError(''); setPlanningV2TransientReplyTarget(''); setPlanningV2Reasoning(''); try { @@ -995,7 +769,7 @@ export function App({ if (isRuntimeConfigMissingError(message)) { requestRuntimeConfigOpen(); } - setProjectSupervisorRuntimeError(message); + setProjectChatError(message); setPlanGddError(message); } finally { if (!designAgentPendingViewRef.current) { @@ -1143,12 +917,9 @@ export function App({ useEffect(() => { // 存在性判据故意走 `status`(必选字段,为 `undefined` 当且仅当 runtime 为 null)而不是整个 // 对象:依赖里只挖 phase/status/updatedAt 三个标量,是为了只在监工状态真的动了时 - // 重灌。把 `projectSupervisorRuntime` 本体写进依赖会让每一轮轮询新建的对象身份都触发一次 + // 重灌。把 `planningRuntime` 本体写进依赖会让每一轮轮询新建的对象身份都触发一次 // hydrate,白烧 IPC。 - if ( - !localProject?.projectPath || - projectSupervisorRuntime?.status === undefined - ) { + if (!localProject?.projectPath || planningRuntime?.status === undefined) { return; } // 后端 hydrate 会抢项目写锁并扫 authority,不是纯内存读。没有这道门,做游戏和做 @@ -1157,7 +928,7 @@ export function App({ // 依赖等于 hydrate 触发 hydrate。 if ( !planningStateNeedsRuntimeRefresh( - projectSupervisorRuntime?.source, + planningRuntime?.source, planGddStateRef.current, ) ) { @@ -1167,10 +938,10 @@ export function App({ }, [ hydratePlanGddState, localProject?.projectPath, - projectSupervisorRuntime?.phase, - projectSupervisorRuntime?.source, - projectSupervisorRuntime?.status, - projectSupervisorRuntime?.updatedAt, + planningRuntime?.phase, + planningRuntime?.source, + planningRuntime?.status, + planningRuntime?.updatedAt, ]); useEffect(() => { @@ -1187,60 +958,9 @@ export function App({ document.removeEventListener('visibilitychange', hydrateOnResume); }; }, [hydratePlanGddState, localProject?.projectPath]); - const [projectSupervisorExpectedRunId, setProjectSupervisorExpectedRunId] = - useState(null); - const chatInputRef = useRef(null); - const supervisorChatMessagesRef = useRef(null); - const supervisorChatShouldFollowLatestRef = useRef(true); + const planningChatMessagesRef = useRef(null); + const planningChatShouldFollowLatestRef = useRef(true); const directProjectChatRef = useRef(null); - const [assetStatus, setAssetStatus] = useState('未上传'); - const [uploadedAssets, setUploadedAssets] = useState< - UploadLocalAssetResult[] - >([]); - const [assetLocalPath, setAssetLocalPath] = useState('assets/hero.png'); - const [assetKind, setAssetKind] = useState('asset'); - const [assetMediaType, setAssetMediaType] = useState( - 'application/octet-stream', - ); - const [assetSourceKind, setAssetSourceKind] = useState('generated'); - const [assetCanvasProjectId, setAssetCanvasProjectId] = useState(''); - const [assetResourceId, setAssetResourceId] = useState(''); - const [assetObjectId, setAssetObjectId] = useState(''); - const [assetGenerationPrompt, setAssetGenerationPrompt] = - useState('首版核心美术素材'); - const [canvasExportPath, setCanvasExportPath] = useState( - '/tmp/canvas-export.zip', - ); - const [editorBaseUrl, setEditorBaseUrl] = useState('http://127.0.0.1:3000'); - const [memoryScope, setMemoryScope] = useState('long'); - const [memoryDraft, setMemoryDraft] = useState(''); - const [memoryStatus, setMemoryStatus] = useState('未读取'); - const [limitedCommandStatus, setLimitedCommandStatus] = useState('未运行'); - const [limitedLocalCommands, setLimitedLocalCommands] = useState< - GameCreationAppLimitedRunCommandDescriptor[] - >([...GAME_CREATION_APP_LIMITED_RUN_COMMANDS]); - const [projectFiles, setProjectFiles] = useState([]); - const [projectCheckpoints, setProjectCheckpoints] = useState< - LocalProjectCheckpointSummary[] - >([]); - const [filePath, setFilePath] = useState('game/index.html'); - const [fileDraft, setFileDraft] = useState(''); - const [fileStatus, setFileStatus] = useState('未读取'); - const [agentRunTrace, setAgentRunTrace] = - useState(null); - const [agentRunHistory, setAgentRunHistory] = useState( - [], - ); - const [agentRunHistoryFiles, setAgentRunHistoryFiles] = useState< - LocalProjectFileEntry[] - >([]); - const [agentRunHistoryVisibleCount, setAgentRunHistoryVisibleCount] = - useState(AGENT_RUN_HISTORY_INITIAL_VISIBLE_COUNT); - const [agentRunHistoryOverflowCount, setAgentRunHistoryOverflowCount] = - useState(0); - const [agentRunHistoryLoadingMore, setAgentRunHistoryLoadingMore] = - useState(false); - const [agentRunStatus, setAgentRunStatus] = useState('未运行'); const [agentRuntimeById, setAgentRuntimeById] = useState< Record >({}); @@ -1248,39 +968,9 @@ export function App({ agentRuntimeByIdRef.current = agentRuntimeById; const [professionalAgentResultsById, setProfessionalAgentResultsById] = useState>({}); - const [runtimeConfigOpen, setRuntimeConfigOpen] = useState(false); - const [llmConfigStatus, setLlmConfigStatus] = - useState(null); const [workspaceStatus, setWorkspaceStatus] = useState( - eagerSupervisorProject ? `已打开:${initialProjectPath}` : '请选择工作区', + eagerProject ? `已打开:${initialProjectPath}` : '请选择工作区', ); - const [selectedAgent, setSelectedAgent] = useState( - null, - ); - const selectedAgentIdRef = useRef(null); - selectedAgentIdRef.current = selectedAgent?.id ?? null; - const [agentConversationInput, setAgentConversationInput] = useState(''); - const [agentConversationStatus, setAgentConversationStatus] = - useState('未选择 agent'); - const [agentConversationMessages, setAgentConversationMessages] = useState< - LocalConversationMessageRecord[] - >([]); - const [agentConversationRuntime, setAgentConversationRuntime] = - useState(null); - const [agentConversationSessionId, setAgentConversationSessionId] = useState< - string | null - >(null); - const [agentConversationRunSubmitMode, setAgentConversationRunSubmitMode] = - useState('steer'); - const [agentConversationRuntimeError, setAgentConversationRuntimeError] = - useState(''); - const [agentConversationVisibleCount, setAgentConversationVisibleCount] = - useState(CONVERSATION_INITIAL_VISIBLE_COUNT); - const [agentConversationSaving, setAgentConversationSaving] = useState(false); - const [agentConversationBackgroundBusy, setAgentConversationBackgroundBusy] = - useState(false); - const [agentMemoryStatus, setAgentMemoryStatus] = useState('未读取'); - const [agentMemoryContent, setAgentMemoryContent] = useState(''); const [messages, setMessages] = useState( createDefaultChatMessages, ); @@ -1297,17 +987,12 @@ export function App({ projectPath: string; announceToChat: boolean; } | null>(null); - const [commandLog, setCommandLog] = useState([ - `${GAME_CREATION_APP_COMMANDS.length} 个命令已登记权限。`, - ]); - const [projectLogStatus, setProjectLogStatus] = useState('未读取'); - const [projectLogContent, setProjectLogContent] = useState(''); const [conversationWriteVersion, setConversationWriteVersion] = useState(0); const savedConversationCountRef = useRef( - eagerSupervisorProject ? createDefaultChatMessages().length : 0, + eagerProject ? createDefaultChatMessages().length : 0, ); const savedConversationProjectPathRef = useRef( - eagerSupervisorProject ? initialProjectPath : null, + eagerProject ? initialProjectPath : null, ); const projectConversationWriteConfirmedRef = useRef(null); const projectConversationWriteCancelledRef = useRef<{ @@ -1317,19 +1002,8 @@ export function App({ const latestMessagesRef = useRef([]); const conversationWriteInFlightRef = useRef(false); const projectScopeVersionRef = useRef(0); - const projectSupervisorHistoryLoadVersionRef = useRef(0); - const projectSupervisorRuntimeResumeProjectPathRef = useRef( - null, - ); - const projectSupervisorSessionIdRef = useRef(null); - projectSupervisorSessionIdRef.current = projectSupervisorSessionId; - const projectSupervisorRuntimeRef = useRef(null); - projectSupervisorRuntimeRef.current = projectSupervisorRuntime; - const projectSupervisorExpectedRunIdRef = useRef(null); - projectSupervisorExpectedRunIdRef.current = projectSupervisorExpectedRunId; - const projectSupervisorResponseStreamRef = - useRef(null); - projectSupervisorResponseStreamRef.current = projectSupervisorResponseStream; + const planningRuntimeRef = useRef(null); + planningRuntimeRef.current = planningRuntime; const refreshManifest = useCallback( (nextProjectPath = localProjectPathRef.current ?? ''): Promise => { @@ -1399,148 +1073,35 @@ export function App({ refreshStates.clear(); }; }, []); - const projectSupervisorRuntimeSyncingRef = useRef(new Set()); - const projectSupervisorRefreshConversationRef = useRef< - | (( - invoke: TauriInvoke, - projectPath: string, - sessionId: string, - ) => Promise) - | null - >(null); const executeChatAgentReplyRef = useRef< (input: ExecuteChatAgentReplyInput) => Promise >(async () => undefined); - const agentConversationSavingRef = useRef(false); - const agentConversationBackgroundBusyRef = useRef(false); - const agentConversationLoadVersionRef = useRef(0); - const agentConversationSessionIdRef = useRef(null); - agentConversationSessionIdRef.current = agentConversationSessionId; const agentRuntimeResumeProjectPathRef = useRef(null); - const agentRunHistoryLoadingMoreRef = useRef(false); const initialProjectOpenedRef = useRef(false); const pendingUiConfirmationActionRef = useRef<(() => void) | null>(null); - const queueRunLocalShortcutRef = useRef<() => void>(() => undefined); const executeRunLocalRef = useRef<(announceToChat: boolean) => void>( () => undefined, ); + const [runtimeConfigOpen, setRuntimeConfigOpen] = useState(false); function requestRuntimeConfigOpen() { - if (projectSupervisorOnly && !supervisorChatOnly) { - return; - } setRuntimeConfigOpen(true); } - const updateProjectSupervisorRuntime = useCallback( + const updatePlanningRuntime = useCallback( ( runtime: AgentRuntimeState | null, - previous = projectSupervisorRuntimeRef.current, + previous = planningRuntimeRef.current, ) => { const nextRuntime = runtime ? normalizeAgentRuntimeState(runtime, previous) : null; - projectSupervisorRuntimeRef.current = nextRuntime; - setProjectSupervisorRuntime(nextRuntime); + planningRuntimeRef.current = nextRuntime; + setPlanningRuntime(nextRuntime); }, [], ); - const updateProjectSupervisorResponseStream = useCallback( - ( - incoming: AgentRuntimeResponseStream | null | undefined, - runtime: AgentRuntimeState, - ) => { - let nextStream = mergeProjectSupervisorResponseStream( - projectSupervisorResponseStreamRef.current, - incoming, - runtime, - ); - const candidateStream = nextStream; - if ( - candidateStream && - latestMessagesRef.current.some( - (message) => - message.runtimeOwned && - message.role === 'assistant' && - message.text === candidateStream.accumulatedText && - typeof message.updatedAt === 'number' && - message.updatedAt >= candidateStream.startedAt, - ) - ) { - // Keep the existing supervisor-chat behavior for restored history. - nextStream = null; - } - projectSupervisorResponseStreamRef.current = nextStream; - setProjectSupervisorResponseStream(nextStream); - }, - [], - ); - - function resetProjectSupervisorState() { - projectSupervisorHistoryLoadVersionRef.current += 1; - projectSupervisorRuntimeResumeProjectPathRef.current = null; - projectSupervisorSessionIdRef.current = null; - projectSupervisorRuntimeRef.current = null; - projectSupervisorExpectedRunIdRef.current = null; - projectSupervisorResponseStreamRef.current = null; - planningV2SessionRef.current = null; - planningV2TurnRef.current = null; - projectSupervisorRuntimeSyncingRef.current.clear(); - setProjectSupervisorSessionId(null); - setProjectSupervisorRuntime(null); - setProjectSupervisorExpectedRunId(null); - setProjectSupervisorResponseStream(null); - setProjectSupervisorRuntimeError(''); - setPlanningV2Session(null); - setPlanningV2TransientReplyTarget(''); - designAgentPendingViewRef.current = null; - designAgentReasoningTurnRef.current = null; - setPlanningV2Reasoning(''); - setPlanningV2Active(planningStartMode); - planningV2ActiveRef.current = planningStartMode; - designAgentLaneRef.current = planningStartMode; - designAgentTurnRef.current = null; - setDesignAgentView(null); - } - - function syncTerminalProjectSupervisorConversation( - invoke: TauriInvoke, - projectPath: string, - runtime: AgentRuntimeState, - ) { - if (!isAgentRuntimeTerminalState(runtime) || !runtime.sessionId) { - return; - } - const syncKey = `${projectPath}\n${runtime.sessionId}\n${runtime.runId}`; - const syncAlreadyStarted = - projectSupervisorRuntimeSyncingRef.current.has(syncKey); - if (syncAlreadyStarted) { - return; - } - const refreshConversation = projectSupervisorRefreshConversationRef.current; - if (!refreshConversation) { - return; - } - projectSupervisorRuntimeSyncingRef.current.add(syncKey); - void refreshConversation(invoke, projectPath, runtime.sessionId) - .then(() => {}) - .catch((error) => { - projectSupervisorRuntimeSyncingRef.current.delete(syncKey); - if ( - localProjectPathRef.current === projectPath && - projectSupervisorSessionIdRef.current === runtime.sessionId - ) { - setProjectSupervisorRuntimeError( - `项目总控 Agent 对话刷新失败:${ - error instanceof Error ? error.message : String(error) - }`, - ); - } - }); - } - - useEscapeToClose(closeAgentConversation, selectedAgent !== null); useEscapeToClose(cancelUiCommandConfirmation, pendingUiConfirmation !== null); useEscapeToClose( cancelProjectCreateInNonEmptyFolder, @@ -1564,619 +1125,43 @@ export function App({ return; } initialProjectOpenedRef.current = true; - if (projectSupervisorOnly) { - if (directProjectMode) { - if (!isAbsoluteProjectPath(initialProjectPath)) { - setWorkspaceStatus('请提供工作区绝对路径'); - return; - } - if (projectPathHasControlCharacter(initialProjectPath)) { - setWorkspaceStatus('工作区路径不能包含控制字符'); - return; - } - // This component is mounted inside the already-created project - // workbench. Hydrate localProject before the first direct turn so the - // direct runtime cannot silently create a second workspace. - void openWorkspace( - initialProjectPath, - false, - 'open', - initialProjectKind, - ); - return; - } - if (!isAbsoluteProjectPath(initialProjectPath)) { - setWorkspaceStatus('请提供工作区绝对路径'); - return; - } - if (projectPathHasControlCharacter(initialProjectPath)) { - setWorkspaceStatus('工作区路径不能包含控制字符'); - return; - } - localProjectPathRef.current = initialProjectPath; - void loadProjectConversation(initialProjectPath).finally(() => { - if ( - localProjectPathRef.current === initialProjectPath && - !planningStartMode - ) { - void refreshAgentRunTrace(initialProjectPath); - } - }); + if (!isAbsoluteProjectPath(initialProjectPath)) { + setWorkspaceStatus('请提供工作区绝对路径'); return; } - void openWorkspace( - initialProjectPath, - false, - initialProjectKind === 'godot' || initialProjectKind === 'cocos' - ? 'open' - : 'create', - initialProjectKind, - ); + if (projectPathHasControlCharacter(initialProjectPath)) { + setWorkspaceStatus('工作区路径不能包含控制字符'); + return; + } + if (directProjectMode) { + // This component is mounted inside the already-created project workbench. + // Hydrate localProject before the first direct turn so the direct runtime + // cannot silently create a second workspace. + void openWorkspace(initialProjectPath, false, 'open', initialProjectKind); + return; + } + localProjectPathRef.current = initialProjectPath; + void loadProjectConversation(initialProjectPath).finally(() => { + if ( + localProjectPathRef.current === initialProjectPath && + !planningStartMode + ) { + void refreshAgentRunTrace(initialProjectPath); + } + }); // Initial project opening is guarded by initialProjectOpenedRef. // eslint-disable-next-line react-hooks/exhaustive-deps - }, [initialProjectPath, projectSupervisorOnly]); - - useEffect(() => { - if (projectSupervisorOnly && !directProjectMode) { - return; - } - if (!canSubscribeTauriEvents()) { - return; - } - let cleanup: (() => void) | null = null; - let disposed = false; - void subscribeTauriEvent( - 'game-creator-agent-progress', - (event) => { - if (event.payload.projectPath !== localProjectPathRef.current) { - return; - } - if (directProjectMode) { - // DirectProject 的进度已经从线程事件流进聊天条目,这条通用进度事件不再参与。 - return; - } - setMessages((current) => [ - ...current, - { role: 'assistant', text: event.payload.message }, - ]); - }, - ) - .then((unlisten) => { - if (disposed) { - unlisten(); - return; - } - cleanup = unlisten; - }) - .catch((error) => { - if (disposed) { - return; - } - setAgentRunStatus( - `实时状态不可用:${ - error instanceof Error ? error.message : String(error) - }`, - ); - }); - return () => { - disposed = true; - cleanup?.(); - }; - }, [directProjectMode, projectSupervisorOnly]); - - useEffect(() => { - const invoke = resolveTauriInvoke(); - if (!canSubscribeTauriEvents() || directProjectMode || planningV2Active) { - return; - } - let cleanup: (() => void) | null = null; - let disposed = false; - void subscribeTauriEvent( - 'game-creator-agent-runtime-update', - (event) => { - const payload = event.payload; - if (payload.projectPath !== localProjectPathRef.current) { - return; - } - if (payload.manifestInvalidated) { - void refreshManifest(payload.projectPath); - } - const nextRuntime = agentRuntimeStateFromResult(payload.runtime); - if (payload.agentId === PROJECT_SUPERVISOR_AGENT_ID) { - const expectedSessionId = projectSupervisorSessionIdRef.current; - const currentRuntime = projectSupervisorRuntimeRef.current; - const expectedRunId = projectSupervisorExpectedRunIdRef.current; - const sameRun = - currentRuntime !== null && - sameAgentRuntimeRun(nextRuntime, currentRuntime); - const isExpectedRun = - expectedRunId !== null && nextRuntime.runId === expectedRunId; - if ( - nextRuntime.agentId !== PROJECT_SUPERVISOR_AGENT_ID || - nextRuntime.runId !== payload.runId || - (expectedSessionId !== null && - nextRuntime.sessionId !== expectedSessionId) || - (currentRuntime !== null && - ((!sameRun && !isExpectedRun) || - (sameRun && nextRuntime.updatedAt < currentRuntime.updatedAt))) - ) { - return; - } - if (expectedSessionId === null && nextRuntime.sessionId) { - projectSupervisorSessionIdRef.current = nextRuntime.sessionId; - setProjectSupervisorSessionId(nextRuntime.sessionId); - } - updateProjectSupervisorRuntime(nextRuntime); - if (isExpectedRun) { - projectSupervisorExpectedRunIdRef.current = null; - setProjectSupervisorExpectedRunId(null); - } - updateProjectSupervisorResponseStream( - payload.runtime.responseStream, - nextRuntime, - ); - setProjectSupervisorRuntimeError(''); - if (invoke) { - syncTerminalProjectSupervisorConversation( - invoke, - payload.projectPath, - nextRuntime, - ); - } - } - rememberAgentRuntimeState(nextRuntime); - if (payload.agentId !== selectedAgentIdRef.current) { - return; - } - if ( - agentConversationSessionIdRef.current !== null && - payload.runtime.state.sessionId !== - agentConversationSessionIdRef.current - ) { - return; - } - setAgentConversationRuntime((current) => - normalizeAgentRuntimeState(nextRuntime, current), - ); - setAgentConversationRuntimeError(''); - }, - ) - .then((unlisten) => { - if (disposed) { - unlisten(); - return; - } - cleanup = unlisten; - }) - .catch((error) => { - if (disposed) { - return; - } - setAgentConversationRuntimeError( - `Runtime 订阅不可用:${ - error instanceof Error ? error.message : String(error) - }`, - ); - }); - return () => { - disposed = true; - cleanup?.(); - }; - // The subscription deliberately invokes the current render's helper; its - // mutable state is read through refs, so resubscribing for that helper is - // neither necessary nor safe. - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [ - directProjectMode, - planningV2Active, - refreshManifest, - updateProjectSupervisorResponseStream, - updateProjectSupervisorRuntime, - ]); - - useEffect(() => { - if (!canSubscribeTauriEvents() || !planningV2Active) { - return; - } - let cleanup: (() => void) | null = null; - let disposed = false; - void subscribeTauriEvent( - 'planning-session-v2-stream', - (event) => { - const payload = event.payload; - const trackedTurn = planningV2TurnRef.current; - if (!trackedTurn || payload.clientTurnId !== trackedTurn.clientTurnId) { - return; - } - if (payload.status === 'started') { - setPlanningV2TransientReplyTarget( - payload.accumulatedText || '正在生成策划方案…', - ); - setProjectSupervisorRuntimeError(''); - } - if ( - payload.status === 'delta' && - (payload.accumulatedText || payload.deltaText) - ) { - setPlanningV2TransientReplyTarget( - payload.accumulatedText || payload.deltaText, - ); - setProjectSupervisorRuntimeError(''); - } - if (payload.status === 'failed' && payload.error?.summary) { - setProjectSupervisorRuntimeError(payload.error.summary); - } - }, - ) - .then((unlisten) => { - if (disposed) { - unlisten(); - return; - } - cleanup = unlisten; - }) - .catch((error) => { - if (!disposed) { - setProjectSupervisorRuntimeError( - `Planning V2 实时回复不可用:${ - error instanceof Error ? error.message : String(error) - }`, - ); - } - }); - return () => { - disposed = true; - cleanup?.(); - }; - }, [planningV2Active]); - - useEffect(() => { - const ready = createDesignAgentEventSubscriptionReady(); - if (!canSubscribeTauriEvents() || !planningV2Active) { - resolveDesignAgentEventSubscriptionReady(); - return () => { - if (designAgentEventSubscriptionReadyRef.current === ready) { - designAgentEventSubscriptionReadyRef.current = null; - createDesignAgentEventSubscriptionReady(); - } - }; - } - let cleanup: (() => void) | null = null; - let disposed = false; - void subscribeTauriEvent('design-agent-update', (event) => { - const payload = event.payload; - const tracked = designAgentTurnRef.current; - if ( - payload.projectPath !== localProjectPathRef.current || - (tracked && payload.clientTurnId !== tracked.clientTurnId) - ) { - return; - } - if (payload.kind === 'text' && payload.text != null) { - setPlanningV2TransientReplyTarget(payload.text); - } - if (payload.reasoningText != null) { - const reasoningTurn = designAgentReasoningTurnRef.current; - if ( - reasoningTurn && - reasoningTurn.projectPath === payload.projectPath && - reasoningTurn.clientTurnId === payload.clientTurnId - ) { - reasoningTurn.text = payload.reasoningText; - setPlanningV2Reasoning(payload.reasoningText); - } - } - if (payload.kind === 'tool' && payload.text) { - setPlanningV2TransientReplyTarget(payload.text); - } - if (payload.view) { - applyDesignAgentViewAfterTransient( - payload.view, - payload.projectPath, - payload.clientTurnId, - ); - } - }) - .then((unlisten) => { - resolveDesignAgentEventSubscriptionReady(); - if (disposed) { - unlisten(); - return; - } - cleanup = unlisten; - }) - .catch(() => { - resolveDesignAgentEventSubscriptionReady(); - }); - return () => { - disposed = true; - cleanup?.(); - resolveDesignAgentEventSubscriptionReady(); - if (designAgentEventSubscriptionReadyRef.current === ready) { - designAgentEventSubscriptionReadyRef.current = null; - createDesignAgentEventSubscriptionReady(); - } - }; - // applyDesignView 读的是 refs 和当前项目路径, - // 把它写进依赖会在每轮回复时重订事件。 - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [planningV2Active]); - - useEffect(() => { - if (!canSubscribeTauriEvents()) { - return; - } - let cleanup: (() => void) | null = null; - let disposed = false; - void subscribeTauriEvent( - 'game-creator-manifest-invalidated', - (event) => { - const activeProjectPath = localProjectPathRef.current; - if ( - !activeProjectPath || - !projectPathsMatchForInvalidation( - event.payload.projectPath, - activeProjectPath, - ) - ) { - return; - } - void refreshManifest(activeProjectPath); - }, - ) - .then((unlisten) => { - if (disposed) { - unlisten(); - return; - } - cleanup = unlisten; - }) - .catch(() => { - // In-process Runtime events continue to carry the same invalidation signal. - }); - return () => { - disposed = true; - cleanup?.(); - }; - }, [refreshManifest]); - - useEffect(() => { - const invoke = resolveTauriInvoke(); - const nextProjectPath = localProject?.projectPath ?? null; - const sessionId = projectSupervisorSessionId; - const trackedRunId = - projectSupervisorExpectedRunId ?? projectSupervisorRuntime?.runId ?? null; - if ( - !invoke || - directProjectMode || - planningV2Active || - !nextProjectPath || - !sessionId || - !trackedRunId || - (!projectSupervisorRuntime && !projectSupervisorExpectedRunId) || - (projectSupervisorRuntime !== null && - isAgentRuntimeTerminalState(projectSupervisorRuntime) && - !projectSupervisorExpectedRunId) - ) { - return; - } - let disposed = false; - let inFlight = false; - const pollRuntime = async () => { - if (disposed || inFlight) { - return; - } - inFlight = true; - const runtimeBeforePoll = projectSupervisorRuntimeRef.current; - const responseStreamBeforePoll = - projectSupervisorResponseStreamRef.current; - try { - const result = await invoke( - 'read_game_creator_agent_runtime', - { - projectPath: nextProjectPath, - agentId: PROJECT_SUPERVISOR_AGENT_ID, - sessionId, - }, - ); - const currentRuntime = projectSupervisorRuntimeRef.current; - if ( - disposed || - currentRuntime !== runtimeBeforePoll || - projectSupervisorResponseStreamRef.current !== - responseStreamBeforePoll || - localProjectPathRef.current !== nextProjectPath || - projectSupervisorSessionIdRef.current !== sessionId - ) { - return; - } - const nextRuntime = agentRuntimeStateFromResult(result, currentRuntime); - if ( - nextRuntime.sessionId !== sessionId || - nextRuntime.runId !== trackedRunId || - (currentRuntime !== null && - sameAgentRuntimeRun(nextRuntime, currentRuntime) && - nextRuntime.updatedAt < currentRuntime.updatedAt) - ) { - return; - } - updateProjectSupervisorRuntime(nextRuntime); - if (nextRuntime.runId === projectSupervisorExpectedRunIdRef.current) { - projectSupervisorExpectedRunIdRef.current = null; - setProjectSupervisorExpectedRunId(null); - } - updateProjectSupervisorResponseStream( - result.responseStream, - nextRuntime, - ); - setProjectSupervisorRuntimeError(''); - syncTerminalProjectSupervisorConversation( - invoke, - nextProjectPath, - nextRuntime, - ); - } catch (error) { - if ( - !disposed && - localProjectPathRef.current === nextProjectPath && - projectSupervisorSessionIdRef.current === sessionId - ) { - const message = - error instanceof Error ? error.message : String(error); - if (isRuntimeConfigMissingError(message)) { - requestRuntimeConfigOpen(); - } - setProjectSupervisorRuntimeError(message); - } - } finally { - inFlight = false; - } - }; - void pollRuntime(); - const timer = window.setInterval(() => { - void pollRuntime(); - }, 750); - return () => { - disposed = true; - window.clearInterval(timer); - }; - // Polling restarts only when the tracked runtime identity or phase changes. - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [ - localProject?.projectPath, - directProjectMode, - planningV2Active, - projectSupervisorSessionId, - projectSupervisorRuntime?.phase, - projectSupervisorRuntime?.runId, - projectSupervisorRuntime?.status, - projectSupervisorExpectedRunId, - ]); - - useEffect(() => { - const invoke = resolveTauriInvoke(); - const nextProjectPath = localProject?.projectPath ?? null; - const supervisorRunId = projectSupervisorRuntime?.runId ?? null; - if ( - !projectSupervisorOnly || - directProjectMode || - planningV2Active || - !invoke || - !nextProjectPath || - !supervisorRunId - ) { - return; - } - let disposed = false; - let inFlight = false; - const pollProfessionalRuntimes = async () => { - if (disposed || inFlight) { - return; - } - inFlight = true; - try { - const runtimes = await invoke( - 'read_game_creator_agent_runtimes', - { projectPath: nextProjectPath }, - ); - if ( - disposed || - localProjectPathRef.current !== nextProjectPath || - projectSupervisorRuntimeRef.current?.runId !== supervisorRunId - ) { - return; - } - const nextRuntimes = runtimes.map((runtimeResult) => - agentRuntimeStateFromResult(runtimeResult), - ); - const authoritativeProfessionalAgentIds = new Set( - nextRuntimes - .filter( - (runtime) => - ['agent-delegate', 'agent-delegate-retry'].includes( - runtime.source, - ) && - runtime.parentAgentId === PROJECT_SUPERVISOR_AGENT_ID && - runtime.parentRunId === supervisorRunId, - ) - .map((runtime) => runtime.agentId), - ); - setAgentRuntimeById((current) => { - const reconciled = { ...current }; - for (const [key, runtime] of Object.entries(reconciled)) { - if ( - runtime && - ['agent-delegate', 'agent-delegate-retry'].includes( - runtime.source, - ) && - runtime.parentAgentId === PROJECT_SUPERVISOR_AGENT_ID && - runtime.parentRunId === supervisorRunId && - !authoritativeProfessionalAgentIds.has(runtime.agentId) - ) { - delete reconciled[key]; - } - } - return nextRuntimes.reduce( - (next, runtime) => - mergeAgentRuntimeStateIntoMap(next, runtime, true), - reconciled, - ); - }); - } catch { - // Preserve the last truthful professional Runtime snapshot on transient read failures. - } finally { - inFlight = false; - } - }; - void pollProfessionalRuntimes(); - const timer = window.setInterval(() => { - void pollProfessionalRuntimes(); - }, 1000); - return () => { - disposed = true; - window.clearInterval(timer); - }; - // Track the stable projection fields; the Runtime object identity changes on every merge. - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [ - localProject?.projectPath, - directProjectMode, - planningV2Active, - projectSupervisorOnly, - projectSupervisorRuntime?.phase, - projectSupervisorRuntime?.runId, - projectSupervisorRuntime?.status, - ]); + }, [initialProjectPath]); useLayoutEffect(() => { - if ( - (!supervisorChatOnly && - !(projectSupervisorOnly && (directProjectMode || planningV2Active))) || - !supervisorChatShouldFollowLatestRef.current - ) { + if (!planningChatShouldFollowLatestRef.current) { return; } - const messageList = supervisorChatMessagesRef.current; + const messageList = planningChatMessagesRef.current; if (messageList) { messageList.scrollTop = messageList.scrollHeight; } - }, [ - messages, - projectSupervisorResponseStream?.sequence, - projectSupervisorRuntime?.updatedAt, - projectSupervisorRuntimeError, - directProjectMode, - planningV2Active, - projectSupervisorOnly, - supervisorChatOnly, - ]); - - useEffect(() => { - if (!supervisorChatOnly) { - return; - } - persistSupervisorChatDraft(initialProjectPath, chatInput); - }, [chatInput, initialProjectPath, supervisorChatOnly]); + }, [messages, planningRuntime?.updatedAt, projectChatError]); useEffect(() => { latestMessagesRef.current = messages; @@ -2193,7 +1178,7 @@ export function App({ } // DirectProject history is written by Rust from raw app-server items. // The browser only renders that projection and must not append chat rows. - if (projectSupervisorOnly && directProjectMode) { + if (directProjectMode) { // `messages` is only an optimistic UI projection in Direct mode; it is // intentionally not proof of durability. Rust owns the raw response // history, so this effect must not route these rows through the generic @@ -2207,7 +1192,6 @@ export function App({ return; } if ( - projectSupervisorOnly && pendingMessages.every( (message) => (message.runtimeOwned && @@ -2272,7 +1256,6 @@ export function App({ } conversationWriteInFlightRef.current = true; void (async () => { - let wroteMessage = false; for (const [index, message] of pendingMessages.entries()) { if ( message.runtimeOwned && @@ -2301,7 +1284,6 @@ export function App({ }, }, ); - wroteMessage = true; savedConversationCountRef.current = start + index + 1; } setWorkspaceStatus((current) => @@ -2310,9 +1292,6 @@ export function App({ ? `已打开:${nextProjectPath}` : current, ); - if (wroteMessage) { - setCommandLog((current) => [...current, 'conversation.write']); - } })() .catch((error) => { savedConversationCountRef.current = Math.min( @@ -2324,12 +1303,6 @@ export function App({ error instanceof Error ? error.message : String(error) }`, ); - setCommandLog((current) => [ - ...current, - `conversation.write.failed ${ - error instanceof Error ? error.message : String(error) - }`, - ]); }) .finally(() => { conversationWriteInFlightRef.current = false; @@ -2347,7 +1320,6 @@ export function App({ messages, conversationWriteVersion, pendingUiConfirmation, - projectSupervisorOnly, directProjectMode, ]); @@ -2364,32 +1336,17 @@ export function App({ if (!invoke || !projectPath) { return; } - let result: Promise; try { - result = Promise.resolve( + void Promise.resolve( invoke('append_local_permission_log', { projectPath, event, commandId, }), - ); - } catch (error) { - setCommandLog((current) => [ - ...current, - `permission.log.failed ${commandId}: ${ - error instanceof Error ? error.message : String(error) - }`, - ]); - return; + ).catch(() => undefined); + } catch { + // 权限日志只是审计旁路,写失败不能阻塞对话。 } - void result.catch((error) => { - setCommandLog((current) => [ - ...current, - `permission.log.failed ${commandId}: ${ - error instanceof Error ? error.message : String(error) - }`, - ]); - }); } function resolvePermissionLogProjectPath(command: PendingCommand) { @@ -2427,49 +1384,6 @@ export function App({ return draftProjectPath; } - function queuePendingCommand(command: PendingCommand) { - setPendingCommand(command); - setCommandLog((current) => [ - ...current, - `permission.pending ${command.id}`, - ]); - appendLocalPermissionLog( - resolvePermissionLogProjectPath(command), - 'permission.pending', - command.id, - ); - } - - function requestCommandConfirmation( - commandId: GameCreationAppCommandDescriptor['id'], - detail: string, - onConfirm: () => void, - ) { - const permission = GAME_CREATION_APP_COMMANDS.find( - (command) => command.id === commandId, - )?.permission as GameCreationAppPermission | undefined; - - if (permission === 'deny' || !permission) { - setCommandLog((current) => [...current, `permission.deny ${commandId}`]); - return; - } - if (permission === 'confirm') { - pendingUiConfirmationActionRef.current = onConfirm; - setPendingUiConfirmation({ commandId, detail, projectPath: null }); - setCommandLog((current) => [ - ...current, - `permission.pending ${commandId}`, - ]); - appendLocalPermissionLog( - resolveUiPermissionLogProjectPath(commandId), - 'permission.pending', - commandId, - ); - return; - } - onConfirm(); - } - function requestProjectPolicyConfirmation( commandId: GameCreationAppCommandDescriptor['id'], projectPath: string, @@ -2478,7 +1392,6 @@ export function App({ ) { pendingUiConfirmationActionRef.current = onConfirm; setPendingUiConfirmation({ commandId, detail, projectPath }); - setCommandLog((current) => [...current, `permission.pending ${commandId}`]); appendLocalPermissionLog(projectPath, 'permission.pending', commandId); } @@ -2486,33 +1399,13 @@ export function App({ commandId: GameCreationAppCommandDescriptor['id'], message: string, ) { - if (commandId.startsWith('project.') || commandId === 'task.list') { - setProjectStatus(message); - setWorkspaceStatus(message); - } if ( - commandId.startsWith('file.') || - commandId === 'agent.trace_read' || - commandId === 'project.index' || - commandId === 'project.diff' || - commandId === 'project.export_package' || - commandId === 'project.export_list' + commandId.startsWith('project.') || + commandId === 'task.list' || + commandId.startsWith('preview.') ) { - setFileStatus(message); - } - if (commandId.startsWith('asset.') || commandId.startsWith('canvas.')) { - setAssetStatus(message); - } - if (commandId.startsWith('preview.')) { - setPreviewStatus(message); setWorkspaceStatus(message); } - if (commandId.startsWith('agent.')) { - setAgentRunStatus(message); - } - if (commandId.startsWith('memory.')) { - setMemoryStatus(message); - } } async function queueProjectPolicyConfirmationIfNeeded( @@ -2530,7 +1423,6 @@ export function App({ if (policyView.policy.deniedCommands.includes(commandId)) { const message = `项目权限策略拒绝执行:${commandId}`; markProjectPolicyDenied(commandId, message); - setCommandLog((current) => [...current, `permission.deny ${commandId}`]); setMessages((current) => [ ...current, { role: 'assistant', text: message }, @@ -2566,7 +1458,6 @@ export function App({ } const message = `项目权限策略拒绝执行:${commandId}`; markProjectPolicyDenied(commandId, message); - setCommandLog((current) => [...current, `permission.deny ${commandId}`]); setMessages((current) => [ ...current, { role: 'assistant', text: message }, @@ -2602,10 +1493,6 @@ export function App({ const action = pendingUiConfirmationActionRef.current; pendingUiConfirmationActionRef.current = null; setPendingUiConfirmation(null); - setCommandLog((current) => [ - ...current, - `permission.confirm ${pending.commandId}`, - ]); appendLocalPermissionLog( permissionProjectPath, 'permission.confirm', @@ -2634,26 +1521,13 @@ export function App({ } } if (pending.commandId === 'conversation.read') { - if (pending.detail.includes('Agent 对话')) { - setAgentConversationStatus((current) => - current === '等待确认' ? '已取消读取 Agent 对话' : current, - ); - setAgentMemoryStatus((current) => - current === '等待确认' ? '已取消读取 Agent 对话' : current, - ); - } else { + if (!pending.detail.includes('Agent 对话')) { setWorkspaceStatus((current) => current === '等待确认' ? '已取消读取项目对话' : current, ); } } if (pending.commandId === 'memory.read') { - setMemoryStatus((current) => - current === '等待确认' ? '已取消读取项目记忆' : current, - ); - setAgentMemoryStatus((current) => - current === '等待确认' ? '已取消读取 Agent 私有记忆' : current, - ); if (!pending.detail.includes('Agent 私有记忆')) { setMessages((current) => [ ...current, @@ -2661,30 +1535,6 @@ export function App({ ]); } } - if (pending.commandId === 'memory.write') { - setMemoryStatus((current) => - current === '等待确认' ? '已取消保存项目记忆' : current, - ); - setAgentMemoryStatus((current) => - current === '等待确认' ? '已取消保存 Agent 私有记忆' : current, - ); - } - if ( - pending.commandId === 'file.list' || - pending.commandId === 'file.read' || - pending.commandId === 'agent.trace_read' - ) { - setFileStatus((current) => - current === '等待确认' ? '已取消读取项目文件' : current, - ); - } - if (pending.commandId === 'agent.trace_read') { - setAgentRunStatus((current) => - current === '等待确认' || current === '等待确认读取 Agent trace' - ? '已取消读取 Agent trace' - : current, - ); - } if ( pending.commandId === 'project.status' || pending.commandId === 'task.list' @@ -2693,60 +1543,23 @@ export function App({ pending.commandId === 'task.list' ? '已取消读取任务拆分' : '已取消读取项目状态'; - setProjectStatus(status); setWorkspaceStatus(status); } if (pending.commandId === 'asset.list') { - setAssetStatus('已取消读取项目资产'); setWorkspaceStatus('已取消读取项目资产'); } - if (pending.commandId === 'asset.upload') { - setAssetStatus('已取消上传资产'); - } - if (pending.commandId === 'canvas.project_open') { - setAssetStatus('已取消打开画板'); - } - if (pending.commandId === 'canvas.project_sync') { - setAssetStatus('已取消同步画板项目'); - } - if (pending.commandId === 'canvas.asset_import') { - setAssetStatus('已取消导入画板资产'); - } - if (pending.commandId === 'canvas.asset_generate') { - setAssetStatus('已取消生成美术素材'); - } - if (pending.commandId === 'canvas.export_import') { - setAssetStatus('已取消导入画板导出包'); - } if ( pending.commandId === 'preview.start' || pending.commandId === 'preview.open' || pending.commandId === 'preview.stop' || pending.commandId === 'preview.status' ) { - setPreviewStatus('已取消预览操作'); setWorkspaceStatus('已取消预览操作'); setMessages((current) => [ ...current, { role: 'assistant', text: '已取消预览操作' }, ]); } - if ( - pending.commandId === 'agent.run_status' || - pending.commandId === 'agent.audit' - ) { - setAgentRunStatus( - pending.commandId === 'agent.audit' - ? '已取消 Agent 审计' - : '已取消读取 Agent run 状态', - ); - } - if ( - pending.commandId === 'agent.resume' && - pending.detail.includes('未完成的 Agent Runtime 任务') - ) { - setAgentRunStatus('已取消恢复 Agent Runtime 任务'); - } if ( pending.commandId === 'project.index' || pending.commandId === 'project.diff' || @@ -2758,37 +1571,13 @@ export function App({ : pending.commandId === 'project.diff' ? '已取消对比项目 checkpoint' : '已取消导出本地试玩包'; - setFileStatus(status); setWorkspaceStatus(status); } if (pending.commandId === 'project.create') { - setProjectStatus('已取消'); setWorkspaceStatus('已取消'); } - if (pending.commandId === 'file.write') { - setFileStatus('已取消保存项目文件'); - } - if (pending.commandId === 'file.delete') { - setFileStatus('已取消删除项目文件'); - } - if (pending.commandId === 'memory.write') { - setMemoryStatus('已取消写入项目记忆'); - } - if (pending.commandId === 'memory.delete') { - setMemoryStatus('已取消删除项目记忆'); - } - if (pending.commandId === 'asset.register') { - setAssetStatus('已取消登记资产'); - } - if (pending.commandId === 'command.run_limited') { - setLimitedCommandStatus('已取消运行'); - } pendingUiConfirmationActionRef.current = null; setPendingUiConfirmation(null); - setCommandLog((current) => [ - ...current, - `permission.cancel ${pending.commandId}`, - ]); appendLocalPermissionLog( pending.projectPath ?? resolveUiPermissionLogProjectPath(pending.commandId), @@ -2797,27 +1586,11 @@ export function App({ ); } - function handleRuntimeConfigOpen() { - setRuntimeConfigOpen(true); - } - - function queueRunLocalShortcut() { - if (!requireChatProjectForUserAction()) { - return; - } - queuePendingCommand({ id: 'game.run_local' }); - setMessages((current) => [ - ...current, - { role: 'assistant', text: '准备运行当前本地游戏。' }, - ]); - } - queueRunLocalShortcutRef.current = queueRunLocalShortcut; executeRunLocalRef.current = executeRunLocal; useEffect(() => { const nextProjectPath = localProject?.projectPath; if ( - !projectSupervisorOnly || !playRequest || !nextProjectPath || playRequest.projectPath !== nextProjectPath @@ -2831,116 +1604,13 @@ export function App({ handledPlayRequestRef.current = requestKey; onPlayRequestHandled?.(playRequest.requestId); void executeRunLocalRef.current(true); - }, [ - localProject?.projectPath, - onPlayRequestHandled, - playRequest, - projectSupervisorOnly, - ]); - - function queueStaticSmokeShortcut() { - if (!requireChatProjectForUserAction()) { - return; - } - queuePendingCommand({ - id: 'command.run_limited', - commandId: 'game.static_smoke', - }); - setMessages((current) => [ - ...current, - { role: 'assistant', text: '准备运行静态入口自检。' }, - ]); - } - - function queueProjectCheckpointShortcut() { - if (!requireChatProjectForUserAction()) { - return; - } - queuePendingCommand({ id: 'project.checkpoint' }); - setMessages((current) => [ - ...current, - { role: 'assistant', text: '准备保存当前项目快照。' }, - ]); - } - - function queueProjectExportPackageShortcut() { - if (!requireChatProjectForUserAction()) { - return; - } - queuePendingCommand({ id: 'project.export_package' }); - setMessages((current) => [ - ...current, - { role: 'assistant', text: '准备导出本地试玩包。' }, - ]); - } - - function queuePreviewStartShortcut() { - if (!requireChatProjectForUserAction()) { - return; - } - queuePendingCommand({ id: 'preview.start' }); - setMessages((current) => [ - ...current, - { role: 'assistant', text: '准备启动本地预览。' }, - ]); - } - - function queuePreviewOpenShortcut() { - if (!requireChatProjectForUserAction()) { - return; - } - queuePendingCommand({ id: 'preview.open' }); - setMessages((current) => [ - ...current, - { role: 'assistant', text: '准备打开当前本地预览。' }, - ]); - } - - async function handleOpenLauncherWindow() { - setMessages((current) => [ - ...current, - { role: 'assistant', text: '请回到首页的项目组切换项目。' }, - ]); - } - - async function handleRevealCurrentProjectDirectory() { - const nextProjectPath = resolveChatProjectPath(localProject); - if (!nextProjectPath) { - return; - } - const invoke = resolveTauriInvoke(); - if (!invoke) { - setMessages((current) => [ - ...current, - { role: 'assistant', text: '需要在 Tauri App 内打开项目目录。' }, - ]); - return; - } - try { - await invoke('open_local_project_directory', { - projectPath: nextProjectPath, - }); - setMessages((current) => [ - ...current, - { role: 'assistant', text: '已打开项目目录。' }, - ]); - } catch (error) { - setMessages((current) => [ - ...current, - { - role: 'assistant', - text: error instanceof Error ? error.message : String(error), - }, - ]); - } - } + }, [localProject?.projectPath, onPlayRequestHandled, playRequest]); function requireChatProjectForUserAction() { const nextProjectPath = resolveChatProjectPath(localProject); if (nextProjectPath) { return nextProjectPath; } - setMemoryStatus('请先初始化本地项目'); setMessages((current) => [ ...current, { role: 'assistant', text: '请先用 /project 设置本地项目。' }, @@ -2948,121 +1618,31 @@ export function App({ return null; } - async function handleProjectInit(event: FormEvent) { - event.preventDefault(); - requestCommandConfirmation( - 'project.create', - `创建 ${projectPath}`, - () => void executeProjectCreate(projectPath, false), - ); + /** + * 切换项目作用域时把这条链路自己的聊天状态清干净。 + * + * 策划会话、策划 Runtime 视图与设计 Agent 视图都属于上一个项目;项目身份一变就不 + * 能留到下一个项目里。 + */ + function resetChatState() { + planningRuntimeRef.current = null; + planningV2SessionRef.current = null; + setPlanningRuntime(null); + setProjectChatError(''); + setPlanningV2Session(null); + setPlanningV2TransientReplyTarget(''); + designAgentPendingViewRef.current = null; + setPlanningV2Reasoning(''); + setPlanningV2Active(planningStartMode); + planningV2ActiveRef.current = planningStartMode; + designAgentLaneRef.current = planningStartMode; + designAgentTurnRef.current = null; + setDesignAgentView(null); } - async function resumeProjectSupervisorRuntimeTasksIfNeeded( - invoke: TauriInvoke, - nextProjectPath: string, - ) { - if ( - projectSupervisorRuntimeResumeProjectPathRef.current === nextProjectPath - ) { - return ''; - } - try { - await invoke( - 'resume_game_creator_agent_runtime_tasks', - { projectPath: nextProjectPath }, - ); - projectSupervisorRuntimeResumeProjectPathRef.current = nextProjectPath; - agentRuntimeResumeProjectPathRef.current = nextProjectPath; - return ''; - } catch (error) { - if (isMissingAgentRuntimeResumeCommandError(error)) { - projectSupervisorRuntimeResumeProjectPathRef.current = nextProjectPath; - agentRuntimeResumeProjectPathRef.current = nextProjectPath; - return ''; - } - const message = error instanceof Error ? error.message : String(error); - if ( - message.includes('项目权限策略要求用户确认:agent.resume') || - message.includes('项目权限策略拒绝执行:agent.resume') - ) { - return ''; - } - if (isRuntimeConfigMissingError(message)) { - requestRuntimeConfigOpen(); - } - const runtimeError = `项目总控 Agent 恢复失败:${message}`; - setProjectSupervisorRuntimeError(runtimeError); - return runtimeError; - } - } - - async function readProjectSupervisorActiveSession( - invoke: TauriInvoke, - nextProjectPath: string, - ) { - return readProjectSupervisorActiveSessionId(invoke, nextProjectPath); - } - - async function refreshProjectSupervisorConversation( - invoke: TauriInvoke, - nextProjectPath: string, - sessionId: string, - ) { - const loadVersion = projectSupervisorHistoryLoadVersionRef.current + 1; - projectSupervisorHistoryLoadVersionRef.current = loadVersion; - const [projectConversation, supervisorConversation] = await Promise.all([ - invoke('read_local_conversation', { - projectPath: nextProjectPath, - agentId: null, - }), - invoke('read_local_conversation', { - projectPath: nextProjectPath, - agentId: PROJECT_SUPERVISOR_AGENT_ID, - sessionId, - }), - ]); - if ( - projectSupervisorHistoryLoadVersionRef.current !== loadVersion || - localProjectPathRef.current !== nextProjectPath || - projectSupervisorSessionIdRef.current !== sessionId - ) { - return; - } - const conversationMessages = mergeProjectSupervisorConversation( - projectConversation.messages, - supervisorConversation.messages, - ); - const transientResponse = projectSupervisorResponseStreamRef.current; - if ( - conversationContainsProjectSupervisorResponseStream( - supervisorConversation.messages, - transientResponse, - ) - ) { - projectSupervisorResponseStreamRef.current = null; - setProjectSupervisorResponseStream(null); - } - setConversationVisibleCount(CONVERSATION_INITIAL_VISIBLE_COUNT); - setMessages((current) => { - // 不能整体替换:乐观插入、尚未落盘的用户消息会被冲掉(初始需求看不到就是这个原因)。 - const nextMessages = mergeLoadedConversationWithPendingRuntimeMessages( - conversationMessages, - current, - ); - savedConversationProjectPathRef.current = nextProjectPath; - savedConversationCountRef.current = nextMessages.length; - latestMessagesRef.current = nextMessages; - return nextMessages; - }); - setProjectSupervisorRuntimeError(''); - } - projectSupervisorRefreshConversationRef.current = - refreshProjectSupervisorConversation; - async function loadProjectConversation( nextProjectPath: string, skipPolicyConfirm = false, - mode: 'initial' | 'replace' = 'initial', ) { const invoke = resolveTauriInvoke(); if (!invoke) { @@ -3079,7 +1659,7 @@ export function App({ 'conversation.read', nextProjectPath, `读取 ${nextProjectPath} 的项目对话历史`, - () => void loadProjectConversation(nextProjectPath, true, mode), + () => void loadProjectConversation(nextProjectPath, true), ); setWorkspaceStatus('等待确认'); return; @@ -3088,79 +1668,13 @@ export function App({ return; } } - const loadVersion = projectSupervisorHistoryLoadVersionRef.current + 1; - projectSupervisorHistoryLoadVersionRef.current = loadVersion; + let planningV2: PlanningSessionCommandResultV2 | null = null; try { - // V2 projects do not have a Supervisor run or legacy conversation. Probe the - // V2 authority first; a missing V2 session returns null and preserves the - // existing open-project behavior for older projects. - let planningV2: PlanningSessionCommandResultV2 | null = null; - if (projectSupervisorOnly || planningStartMode) { - try { - const design = await hydrateDesignAgentSession(nextProjectPath); - if (design) { - if ( - projectSupervisorHistoryLoadVersionRef.current !== loadVersion || - localProjectPathRef.current !== nextProjectPath - ) { - return; - } - setWorkspaceStatus((workspaceStatus) => - workspaceStatus === '等待确认' - ? `已打开:${nextProjectPath}` - : workspaceStatus, - ); - return; - } - } catch (error) { - if (planningStartMode) { - setPlanGddError( - `策划会话无法恢复:${error instanceof Error ? error.message : String(error)}`, - ); - } - } - try { - planningV2 = await invoke( - 'hydrate_planning_session_v2', - { projectPath: nextProjectPath }, - ); - } catch (error) { - if (planningStartMode) { - throw error; - } - // Older projects/binaries may not expose V2 yet; continue with the - // legacy read path unless the caller explicitly entered planning V2. - } - } - if (planningStartMode || (projectSupervisorOnly && planningV2)) { - if ( - projectSupervisorHistoryLoadVersionRef.current !== loadVersion || - localProjectPathRef.current !== nextProjectPath - ) { + const design = await hydrateDesignAgentSession(nextProjectPath); + if (design) { + if (localProjectPathRef.current !== nextProjectPath) { return; } - planningV2ActiveRef.current = true; - setPlanningV2Active(true); - if (planningV2) { - designAgentLaneRef.current = false; - setDesignAgentView(null); - applyPlanningV2CommandResult(planningV2); - } else { - designAgentLaneRef.current = true; - planningV2SessionRef.current = null; - setPlanningV2Session(null); - setPlanGddState(null); - projectSupervisorSessionIdRef.current = null; - setProjectSupervisorSessionId(null); - updateProjectSupervisorRuntime(null); - setMessages(createDefaultChatMessages()); - savedConversationProjectPathRef.current = nextProjectPath; - savedConversationCountRef.current = 0; - latestMessagesRef.current = []; - } - setProjectSupervisorRuntimeError( - planningV2?.session.lastError?.summary ?? '', - ); setWorkspaceStatus((workspaceStatus) => workspaceStatus === '等待确认' ? `已打开:${nextProjectPath}` @@ -3168,150 +1682,62 @@ export function App({ ); return; } - const resumeError = directProjectMode - ? '' - : await resumeProjectSupervisorRuntimeTasksIfNeeded( - invoke, - nextProjectPath, - ); - if (directProjectMode) { - projectSupervisorRuntimeResumeProjectPathRef.current = nextProjectPath; - agentRuntimeResumeProjectPathRef.current = nextProjectPath; - } - // Direct Codex owns its app-server conversation identity. The project - // workbench only hydrates the project conversation and must not inspect - // or create a legacy Supervisor Session while opening the project. - const sessionId = directProjectMode - ? null - : await readProjectSupervisorActiveSession(invoke, nextProjectPath); - let runtimeError = ''; - // DirectProject 的项目对话由聊天容器自己的订阅与切片读取拥有:工作台壳既不再 - // 读老的项目对话文件,也不把它混进 Supervisor 的会话回读。 - const resolvedProjectConversation: LocalConversationResult = - directProjectMode - ? { path: nextProjectPath, agentId: null, messages: [] } - : await invoke('read_local_conversation', { - projectPath: nextProjectPath, - agentId: null, - }); - // 首屏历史直接进聊天 reducer;`messages` 在 DirectProject 下只剩运行期本地消息。 - let supervisorConversation: LocalConversationResult | null = null; - let runtime: AgentRuntimeState | null = null; - let runtimeResponseStream: AgentRuntimeResponseStream | null = null; - if (sessionId) { - supervisorConversation = await invoke( - 'read_local_conversation', - { - projectPath: nextProjectPath, - agentId: PROJECT_SUPERVISOR_AGENT_ID, - sessionId, - }, + } catch (error) { + if (planningStartMode) { + setPlanGddError( + `策划会话无法恢复:${error instanceof Error ? error.message : String(error)}`, ); } - if (sessionId) { - try { - const runtimeResult = await invoke( - 'read_game_creator_agent_runtime', - { - projectPath: nextProjectPath, - agentId: PROJECT_SUPERVISOR_AGENT_ID, - sessionId, - }, - ); - runtime = agentRuntimeStateFromResult(runtimeResult); - runtimeResponseStream = runtimeResult.responseStream ?? null; - } catch (error) { - runtimeError = error instanceof Error ? error.message : String(error); - } - } - if ( - projectSupervisorHistoryLoadVersionRef.current !== loadVersion || - localProjectPathRef.current !== nextProjectPath - ) { - return; - } - const conversationMessages = mergeProjectSupervisorConversation( - resolvedProjectConversation.messages, - supervisorConversation?.messages ?? [], + } + try { + planningV2 = await invoke( + 'hydrate_planning_session_v2', + { projectPath: nextProjectPath }, ); - if ( - conversationContainsProjectSupervisorResponseStream( - supervisorConversation?.messages ?? [], - runtimeResponseStream, - ) - ) { - runtimeResponseStream = null; - } - projectSupervisorSessionIdRef.current = sessionId; - setProjectSupervisorSessionId(sessionId); - updateProjectSupervisorRuntime(runtime); - if (runtime) { - updateProjectSupervisorResponseStream(runtimeResponseStream, runtime); - } else { - projectSupervisorResponseStreamRef.current = null; - setProjectSupervisorResponseStream(null); - } - setProjectSupervisorRuntimeError(runtimeError || resumeError); - setMessages((current) => { - // replace 分支同样不能丢掉尚未落盘的运行时消息(初始需求)。 - const nextConversationMessages = - mergeLoadedConversationWithPendingRuntimeMessages( - conversationMessages, - current, - ); - // 空对话(没有默认问候之后的新常态)同样应当接受回读结果。 - const isEmptyConversation = current.length === 0; - const hasOnlyDefaultGreeting = - current.length === 1 && - current[0]?.role === 'assistant' && - current[0]?.text === '想做什么游戏?'; - const hasOnlyOpenStatus = - current.length === 2 && - current[0]?.role === 'assistant' && - current[0]?.text === '想做什么游戏?' && - current[1]?.role === 'assistant' && - current[1]?.text === `已设置本地项目:${nextProjectPath}`; - if ( - mode !== 'replace' && - !isEmptyConversation && - !hasOnlyDefaultGreeting && - !hasOnlyOpenStatus - ) { - return current; - } - setConversationVisibleCount(CONVERSATION_INITIAL_VISIBLE_COUNT); - savedConversationProjectPathRef.current = nextProjectPath; - savedConversationCountRef.current = nextConversationMessages.length; - latestMessagesRef.current = nextConversationMessages; - setWorkspaceStatus((workspaceStatus) => { - if (mode === 'replace') { - return `已读取项目对话历史:${conversationMessages.length} 条`; - } - return workspaceStatus === '等待确认' - ? `已打开:${nextProjectPath}` - : workspaceStatus; - }); - return nextConversationMessages; - }); } catch (error) { - if ( - projectSupervisorHistoryLoadVersionRef.current !== loadVersion || - localProjectPathRef.current !== nextProjectPath - ) { + if (planningStartMode) { + throw error; + } + // Older projects/binaries may not expose V2 yet; fall through to the + // DirectProject path. + } + if (planningStartMode || planningV2) { + if (localProjectPathRef.current !== nextProjectPath) { return; } - const message = error instanceof Error ? error.message : String(error); - if (isRuntimeConfigMissingError(message)) { - requestRuntimeConfigOpen(); + planningV2ActiveRef.current = true; + setPlanningV2Active(true); + if (planningV2) { + designAgentLaneRef.current = false; + setDesignAgentView(null); + applyPlanningV2CommandResult(planningV2); + } else { + designAgentLaneRef.current = true; + planningV2SessionRef.current = null; + setPlanningV2Session(null); + setPlanGddState(null); + updatePlanningRuntime(null); + setMessages(createDefaultChatMessages()); + savedConversationProjectPathRef.current = nextProjectPath; + savedConversationCountRef.current = 0; + latestMessagesRef.current = []; } - setProjectSupervisorRuntimeError(message); + setProjectChatError(planningV2?.session.lastError?.summary ?? ''); setWorkspaceStatus((workspaceStatus) => workspaceStatus === '等待确认' - ? `项目对话读取失败:${message}` + ? `已打开:${nextProjectPath}` : workspaceStatus, ); - // Keep the default greeting when history is missing or blocked. + return; } + // DirectProject 的项目对话由聊天容器自己的订阅与切片读取拥有:工作台壳不读项目 + // 对话文件,也不把历史混进壳自己的 `messages`。 + setProjectChatError(''); + setWorkspaceStatus((workspaceStatus) => + workspaceStatus === '等待确认' + ? `已打开:${nextProjectPath}` + : workspaceStatus, + ); } async function openWorkspace( @@ -3333,7 +1759,6 @@ export function App({ const invoke = resolveTauriInvoke(); if (!invoke) { setWorkspaceStatus('需要在 Tauri App 内运行'); - setProjectStatus('需要在 Tauri App 内运行'); if (announceToChat) { setMessages((current) => [ ...current, @@ -3345,25 +1770,9 @@ export function App({ const projectScopeVersion = projectScopeVersionRef.current + 1; projectScopeVersionRef.current = projectScopeVersion; - resetProjectSupervisorState(); + resetChatState(); updateClientPreview(null); - setPreviewStatus('未启动'); setWorkspaceStatus('正在打开'); - setProjectStatus('正在初始化'); - setSelectedAgent(null); - setAgentConversationInput(''); - setAgentConversationMessages([]); - setAgentConversationRuntime(null); - setAgentConversationSessionId(null); - setAgentConversationRunSubmitMode('steer'); - setAgentConversationRuntimeError(''); - setAgentConversationVisibleCount(CONVERSATION_INITIAL_VISIBLE_COUNT); - setAgentConversationStatus('未选择 agent'); - setAgentConversationSaving(false); - setAgentMemoryStatus('未读取'); - setAgentMemoryContent(''); - agentConversationSavingRef.current = false; - agentConversationLoadVersionRef.current += 1; try { const result = mode === 'open' @@ -3391,7 +1800,6 @@ export function App({ ) { const message = '本地项目路径无效'; setWorkspaceStatus(message); - setProjectStatus(message); if (announceToChat) { setMessages((current) => [ ...current, @@ -3413,16 +1821,9 @@ export function App({ setProjectPath(openedProject.projectPath); setWorkspaceProjectKind(projectKind); setLocalProject(openedProject); - if (supervisorChatOnly) { - setChatInput(readSupervisorChatDraft(openedProject.projectPath)); - } setChatReferences([]); setChatContent([]); setManifest(openedProject.manifest); - setProjectFiles([]); - setProjectCheckpoints([]); - setAgentRunHistory([]); - setAgentRunHistoryFiles([]); setAgentRuntimeById({}); setMessages(conversationMessages); setConversationVisibleCount(CONVERSATION_INITIAL_VISIBLE_COUNT); @@ -3430,7 +1831,6 @@ export function App({ savedConversationCountRef.current = conversationMessages.length; writeRecentWorkspace(openedProject.projectPath); setWorkspaceStatus(`已打开:${openedProject.projectPath}`); - setProjectStatus(mode === 'open' ? '已打开' : '已初始化'); appendLocalPermissionLog( openedProject.projectPath, 'permission.confirm', @@ -3445,9 +1845,6 @@ export function App({ }, ]); } - // Direct Codex must not resume or inspect legacy Supervisor work. Its - // own project conversation is hydrated above; the old run trace remains - // available only in the explicit legacy diagnostic surface. void loadProjectConversation(openedProject.projectPath); if (!directProjectMode) { void refreshAgentRunTrace(openedProject.projectPath); @@ -3458,7 +1855,6 @@ export function App({ } const message = error instanceof Error ? error.message : String(error); setWorkspaceStatus(message); - setProjectStatus(message); if (announceToChat) { setMessages((current) => [ ...current, @@ -3468,200 +1864,6 @@ export function App({ } } - async function openAgentConversation( - agent: AgentStatusCard, - skipConversationPolicyConfirm = false, - skipMemoryPolicyConfirm = false, - ) { - const loadVersion = agentConversationLoadVersionRef.current + 1; - agentConversationLoadVersionRef.current = loadVersion; - setSelectedAgent(agent); - setAgentConversationInput(''); - setAgentConversationMessages([]); - setAgentConversationRuntime(null); - setAgentConversationSessionId(null); - setAgentConversationRunSubmitMode('steer'); - setAgentConversationRuntimeError(''); - setAgentConversationVisibleCount(CONVERSATION_INITIAL_VISIBLE_COUNT); - setAgentMemoryContent(''); - const invoke = resolveTauriInvoke(); - const nextProjectPath = resolveChatProjectPath(localProject); - if (!invoke || !nextProjectPath) { - setAgentConversationStatus('请先初始化本地项目'); - setAgentMemoryStatus('请先初始化本地项目'); - return; - } - try { - const status = await invoke( - 'check_game_creator_llm_config', - ); - if (agentConversationLoadVersionRef.current === loadVersion) { - setLlmConfigStatus(status); - } - } catch { - // Agent conversation history is still useful when config status is unavailable. - } - try { - if ( - !skipConversationPolicyConfirm && - (await queueProjectPolicyConfirmationIfNeeded( - invoke, - 'conversation.read', - nextProjectPath, - `读取 ${agent.title} Agent 对话`, - '准备读取 Agent 对话。', - () => - void openAgentConversation(agent, true, skipMemoryPolicyConfirm), - )) - ) { - setAgentConversationStatus('等待确认'); - setAgentMemoryStatus('等待确认'); - return; - } - } catch (error) { - const message = error instanceof Error ? error.message : String(error); - setAgentConversationStatus(message); - setAgentMemoryStatus(message); - return; - } - setAgentConversationStatus('正在读取'); - setAgentMemoryStatus('正在读取'); - try { - const result = await invoke( - 'read_local_conversation', - { - projectPath: nextProjectPath, - agentId: agent.id, - }, - ); - if (agentConversationLoadVersionRef.current !== loadVersion) { - return; - } - setAgentConversationMessages(result.messages); - setAgentConversationSessionId(result.sessionId ?? null); - try { - const runtime = await invoke( - 'read_game_creator_agent_runtime', - { - projectPath: nextProjectPath, - agentId: agent.id, - ...(result.sessionId ? { sessionId: result.sessionId } : {}), - }, - ); - if (agentConversationLoadVersionRef.current === loadVersion) { - const nextRuntime = agentRuntimeStateFromResult(runtime); - setAgentConversationRuntime(nextRuntime); - rememberAgentRuntimeState(nextRuntime); - setAgentConversationRuntimeError(''); - } - } catch (error) { - if (agentConversationLoadVersionRef.current === loadVersion) { - setAgentConversationRuntime(null); - setAgentConversationRuntimeError( - error instanceof Error ? error.message : String(error), - ); - } - } - setAgentConversationStatus( - `已读取 ${result.messages.length} 条:${result.path}`, - ); - setCommandLog((current) => [...current, 'conversation.read']); - } catch (error) { - if (agentConversationLoadVersionRef.current !== loadVersion) { - return; - } - setAgentConversationMessages([]); - setAgentConversationStatus( - error instanceof Error ? error.message : String(error), - ); - } - try { - if ( - !skipMemoryPolicyConfirm && - (await queueProjectPolicyConfirmationIfNeeded( - invoke, - 'memory.read', - nextProjectPath, - `读取 ${agent.title} Agent 私有记忆`, - '准备读取 Agent 私有记忆。', - () => void openAgentConversation(agent, true, true), - )) - ) { - setAgentMemoryStatus('等待确认'); - return; - } - } catch (error) { - if (agentConversationLoadVersionRef.current !== loadVersion) { - return; - } - setAgentMemoryStatus( - error instanceof Error ? error.message : String(error), - ); - return; - } - try { - const result = await invoke( - 'read_local_agent_memory', - { - projectPath: nextProjectPath, - taskId: agent.id, - }, - ); - if (agentConversationLoadVersionRef.current !== loadVersion) { - return; - } - setAgentMemoryContent(result.content); - setAgentMemoryStatus( - result.exists ? `已读取:${result.path}` : '私有记忆为空', - ); - setCommandLog((current) => [...current, 'memory.agent.read']); - } catch (error) { - if (agentConversationLoadVersionRef.current !== loadVersion) { - return; - } - setAgentMemoryContent(''); - setAgentMemoryStatus( - error instanceof Error ? error.message : String(error), - ); - } - } - - function closeAgentConversation() { - agentConversationLoadVersionRef.current += 1; - agentConversationSavingRef.current = false; - agentConversationBackgroundBusyRef.current = false; - setSelectedAgent(null); - setAgentConversationInput(''); - setAgentConversationMessages([]); - setAgentConversationRuntime(null); - setAgentConversationSessionId(null); - setAgentConversationRunSubmitMode('steer'); - setAgentConversationVisibleCount(CONVERSATION_INITIAL_VISIBLE_COUNT); - setAgentConversationStatus('未选择 agent'); - setAgentConversationSaving(false); - setAgentConversationBackgroundBusy(false); - setAgentMemoryStatus('未读取'); - setAgentMemoryContent(''); - } - - function prepareSuggestedToolCommandDraft( - toolCall: GameCreationAgentToolCallTrace, - ) { - const commandDraft = commandDraftFromSuggestedToolCall(toolCall); - if (!commandDraft) { - return; - } - prepareChatCommandDraft(commandDraft); - closeAgentConversation(); - } - - function prepareChatCommandDraft(commandDraft: string) { - setChatInput(commandDraft); - setChatReferences([]); - setChatContent([]); - window.setTimeout(() => chatInputRef.current?.focus(), 0); - } - function handleChatComposerChange(draft: ChatComposerDraft) { const content = draft.content; setChatContent(content); @@ -3717,2253 +1919,14 @@ export function App({ ); }, []); - function prepareProjectAssetRegisterDraft(localPath: string) { - prepareChatCommandDraft(projectFileActionDrafts(localPath).assetCommand); - } - - function prepareAgentEvidenceReadDraft(localPath: string) { - if (!isSafeProjectRelativePath(localPath)) { - return; - } - prepareChatCommandDraft(projectFileActionDrafts(localPath).readCommand); - closeAgentConversation(); - } - - async function handlePickCanvasExportFileDraft() { - if (!requireChatProjectForUserAction()) { - return; - } - const invoke = resolveTauriInvoke(); - if (!invoke) { - setMessages((current) => [ - ...current, - { role: 'assistant', text: '需要在 Tauri App 内选择画板导出包。' }, - ]); - return; - } - try { - const selectedPath = await invoke('pick_local_file'); - if (!selectedPath) { - setMessages((current) => [ - ...current, - { role: 'assistant', text: '已取消选择画板导出包。' }, - ]); - return; - } - setCanvasExportPath(selectedPath); - prepareChatCommandDraft(`/import-canvas-export ${selectedPath} `); - setAssetStatus(`已选择画板导出包:${selectedPath}`); - setMessages((current) => [ - ...current, - { role: 'assistant', text: `已选择画板导出包:${selectedPath}` }, - ]); - } catch (error) { - const message = error instanceof Error ? error.message : String(error); - setAssetStatus(message); - setMessages((current) => [ - ...current, - { role: 'assistant', text: message }, - ]); - } - } - - async function saveAgentConversationMessage( - agent: AgentStatusCard, - content: string, - skipPolicyConfirm = false, - ) { - if (!agent || !content || agentConversationSavingRef.current) { - return; - } - if (agentRuntimeNeedsUserInput(agentConversationRuntime)) { - setAgentConversationStatus('请先回答 Agent 当前的澄清问题'); - return; - } - const invoke = resolveTauriInvoke(); - const nextProjectPath = resolveChatProjectPath(localProject); - if (!invoke) { - setAgentConversationStatus('需要在 Tauri App 内运行'); - return; - } - if (!nextProjectPath) { - setAgentConversationStatus('请先初始化本地项目'); - return; - } - const llmWarning = formatAgentLlmConfigWarning(llmConfigStatus, agent); - if (llmWarning) { - setAgentConversationStatus(llmWarning); - return; - } - const saveVersion = agentConversationLoadVersionRef.current; - try { - if ( - !skipPolicyConfirm && - (await queueProjectPolicyConfirmationIfNeeded( - invoke, - 'conversation.write', - nextProjectPath, - `写入 ${agent.title} Agent 对话`, - '准备保存 Agent 对话。', - () => void saveAgentConversationMessage(agent, content, true), - )) - ) { - setAgentConversationStatus('等待确认'); - return; - } - } catch (error) { - setAgentConversationStatus( - error instanceof Error ? error.message : String(error), - ); - return; - } - agentConversationSavingRef.current = true; - setAgentConversationSaving(true); - setAgentConversationInput(''); - setAgentConversationStatus('正在保存用户消息'); - let savedUserResult: LocalConversationResult | null = null; - let stopStreamListen: (() => void) | null = null; - let streamListenDisposed = false; - let streamListenReady = false; - try { - savedUserResult = await invoke( - 'append_local_conversation_message', - { - projectPath: nextProjectPath, - agentId: agent.id, - message: { - role: 'user', - content, - agentId: null, - }, - }, - ); - if (agentConversationLoadVersionRef.current !== saveVersion) { - return; - } - const savedUserMessages = savedUserResult.messages; - setAgentConversationMessages(savedUserMessages); - setAgentConversationStatus('正在连接 Agent LLM'); - const streamRunId = createAgentChatRunId('agent-conversation'); - if (canSubscribeTauriEvents()) { - try { - stopStreamListen = - await subscribeTauriEvent( - 'game-creator-role-agent-chat-stream', - (event) => { - const payload = event.payload; - if ( - payload.projectPath !== nextProjectPath || - payload.agentId !== agent.id || - payload.runId !== streamRunId || - agentConversationLoadVersionRef.current !== saveVersion - ) { - return; - } - if (payload.runtimeState) { - setAgentConversationRuntime((current) => { - const nextRuntime = normalizeAgentRuntimeState( - payload.runtimeState!, - current, - ); - rememberAgentRuntimeState(nextRuntime); - return nextRuntime; - }); - setAgentConversationRuntimeError(''); - } - if (payload.status === 'started') { - setAgentConversationStatus( - payload.runtimeSummary ?? 'Agent 已连接,正在等待回复', - ); - return; - } - if (payload.status === 'delta') { - const draftText = - payload.accumulatedText || payload.deltaText; - if (draftText) { - setAgentConversationMessages([ - ...savedUserMessages, - createLocalConversationDraftMessage(draftText), - ]); - } - setAgentConversationStatus( - payload.finishReason - ? `Agent 回复结束:${payload.finishReason}` - : '正在接收 Agent 回复', - ); - return; - } - if (payload.status === 'completed') { - setAgentConversationStatus( - payload.runtimeSummary ?? 'Agent 回复完成,正在保存', - ); - return; - } - if (payload.status === 'failed') { - setAgentConversationStatus( - payload.runtimeSummary ?? - 'Agent 流式回复失败,正在记录错误', - ); - } - }, - ); - streamListenReady = true; - if (streamListenDisposed) { - stopStreamListen(); - stopStreamListen = null; - } - } catch { - setAgentConversationStatus('实时状态不可用,正在使用普通回复模式'); - } - } - const reply = streamListenReady - ? await invoke( - 'chat_with_game_creator_role_agent_stream', - { - projectPath: nextProjectPath, - agentId: agent.id, - prompt: content, - runId: streamRunId, - }, - ) - : await invoke( - 'chat_with_game_creator_role_agent', - { - projectPath: nextProjectPath, - agentId: agent.id, - prompt: content, - }, - ); - if (agentConversationLoadVersionRef.current !== saveVersion) { - return; - } - try { - const runtime = await invoke( - 'read_game_creator_agent_runtime', - { - projectPath: nextProjectPath, - agentId: agent.id, - }, - ); - if (agentConversationLoadVersionRef.current === saveVersion) { - const nextRuntime = agentRuntimeStateFromResult(runtime); - setAgentConversationRuntime(nextRuntime); - rememberAgentRuntimeState(nextRuntime); - setAgentConversationRuntimeError(''); - } - } catch (error) { - if (agentConversationLoadVersionRef.current === saveVersion) { - setAgentConversationRuntimeError( - error instanceof Error ? error.message : String(error), - ); - } - } - setAgentConversationStatus('正在保存 Agent 回复'); - setAgentConversationMessages([ - ...savedUserMessages, - createLocalConversationDraftMessage(reply.replyText), - ]); - const assistantResult = await invoke( - 'append_local_conversation_message', - { - projectPath: nextProjectPath, - agentId: agent.id, - message: { - role: 'assistant', - content: reply.replyText, - agentId: null, - }, - }, - ); - if (agentConversationLoadVersionRef.current !== saveVersion) { - return; - } - setAgentConversationMessages(assistantResult.messages); - setAgentConversationStatus( - `已保存 ${assistantResult.messages.length} 条:${assistantResult.path}`, - ); - setCommandLog((current) => [...current, 'conversation.write']); - } catch (error) { - if (agentConversationLoadVersionRef.current !== saveVersion) { - return; - } - void captureAgentRuntimeError(error, agent.id); - if (savedUserResult) { - const message = `已保存用户消息;Agent 回复失败:${ - error instanceof Error ? error.message : String(error) - }`; - try { - const errorResult = await invoke( - 'append_local_conversation_message', - { - projectPath: nextProjectPath, - agentId: agent.id, - message: { - role: 'assistant', - content: message, - agentId: null, - }, - }, - ); - if (agentConversationLoadVersionRef.current !== saveVersion) { - return; - } - setAgentConversationMessages(errorResult.messages); - } catch { - setAgentConversationMessages([ - ...savedUserResult.messages, - { - schemaVersion: 'game-creator-conversation.v1', - role: 'assistant', - content: message, - agentId: null, - updatedAt: Date.now(), - }, - ]); - } - setAgentConversationStatus(message); - } else { - setAgentConversationInput(content); - setAgentConversationStatus( - error instanceof Error ? error.message : String(error), - ); - } - } finally { - streamListenDisposed = true; - stopStreamListen?.(); - agentConversationSavingRef.current = false; - setAgentConversationSaving(false); - } - } - - async function startSelectedAgentBackgroundTask( - agent: AgentStatusCard, - content: string, - skipPolicyConfirm = false, - submitMode = agentConversationRunSubmitMode, - ) { - if (!agent || !content || agentConversationBackgroundBusyRef.current) { - return; - } - if (agentRuntimeNeedsUserInput(agentConversationRuntime)) { - setAgentConversationStatus('请先回答 Agent 当前的澄清问题'); - return; - } - const invoke = resolveTauriInvoke(); - const nextProjectPath = resolveChatProjectPath(localProject); - if (!invoke) { - setAgentConversationStatus('需要在 Tauri App 内运行'); - return; - } - if (!nextProjectPath) { - setAgentConversationStatus('请先初始化本地项目'); - return; - } - const llmWarning = formatAgentLlmConfigWarning(llmConfigStatus, agent); - if (llmWarning) { - setAgentConversationStatus(llmWarning); - return; - } - const steerRuntime = - submitMode === 'steer' - ? matchingAgentRuntimeForSteer( - [agentConversationRuntime], - agent.id, - agentConversationSessionId, - ) - : null; - const saveVersion = agentConversationLoadVersionRef.current; - try { - if ( - !skipPolicyConfirm && - (await queueProjectPolicyConfirmationIfNeeded( - invoke, - 'conversation.write', - nextProjectPath, - steerRuntime - ? `向 ${agent.title} 当前 Run 追加指令` - : `启动 ${agent.title} 后台任务`, - steerRuntime - ? '准备向当前 Agent Run 追加指令。' - : '准备启动 Agent 后台任务。', - () => - void startSelectedAgentBackgroundTask( - agent, - content, - true, - submitMode, - ), - )) - ) { - setAgentConversationStatus('等待确认'); - return; - } - } catch (error) { - setAgentConversationStatus( - error instanceof Error ? error.message : String(error), - ); - return; - } - agentConversationBackgroundBusyRef.current = true; - setAgentConversationBackgroundBusy(true); - if (steerRuntime) { - setAgentConversationStatus( - `正在向当前 Run 追加指令:${steerRuntime.runId}`, - ); - } else { - setAgentConversationInput(''); - setAgentConversationStatus('正在启动 Agent 后台任务'); - } - try { - let runtime: AgentRuntimeResult; - let successStatus: string; - if (steerRuntime) { - const steer = await invoke( - 'steer_game_creator_agent_runtime_task', - { - projectPath: nextProjectPath, - agentId: agent.id, - sessionId: steerRuntime.sessionId, - runId: steerRuntime.runId, - steerId: createAgentChatRunId('agent-steer'), - instruction: content, - }, - ); - runtime = steer.runtime; - successStatus = agentRuntimeSteerStatus(steer); - } else { - runtime = await invoke( - 'start_game_creator_agent_runtime_task', - { - projectPath: nextProjectPath, - agentId: agent.id, - task: content, - runId: createAgentChatRunId('agent-background-task'), - ...(agentConversationSessionId - ? { sessionId: agentConversationSessionId } - : {}), - }, - ); - successStatus = agentRuntimeStartStatus(runtime); - } - if (agentConversationLoadVersionRef.current !== saveVersion) { - return; - } - const nextRuntime = agentRuntimeStateFromResult(runtime); - setAgentConversationInput(''); - setAgentConversationRunSubmitMode('steer'); - setAgentConversationSessionId(nextRuntime.sessionId); - setAgentConversationRuntime(nextRuntime); - rememberAgentRuntimeState(nextRuntime); - setAgentConversationRuntimeError(''); - try { - const conversation = await invoke( - 'read_local_conversation', - { - projectPath: nextProjectPath, - agentId: agent.id, - sessionId: nextRuntime.sessionId, - }, - ); - if (agentConversationLoadVersionRef.current !== saveVersion) { - return; - } - setAgentConversationMessages(conversation.messages); - setAgentConversationSessionId( - conversation.sessionId ?? nextRuntime.sessionId, - ); - } catch (error) { - if (agentConversationLoadVersionRef.current === saveVersion) { - setAgentConversationRuntimeError( - `对话刷新失败:${ - error instanceof Error ? error.message : String(error) - }`, - ); - } - } - setAgentConversationStatus(successStatus); - setCommandLog((current) => [ - ...current, - steerRuntime ? 'agent.runtime.steer' : 'agent.runtime.background_task', - ]); - } catch (error) { - if (agentConversationLoadVersionRef.current !== saveVersion) { - return; - } - setAgentConversationInput(content); - setAgentConversationStatus( - error instanceof Error ? error.message : String(error), - ); - } finally { - agentConversationBackgroundBusyRef.current = false; - setAgentConversationBackgroundBusy(false); - } - } - - async function cancelSelectedAgentRuntimeTask( - agent: AgentStatusCard, - runId: string, - ) { - if (!agent || !runId || agentConversationBackgroundBusyRef.current) { - return; - } - const invoke = resolveTauriInvoke(); - const nextProjectPath = resolveChatProjectPath(localProject); - if (!invoke) { - setAgentConversationStatus('需要在 Tauri App 内运行'); - return; - } - if (!nextProjectPath) { - setAgentConversationStatus('请先初始化本地项目'); - return; - } - const saveVersion = agentConversationLoadVersionRef.current; - agentConversationBackgroundBusyRef.current = true; - setAgentConversationBackgroundBusy(true); - setAgentConversationStatus('正在取消 Agent 后台任务'); - try { - const runtime = await invoke( - 'cancel_game_creator_agent_runtime_task', - { - projectPath: nextProjectPath, - agentId: agent.id, - runId, - }, - ); - if (agentConversationLoadVersionRef.current !== saveVersion) { - return; - } - const nextRuntime = agentRuntimeStateFromResult(runtime); - setAgentConversationRuntime(nextRuntime); - rememberAgentRuntimeState(nextRuntime); - setAgentConversationRuntimeError(''); - setAgentConversationStatus(agentRuntimeCancelStatus(nextRuntime, runId)); - setCommandLog((current) => [ - ...current, - 'agent.runtime.background_task.cancel', - ]); - } catch (error) { - if (agentConversationLoadVersionRef.current !== saveVersion) { - return; - } - setAgentConversationStatus( - error instanceof Error ? error.message : String(error), - ); - } finally { - agentConversationBackgroundBusyRef.current = false; - setAgentConversationBackgroundBusy(false); - } - } - - async function retrySelectedAgentRuntimeTask( - agent: AgentStatusCard, - runId: string, - ) { - if (!agent || !runId || agentConversationBackgroundBusyRef.current) { - return; - } - const invoke = resolveTauriInvoke(); - const nextProjectPath = resolveChatProjectPath(localProject); - if (!invoke) { - setAgentConversationStatus('需要在 Tauri App 内运行'); - return; - } - if (!nextProjectPath) { - setAgentConversationStatus('请先初始化本地项目'); - return; - } - const llmWarning = formatAgentLlmConfigWarning(llmConfigStatus, agent); - if (llmWarning) { - setAgentConversationStatus(llmWarning); - return; - } - const saveVersion = agentConversationLoadVersionRef.current; - agentConversationBackgroundBusyRef.current = true; - setAgentConversationBackgroundBusy(true); - setAgentConversationStatus('正在重试 Agent 后台任务'); - try { - const runtime = await invoke( - 'retry_game_creator_agent_runtime_task', - { - projectPath: nextProjectPath, - agentId: agent.id, - runId, - nextRunId: createAgentChatRunId('agent-background-retry'), - }, - ); - if (agentConversationLoadVersionRef.current !== saveVersion) { - return; - } - const nextRuntime = agentRuntimeStateFromResult(runtime); - setAgentConversationRuntime(nextRuntime); - rememberAgentRuntimeState(nextRuntime); - setAgentConversationRuntimeError(''); - const conversation = await invoke( - 'read_local_conversation', - { - projectPath: nextProjectPath, - agentId: agent.id, - }, - ); - if (agentConversationLoadVersionRef.current !== saveVersion) { - return; - } - setAgentConversationMessages(conversation.messages); - setAgentConversationStatus(agentRuntimeStartStatus(runtime)); - setCommandLog((current) => [ - ...current, - 'agent.runtime.background_task.retry', - ]); - } catch (error) { - if (agentConversationLoadVersionRef.current !== saveVersion) { - return; - } - setAgentConversationStatus( - error instanceof Error ? error.message : String(error), - ); - } finally { - agentConversationBackgroundBusyRef.current = false; - setAgentConversationBackgroundBusy(false); - } - } - - async function confirmSelectedAgentRuntimeTask( - agent: AgentStatusCard, - runId: string, - actionId: string, - ) { - if ( - !agent || - !runId || - !actionId || - agentConversationBackgroundBusyRef.current - ) { - return; - } - const invoke = resolveTauriInvoke(); - const nextProjectPath = resolveChatProjectPath(localProject); - if (!invoke) { - setAgentConversationStatus('需要在 Tauri App 内运行'); - return; - } - if (!nextProjectPath) { - setAgentConversationStatus('请先初始化本地项目'); - return; - } - const llmWarning = formatAgentLlmConfigWarning(llmConfigStatus, agent); - if (llmWarning) { - setAgentConversationStatus(llmWarning); - return; - } - const saveVersion = agentConversationLoadVersionRef.current; - agentConversationBackgroundBusyRef.current = true; - setAgentConversationBackgroundBusy(true); - setAgentConversationStatus('正在确认并继续 Agent 后台任务'); - try { - const runtime = await invoke( - 'confirm_game_creator_agent_runtime_task', - { - projectPath: nextProjectPath, - agentId: agent.id, - runId, - actionId, - note: '开发者已确认待执行工具动作', - }, - ); - if (agentConversationLoadVersionRef.current !== saveVersion) { - return; - } - const nextRuntime = agentRuntimeStateFromResult(runtime); - setAgentConversationRuntime(nextRuntime); - rememberAgentRuntimeState(nextRuntime); - setAgentConversationRuntimeError(''); - const conversation = await invoke( - 'read_local_conversation', - { - projectPath: nextProjectPath, - agentId: agent.id, - }, - ); - if (agentConversationLoadVersionRef.current !== saveVersion) { - return; - } - setAgentConversationMessages(conversation.messages); - setAgentConversationStatus(agentRuntimeStartStatus(runtime)); - setCommandLog((current) => [ - ...current, - 'agent.runtime.tool_confirmation.approved', - ]); - } catch (error) { - if (agentConversationLoadVersionRef.current !== saveVersion) { - return; - } - setAgentConversationStatus( - error instanceof Error ? error.message : String(error), - ); - } finally { - agentConversationBackgroundBusyRef.current = false; - setAgentConversationBackgroundBusy(false); - } - } - - async function rejectSelectedAgentRuntimeTask( - agent: AgentStatusCard, - runId: string, - actionId: string, - ) { - if ( - !agent || - !runId || - !actionId || - agentConversationBackgroundBusyRef.current - ) { - return; - } - const invoke = resolveTauriInvoke(); - const nextProjectPath = resolveChatProjectPath(localProject); - if (!invoke) { - setAgentConversationStatus('需要在 Tauri App 内运行'); - return; - } - if (!nextProjectPath) { - setAgentConversationStatus('请先初始化本地项目'); - return; - } - const llmWarning = formatAgentLlmConfigWarning(llmConfigStatus, agent); - if (llmWarning) { - setAgentConversationStatus(llmWarning); - return; - } - const saveVersion = agentConversationLoadVersionRef.current; - agentConversationBackgroundBusyRef.current = true; - setAgentConversationBackgroundBusy(true); - setAgentConversationStatus('正在拒绝工具动作并继续 Agent 后台任务'); - try { - const runtime = await invoke( - 'reject_game_creator_agent_runtime_task', - { - projectPath: nextProjectPath, - agentId: agent.id, - runId, - actionId, - note: '开发者拒绝待执行工具动作', - }, - ); - if (agentConversationLoadVersionRef.current !== saveVersion) { - return; - } - const nextRuntime = agentRuntimeStateFromResult(runtime); - setAgentConversationRuntime(nextRuntime); - rememberAgentRuntimeState(nextRuntime); - setAgentConversationRuntimeError(''); - const conversation = await invoke( - 'read_local_conversation', - { - projectPath: nextProjectPath, - agentId: agent.id, - }, - ); - if (agentConversationLoadVersionRef.current !== saveVersion) { - return; - } - setAgentConversationMessages(conversation.messages); - setAgentConversationStatus(agentRuntimeStartStatus(runtime)); - setCommandLog((current) => [ - ...current, - 'agent.runtime.tool_confirmation.rejected', - ]); - } catch (error) { - if (agentConversationLoadVersionRef.current !== saveVersion) { - return; - } - setAgentConversationStatus( - error instanceof Error ? error.message : String(error), - ); - } finally { - agentConversationBackgroundBusyRef.current = false; - setAgentConversationBackgroundBusy(false); - } - } - - async function answerSelectedAgentRuntimeUserInput( - agent: AgentStatusCard, - request: AgentRuntimeUserInputRequest, - responseId: string, - answers: Record, - ) { - const invoke = resolveTauriInvoke(); - const nextProjectPath = resolveChatProjectPath(localProject); - const currentRequest = agentConversationRuntime?.userInputRequest; - if ( - !invoke || - !nextProjectPath || - !responseId || - agentConversationBackgroundBusyRef.current - ) { - return; - } - if ( - request.agentId !== agent.id || - request.sessionId !== agentConversationSessionId || - request.runId !== agentConversationRuntime?.runId || - currentRequest?.requestId !== request.requestId || - currentRequest?.actionId !== request.actionId - ) { - setAgentConversationRuntimeError('待回答请求已变更,请刷新 Runtime 状态'); - return; - } - const saveVersion = agentConversationLoadVersionRef.current; - agentConversationBackgroundBusyRef.current = true; - setAgentConversationBackgroundBusy(true); - setAgentConversationRuntimeError(''); - setAgentConversationStatus('正在提交回答并继续当前 Run'); - try { - const result = await invoke( - 'answer_game_creator_agent_runtime_user_input', - { - projectPath: nextProjectPath, - agentId: agent.id, - runId: request.runId, - actionId: request.actionId, - requestId: request.requestId, - responseId, - answers, - }, - ); - if (agentConversationLoadVersionRef.current !== saveVersion) { - return; - } - const runtimeState = agentRuntimeStateFromResult( - result, - agentConversationRuntime, - ); - if ( - runtimeState.agentId !== agent.id || - runtimeState.sessionId !== request.sessionId || - runtimeState.runId !== request.runId - ) { - throw new Error('Agent 回答后 Runtime 身份不匹配'); - } - setAgentConversationRuntime(runtimeState); - rememberAgentRuntimeState(runtimeState); - setAgentConversationSessionId(runtimeState.sessionId); - const conversation = await invoke( - 'read_local_conversation', - { - projectPath: nextProjectPath, - agentId: agent.id, - sessionId: runtimeState.sessionId, - }, - ); - if (agentConversationLoadVersionRef.current !== saveVersion) { - return; - } - setAgentConversationMessages(conversation.messages); - setAgentConversationSessionId( - conversation.sessionId ?? runtimeState.sessionId, - ); - setAgentConversationRuntimeError(''); - setAgentConversationStatus(agentRuntimeConversationStatus(runtimeState)); - setCommandLog((current) => [ - ...current, - 'agent.runtime.user_input.answered', - ]); - } catch (error) { - if (agentConversationLoadVersionRef.current !== saveVersion) { - return; - } - const message = error instanceof Error ? error.message : String(error); - setAgentConversationRuntimeError(message); - setAgentConversationStatus(`回答提交失败:${message}`); - } finally { - agentConversationBackgroundBusyRef.current = false; - setAgentConversationBackgroundBusy(false); - } - } - - async function saveSelectedAgentPrivateMemory( - agent: AgentStatusCard, - content: string, - skipPolicyConfirm = false, - ) { - if (!agent || !content || agentConversationSavingRef.current) { - return; - } - const invoke = resolveTauriInvoke(); - const nextProjectPath = resolveChatProjectPath(localProject); - if (!invoke) { - setAgentMemoryStatus('需要在 Tauri App 内运行'); - return; - } - if (!nextProjectPath) { - setAgentMemoryStatus('请先初始化本地项目'); - return; - } - const saveVersion = agentConversationLoadVersionRef.current; - try { - if (!skipPolicyConfirm) { - const policyView = await invoke( - 'read_project_permission_policy', - { projectPath: nextProjectPath }, - ); - if (policyView.policy.deniedCommands.includes('memory.write')) { - const message = '项目权限策略拒绝执行:memory.write'; - markProjectPolicyDenied('memory.write', message); - setAgentMemoryStatus(message); - setCommandLog((current) => [ - ...current, - 'permission.deny memory.write', - ]); - setMessages((current) => [ - ...current, - { role: 'assistant', text: message }, - ]); - return; - } - if (policyView.policy.confirmCommands.includes('memory.write')) { - requestProjectPolicyConfirmation( - 'memory.write', - nextProjectPath, - `写入 ${agent.title} Agent 私有记忆`, - () => void saveSelectedAgentPrivateMemory(agent, content, true), - ); - setAgentMemoryStatus('等待确认'); - setMessages((current) => [ - ...current, - { role: 'assistant', text: '准备保存 Agent 私有记忆。' }, - ]); - return; - } - } - } catch (error) { - setAgentMemoryStatus( - error instanceof Error ? error.message : String(error), - ); - return; - } - agentConversationSavingRef.current = true; - setAgentConversationSaving(true); - setAgentConversationInput(''); - setAgentMemoryStatus('正在保存'); - try { - const result = await invoke( - 'write_local_agent_memory', - { - projectPath: nextProjectPath, - taskId: agent.id, - content: appendMemoryContent(agentMemoryContent, content), - }, - ); - if (agentConversationLoadVersionRef.current !== saveVersion) { - return; - } - setAgentMemoryContent(result.content); - setAgentMemoryStatus(`已追加私有记忆:${result.path}`); - setAgentConversationStatus(`已写入 ${agent.title} 私有记忆。`); - setCommandLog((current) => [...current, 'memory.agent.write']); - } catch (error) { - if (agentConversationLoadVersionRef.current !== saveVersion) { - return; - } - setAgentConversationInput(content); - setAgentMemoryStatus( - error instanceof Error ? error.message : String(error), - ); - } finally { - agentConversationSavingRef.current = false; - setAgentConversationSaving(false); - } - } - - function handleAgentConversationSubmit(event: FormEvent) { - event.preventDefault(); - const agent = selectedAgent; - const content = agentConversationInput.trim(); - if (agentRuntimeNeedsUserInput(agentConversationRuntime)) { - setAgentConversationStatus('请先回答 Agent 当前的澄清问题'); - return; - } - if (!agent || !content || agentConversationSavingRef.current) { - return; - } - void saveAgentConversationMessage(agent, content); - } - - function handleAgentPrivateMemorySubmit() { - const agent = selectedAgent; - const content = agentConversationInput.trim(); - if (!agent || !content || agentConversationSavingRef.current) { - return; - } - void saveSelectedAgentPrivateMemory(agent, content); - } - - function handleAgentBackgroundTaskSubmit() { - const agent = selectedAgent; - const content = agentConversationInput.trim(); - if (agentRuntimeNeedsUserInput(agentConversationRuntime)) { - setAgentConversationStatus('请先回答 Agent 当前的澄清问题'); - return; - } - if (!agent || !content || agentConversationBackgroundBusyRef.current) { - return; - } - void startSelectedAgentBackgroundTask(agent, content); - } - - function showChatHelp() { - setCommandLog((current) => [...current, 'help.show']); - setMessages((current) => [ - ...current, - { - role: 'assistant', - text: `可用命令:\n${chatCommandHelp.join('\n')}`, - }, - ]); - } - - async function handleChatSubmit(event: FormEvent) { - event.preventDefault(); - const prompt = chatInput.trim(); - const references = chatReferences; - if (agentRuntimeNeedsUserInput(projectSupervisorRuntimeRef.current)) { - setProjectSupervisorRuntimeError('请先回答项目总控 Agent 当前的澄清问题'); - return; - } - if ((!prompt && references.length === 0) || chatAgentBusy) { - return; - } - - setChatInput(''); - setChatReferences([]); - setChatContent([]); - setMessages((current) => [ - ...current, - { - role: 'user', - text: prompt, - runtimeOwned: !prompt.startsWith('/'), - }, - ]); - if (prompt === '/help') { - showChatHelp(); - return; - } - - if (prompt === '/config') { - handleRuntimeConfigOpen(); - return; - } - - if (prompt === '/capabilities' || prompt === '/能力') { - void executeAgentCapabilitiesChat(); - return; - } - - if (prompt === '/audit' || prompt === '/审计') { - void executeAgentAuditChat(); - return; - } - - const missingArgumentMessage = missingChatCommandArgumentMessage(prompt); - if (missingArgumentMessage) { - setMessages((current) => [ - ...current, - { role: 'assistant', text: missingArgumentMessage }, - ]); - return; - } - - if (prompt.startsWith('/project ')) { - const nextProjectPath = prompt.slice('/project '.length).trim(); - if (!nextProjectPath || !isAbsoluteProjectPath(nextProjectPath)) { - setMessages((current) => [ - ...current, - { role: 'assistant', text: '请提供本地项目绝对路径。' }, - ]); - return; - } - if (projectPathHasControlCharacter(nextProjectPath)) { - setMessages((current) => [ - ...current, - { role: 'assistant', text: '本地项目路径不能包含控制字符。' }, - ]); - return; - } - queuePendingCommand({ - id: 'project.create', - projectPath: nextProjectPath, - }); - setMessages((current) => [ - ...current, - { role: 'assistant', text: `准备初始化本地项目:${nextProjectPath}` }, - ]); - return; - } - - if (prompt.startsWith('/generate ') || prompt.startsWith('/draft ')) { - if (!requireChatProjectForUserAction()) { - return; - } - const generationPrompt = prompt - .slice( - prompt.startsWith('/generate ') - ? '/generate '.length - : '/draft '.length, - ) - .trim(); - if (!generationPrompt) { - setMessages((current) => [ - ...current, - { role: 'assistant', text: '格式:/generate 创作想法' }, - ]); - return; - } - queuePendingCommand({ - id: 'game.generate_draft', - prompt: generationPrompt, - }); - setMessages((current) => [ - ...current, - { - role: 'assistant', - text: `准备生成本地游戏草案:${generationPrompt}`, - }, - ]); - return; - } - - if (prompt === '/status') { - void executeProjectStatus(true); - return; - } - - if ( - handleProjectSummaryChatCommand({ - agentRunHistory, - agentRunTrace, - agentStatusCards, - manifest, - prompt, - requireChatProjectForUserAction, - setMessages, - }) - ) { - return; - } - - if (prompt === '/index') { - void queueOrExecuteProjectIndex(); - return; - } - - if (prompt === '/checkpoint') { - if (!requireChatProjectForUserAction()) { - return; - } - queuePendingCommand({ id: 'project.checkpoint' }); - setMessages((current) => [ - ...current, - { role: 'assistant', text: '准备保存当前项目快照。' }, - ]); - return; - } - - if (prompt === '/checkpoints') { - void executeProjectCheckpoints(true); - return; - } - - if (prompt.startsWith('/diff ')) { - const checkpointId = prompt.slice('/diff '.length).trim(); - if (!checkpointId) { - setMessages((current) => [ - ...current, - { role: 'assistant', text: '格式:/diff checkpoint-id' }, - ]); - return; - } - if (!isSafeCheckpointId(checkpointId)) { - setMessages((current) => [ - ...current, - { role: 'assistant', text: 'checkpoint id 非法。' }, - ]); - return; - } - void executeProjectDiff(checkpointId, true); - return; - } - - if (prompt.startsWith('/restore ')) { - if (!requireChatProjectForUserAction()) { - return; - } - const checkpointId = prompt.slice('/restore '.length).trim(); - if (!checkpointId) { - setMessages((current) => [ - ...current, - { role: 'assistant', text: '格式:/restore checkpoint-id' }, - ]); - return; - } - if (!isSafeCheckpointId(checkpointId)) { - setMessages((current) => [ - ...current, - { role: 'assistant', text: 'checkpoint id 非法。' }, - ]); - return; - } - queuePendingCommand({ id: 'project.restore', checkpointId }); - setMessages((current) => [ - ...current, - { role: 'assistant', text: `准备回滚到 checkpoint:${checkpointId}` }, - ]); - return; - } - - if (prompt === '/policy') { - void executeProjectPolicyRead(true); - return; - } - - if ( - prompt.startsWith('/policy-deny ') || - prompt.startsWith('/policy-allow ') || - prompt.startsWith('/policy-confirm ') || - prompt.startsWith('/policy-auto ') - ) { - if (!requireChatProjectForUserAction()) { - return; - } - const mode = prompt.startsWith('/policy-deny ') - ? 'deny' - : prompt.startsWith('/policy-allow ') - ? 'allow' - : prompt.startsWith('/policy-confirm ') - ? 'confirm' - : 'auto'; - const commandPrefix = - mode === 'deny' - ? '/policy-deny ' - : mode === 'allow' - ? '/policy-allow ' - : mode === 'confirm' - ? '/policy-confirm ' - : '/policy-auto '; - const commandId = prompt.slice(commandPrefix.length).trim(); - if (!commandId) { - setMessages((current) => [ - ...current, - { - role: 'assistant', - text: - mode === 'deny' || mode === 'allow' - ? '格式:/policy-deny file.write' - : '格式:/policy-confirm project.index', - }, - ]); - return; - } - if (!isRegisteredGameCreationCommandId(commandId)) { - setMessages((current) => [ - ...current, - { role: 'assistant', text: `未知内置命令:${commandId}` }, - ]); - return; - } - if ( - (mode === 'confirm' || mode === 'auto') && - !isProjectPolicyConfirmableCommandId(commandId) - ) { - setMessages((current) => [ - ...current, - { - role: 'assistant', - text: '当前仅支持确认 project.index、project.status、project.bootstrap、project.checkpoint、project.diff、project.restore、project.export_package、project.export_list、file.list、file.read、memory.read、memory.write、memory.delete、asset.register、asset.list、task.list、task.create、task.update、agent.run_status、agent.kill、agent.retry、agent.resume、agent.delegate、agent.spawn_isolated、agent.schedule_ready、agent.audit、agent.trace_read、preview.status、preview.start、preview.validate、preview.open、preview.stop、canvas.project_sync、canvas.asset_import、canvas.asset_generate、canvas.export_import、conversation.read 和 conversation.write。', - }, - ]); - return; - } - void queueProjectPolicyMutation(commandId, mode); - return; - } - - if ( - prompt.startsWith('/agent-policy-deny ') || - prompt.startsWith('/agent-policy-allow ') || - prompt.startsWith('/agent-policy-confirm ') || - prompt.startsWith('/agent-policy-auto ') - ) { - if (!requireChatProjectForUserAction()) { - return; - } - const mode = prompt.startsWith('/agent-policy-deny ') - ? 'deny' - : prompt.startsWith('/agent-policy-allow ') - ? 'allow' - : prompt.startsWith('/agent-policy-confirm ') - ? 'confirm' - : 'auto'; - const commandPrefix = - mode === 'deny' - ? '/agent-policy-deny ' - : mode === 'allow' - ? '/agent-policy-allow ' - : mode === 'confirm' - ? '/agent-policy-confirm ' - : '/agent-policy-auto '; - const args = prompt.slice(commandPrefix.length).trim(); - const [agentId = '', commandId = ''] = args.split(/\s+/, 2); - if (!agentId || !commandId) { - setMessages((current) => [ - ...current, - { - role: 'assistant', - text: - mode === 'deny' || mode === 'allow' - ? '格式:/agent-policy-deny design-director file.read' - : '格式:/agent-policy-confirm design-director memory.write', - }, - ]); - return; - } - if (!isRegisteredGameCreationCommandId(commandId)) { - setMessages((current) => [ - ...current, - { role: 'assistant', text: `未知内置命令:${commandId}` }, - ]); - return; - } - if ( - (mode === 'confirm' || mode === 'auto') && - !isProjectPolicyConfirmableCommandId(commandId) - ) { - setMessages((current) => [ - ...current, - { - role: 'assistant', - text: '当前仅支持确认 project.index、project.status、project.bootstrap、project.checkpoint、project.diff、project.restore、project.export_package、project.export_list、file.list、file.read、memory.read、memory.write、memory.delete、asset.register、asset.list、task.list、task.create、task.update、agent.run_status、agent.kill、agent.retry、agent.resume、agent.delegate、agent.spawn_isolated、agent.schedule_ready、agent.audit、agent.trace_read、preview.status、preview.start、preview.validate、preview.open、preview.stop、canvas.project_sync、canvas.asset_import、canvas.asset_generate、canvas.export_import、conversation.read 和 conversation.write。', - }, - ]); - return; - } - void queueAgentPolicyMutation(agentId, commandId, mode); - return; - } - - if (prompt === '/llm-status') { - void executeLlmConfigStatus(); - return; - } - - if (prompt === '/llm-routes') { - void executeLlmRouteSummary(); - return; - } - - if (prompt === '/open-project' || prompt === '/show-project') { - if (!requireChatProjectForUserAction()) { - return; - } - void handleRevealCurrentProjectDirectory(); - return; - } - - if (prompt === '/switch-project') { - void handleOpenLauncherWindow(); - return; - } - - if (prompt === '/files') { - void executeProjectFiles(true); - return; - } - - if (prompt === '/assets') { - void executeProjectAssets(true); - return; - } - - if (prompt === '/credits') { - if (!requireChatProjectForUserAction()) { - return; - } - const summary = summarizeProjectAssetCredits(manifest); - setMessages((current) => [ - ...current, - { - role: 'assistant', - text: summary.text, - draftCommand: summary.draftCommand, - draftCommandLabel: summary.draftCommandLabel, - }, - ]); - return; - } - - if (prompt === '/art') { - if (!requireChatProjectForUserAction()) { - return; - } - const summary = summarizeProjectVisualAssets(manifest); - setMessages((current) => [ - ...current, - { - role: 'assistant', - text: summary.text, - draftCommand: summary.draftCommand, - draftCommandLabel: summary.draftCommandLabel, - }, - ]); - return; - } - - if (prompt === '/audio') { - if (!requireChatProjectForUserAction()) { - return; - } - const summary = summarizeProjectAudioAssets(manifest); - setMessages((current) => [ - ...current, - { - role: 'assistant', - text: summary.text, - draftCommand: summary.draftCommand, - draftCommandLabel: summary.draftCommandLabel, - }, - ]); - return; - } - - if (prompt === '/artifacts') { - if (!requireChatProjectForUserAction()) { - return; - } - setMessages((current) => [ - ...current, - { - role: 'assistant', - text: summarizeCommonProjectArtifactReadDrafts(), - draftCommand: `/read ${commonProjectArtifactReadDrafts[0].path}`, - draftCommandLabel: commonProjectArtifactReadDrafts[0].label, - }, - ]); - return; - } - - if (prompt === '/run-artifacts') { - if (!requireChatProjectForUserAction()) { - return; - } - const artifacts = agentRunTrace - ? readableArtifactsFromAgentRunTrace(agentRunTrace) - : []; - const firstArtifact = artifacts[0] ?? null; - setMessages((current) => [ - ...current, - { - role: 'assistant', - text: summarizeRunArtifactReadDrafts(agentRunTrace), - draftCommand: firstArtifact - ? `/read ${firstArtifact.path}` - : undefined, - draftCommandLabel: firstArtifact ? '读取首个 Run 产物' : undefined, - }, - ]); - return; - } - - if (prompt === '/passes') { - if (!requireChatProjectForUserAction()) { - return; - } - const artifacts = agentRunTrace - ? readablePassArtifactsFromAgentRunTrace(agentRunTrace) - : []; - const firstArtifact = artifacts[0] ?? null; - setMessages((current) => [ - ...current, - { - role: 'assistant', - text: summarizeAgentPassArtifactReadDrafts(agentRunTrace), - draftCommand: firstArtifact - ? `/read ${firstArtifact.path}` - : undefined, - draftCommandLabel: firstArtifact ? '读取首个轮次产物' : undefined, - }, - ]); - return; - } - - if (prompt === '/runs') { - if (!requireChatProjectForUserAction()) { - return; - } - const firstHistory = agentRunHistory[0] ?? null; - setMessages((current) => [ - ...current, - { - role: 'assistant', - text: summarizeAgentRunHistoryReadDrafts( - agentRunTrace, - agentRunHistory, - ), - draftCommand: firstHistory - ? `/read ${firstHistory.path}` - : agentRunTrace - ? '/trace' - : undefined, - draftCommandLabel: firstHistory - ? '读取首个历史 Run' - : agentRunTrace - ? '查看最近 Run' - : undefined, - }, - ]); - return; - } - - if (prompt === '/run-files') { - if (!requireChatProjectForUserAction()) { - return; - } - setMessages((current) => [ - ...current, - { - role: 'assistant', - text: summarizeAgentRunSupportFileReadDrafts(), - draftCommand: `/read ${commonAgentRunSupportReadDrafts[0].path}`, - draftCommandLabel: commonAgentRunSupportReadDrafts[0].label, - }, - ]); - return; - } - - if (prompt === '/internals') { - if (!requireChatProjectForUserAction()) { - return; - } - setMessages((current) => [ - ...current, - { - role: 'assistant', - text: summarizeProjectInternalReadDrafts(), - draftCommand: `/read ${commonProjectInternalReadDrafts[0].path}`, - draftCommandLabel: commonProjectInternalReadDrafts[0].label, - }, - ]); - return; - } - - if (prompt === '/logs') { - if (!requireChatProjectForUserAction()) { - return; - } - setMessages((current) => [ - ...current, - { - role: 'assistant', - text: summarizeCommonProjectLogReadDrafts(), - draftCommand: `/read ${commonProjectLogReadDrafts[0].path}`, - draftCommandLabel: commonProjectLogReadDrafts[0].label, - }, - ]); - return; - } - - if (prompt.startsWith('/asset-register ')) { - if (!requireChatProjectForUserAction()) { - return; - } - const [ - localPath, - kind = 'asset', - mediaType = 'application/octet-stream', - ] = prompt.slice('/asset-register '.length).trim().split(/\s+/); - if (!localPath) { - setMessages((current) => [ - ...current, - { - role: 'assistant', - text: '格式:/asset-register assets/hero.png [kind] [mediaType]', - }, - ]); - return; - } - if (!isSafeProjectRelativePath(localPath)) { - setMessages((current) => [ - ...current, - { role: 'assistant', text: '资产路径必须是项目内相对路径。' }, - ]); - return; - } - queuePendingCommand({ id: 'asset.register', localPath, kind, mediaType }); - setMessages((current) => [ - ...current, - { role: 'assistant', text: `准备登记项目资产:${localPath}` }, - ]); - return; - } - - if (prompt.startsWith('/read ')) { - const relativePath = prompt.slice('/read '.length).trim(); - if (!relativePath) { - setMessages((current) => [ - ...current, - { role: 'assistant', text: '格式:/read game/index.html' }, - ]); - return; - } - if (!isSafeProjectRelativePath(relativePath)) { - setMessages((current) => [ - ...current, - { role: 'assistant', text: '文件路径必须是项目内相对路径。' }, - ]); - return; - } - void executeProjectFileReadChat(relativePath); - return; - } - - if (prompt === '/tasks') { - void executeProjectTasks(true); - return; - } - - if (prompt === '/agents') { - if (!requireChatProjectForUserAction()) { - return; - } - setMessages((current) => [ - ...current, - { - role: 'assistant', - text: summarizeAgentStatusCardsForChat( - agentStatusCards, - llmConfigStatus, - ), - }, - ]); - return; - } - - if (prompt === '/agent-conversations') { - if (!requireChatProjectForUserAction()) { - return; - } - const drafts = agentConversationReadDraftsFromManifest(manifest); - const firstDraft = drafts[0] ?? null; - setMessages((current) => [ - ...current, - { - role: 'assistant', - text: summarizeAgentConversationReadDrafts(manifest), - draftCommand: firstDraft ? `/read ${firstDraft.path}` : undefined, - draftCommandLabel: firstDraft - ? `读取${firstDraft.task.title}对话` - : undefined, - }, - ]); - return; - } - - if (prompt === '/agent-memories') { - if (!requireChatProjectForUserAction()) { - return; - } - const drafts = agentMemoryReadDraftsFromManifest(manifest); - const firstDraft = drafts[0] ?? null; - setMessages((current) => [ - ...current, - { - role: 'assistant', - text: summarizeAgentMemoryReadDrafts(manifest), - draftCommand: firstDraft ? `/read ${firstDraft.path}` : undefined, - draftCommandLabel: firstDraft - ? `读取${firstDraft.task.title}记忆` - : undefined, - }, - ]); - return; - } - - if (prompt === '/trace' || prompt === '/loop') { - void executeAgentTraceChat(); - return; - } - - if (prompt === '/agent-status') { - void executeAgentRunControl('status', undefined, true); - return; - } - - if (prompt === '/agent-kill') { - if (!requireChatProjectForUserAction()) { - return; - } - queuePendingCommand({ id: 'agent.kill' }); - setMessages((current) => [ - ...current, - { role: 'assistant', text: '准备标记最近 run 为 killed。' }, - ]); - return; - } - - if (prompt === '/agent-retry') { - if (!requireChatProjectForUserAction()) { - return; - } - queuePendingCommand({ id: 'agent.retry' }); - setMessages((current) => [ - ...current, - { role: 'assistant', text: '准备重新运行最近 run 的目标。' }, - ]); - return; - } - - if (prompt === '/agent-resume' || prompt.startsWith('/agent-resume ')) { - if (!requireChatProjectForUserAction()) { - return; - } - queuePendingCommand({ - id: 'agent.resume', - detail: prompt.slice('/agent-resume'.length).trim(), - }); - setMessages((current) => [ - ...current, - { role: 'assistant', text: '准备恢复最近 run。' }, - ]); - return; - } - - if (prompt === '/history') { - const nextProjectPath = requireChatProjectForUserAction(); - if (!nextProjectPath) { - return; - } - void loadProjectConversation(nextProjectPath, false, 'replace'); - return; - } - - if (prompt === '/commands' || prompt === '/limited-commands') { - void executeLimitedCommandList(); - return; - } - - if (prompt === '/smoke') { - if (!requireChatProjectForUserAction()) { - return; - } - queuePendingCommand({ - id: 'command.run_limited', - commandId: 'game.static_smoke', - }); - setMessages((current) => [ - ...current, - { role: 'assistant', text: '准备运行静态入口自检。' }, - ]); - return; - } - - if (prompt === '/run') { - if (!requireChatProjectForUserAction()) { - return; - } - queuePendingCommand({ id: 'game.run_local' }); - setMessages((current) => [ - ...current, - { role: 'assistant', text: '准备运行当前本地游戏。' }, - ]); - return; - } - - if (prompt === '/export') { - queueProjectExportPackageShortcut(); - return; - } - - if (prompt === '/exports') { - void executeProjectExportPackages(true); - return; - } - - if (prompt === '/preview') { - if (!requireChatProjectForUserAction()) { - return; - } - queuePendingCommand({ id: 'preview.start' }); - setMessages((current) => [ - ...current, - { role: 'assistant', text: '准备启动本地预览。' }, - ]); - return; - } - - if (prompt === '/open-preview') { - if (!requireChatProjectForUserAction()) { - return; - } - queuePendingCommand({ id: 'preview.open' }); - setMessages((current) => [ - ...current, - { role: 'assistant', text: '准备打开当前本地预览。' }, - ]); - return; - } - - if (prompt === '/preview-status') { - void executePreviewStatus(true); - return; - } - - if (prompt === '/preview-stop') { - if (!requireChatProjectForUserAction()) { - return; - } - void executePreviewStop(true); - return; - } - - if (prompt === '/memory' || prompt.startsWith('/memory ')) { - if (!requireChatProjectForUserAction()) { - return; - } - const scope = parseOptionalMemoryScope(prompt.slice('/memory'.length)); - if (!scope) { - setMessages((current) => [ - ...current, - { - role: 'assistant', - text: '格式:/memory [short|long|blackboard]', - }, - ]); - return; - } - void executeMemoryReadChat(scope); - return; - } - - if (prompt.startsWith('/remember ')) { - if (!requireChatProjectForUserAction()) { - return; - } - const { scope, content } = parseRememberInput( - prompt.slice('/remember '.length), - ); - if (!content) { - setMessages((current) => [ - ...current, - { role: 'assistant', text: '请提供要追加的记忆内容。' }, - ]); - return; - } - queuePendingCommand({ - id: 'memory.write', - scope, - content, - mode: 'append', - }); - setMessages((current) => [ - ...current, - { role: 'assistant', text: `准备追加${memoryScopeLabel(scope)}记忆。` }, - ]); - return; - } - - if (prompt.startsWith('/memory-set ')) { - if (!requireChatProjectForUserAction()) { - return; - } - const { scope, content } = parseRememberInput( - prompt.slice('/memory-set '.length), - ); - if (!content) { - setMessages((current) => [ - ...current, - { role: 'assistant', text: '请提供要保存的记忆内容。' }, - ]); - return; - } - queuePendingCommand({ - id: 'memory.write', - scope, - content, - mode: 'replace', - }); - setMessages((current) => [ - ...current, - { - role: 'assistant', - text: `准备覆盖保存${memoryScopeLabel(scope)}记忆。`, - }, - ]); - return; - } - - if (prompt === '/forget-memory' || prompt.startsWith('/forget-memory ')) { - if (!requireChatProjectForUserAction()) { - return; - } - const scope = parseOptionalMemoryScope( - prompt.slice('/forget-memory'.length), - ); - if (!scope) { - setMessages((current) => [ - ...current, - { - role: 'assistant', - text: '格式:/forget-memory [short|long|blackboard]', - }, - ]); - return; - } - queuePendingCommand({ id: 'memory.delete', scope }); - setMessages((current) => [ - ...current, - { role: 'assistant', text: `准备删除${memoryScopeLabel(scope)}记忆。` }, - ]); - return; - } - - if (prompt.startsWith('/canvas ')) { - const canvasProjectId = prompt.slice('/canvas '.length).trim(); - if (!canvasProjectId) { - setMessages((current) => [ - ...current, - { role: 'assistant', text: '请提供画板项目 ID。' }, - ]); - return; - } - if (!isSafeCanvasProjectId(canvasProjectId)) { - setMessages((current) => [ - ...current, - { role: 'assistant', text: '画板项目 ID 不能包含控制字符。' }, - ]); - return; - } - queuePendingCommand({ id: 'canvas.project_open', canvasProjectId }); - setMessages((current) => [ - ...current, - { role: 'assistant', text: `准备打开画板项目:${canvasProjectId}` }, - ]); - return; - } - - if (prompt.startsWith('/sync-canvas-project ')) { - if (!requireChatProjectForUserAction()) { - return; - } - const canvasProjectId = prompt - .slice('/sync-canvas-project '.length) - .trim(); - if (!canvasProjectId) { - setMessages((current) => [ - ...current, - { role: 'assistant', text: '请提供画板项目 ID。' }, - ]); - return; - } - if (!isSafeCanvasProjectId(canvasProjectId)) { - setMessages((current) => [ - ...current, - { role: 'assistant', text: '画板项目 ID 不能包含控制字符。' }, - ]); - return; - } - queuePendingCommand({ id: 'canvas.project_sync', canvasProjectId }); - setMessages((current) => [ - ...current, - { role: 'assistant', text: `准备同步画板项目资源:${canvasProjectId}` }, - ]); - return; - } - - if (prompt.startsWith('/generate-art ')) { - if (!requireChatProjectForUserAction()) { - return; - } - const generationPrompt = prompt.slice('/generate-art '.length).trim(); - if (!generationPrompt) { - setMessages((current) => [ - ...current, - { role: 'assistant', text: '请提供美术生成提示词。' }, - ]); - return; - } - queuePendingCommand({ - id: 'canvas.asset_generate', - prompt: generationPrompt, - }); - setMessages((current) => [ - ...current, - { role: 'assistant', text: '准备生成首版美术素材。' }, - ]); - return; - } - - if (prompt.startsWith('/import-canvas-asset ')) { - if (!requireChatProjectForUserAction()) { - return; - } - const [localPath, canvasProjectId, canvasAssetId, kind, mediaType] = - prompt.slice('/import-canvas-asset '.length).trim().split(/\s+/); - if (!localPath || !canvasProjectId || !canvasAssetId) { - setMessages((current) => [ - ...current, - { - role: 'assistant', - text: '格式:/import-canvas-asset assets/hero.png 画板项目ID 资源ID|object:资产对象ID', - }, - ]); - return; - } - if (!isSafeCanvasProjectId(canvasProjectId)) { - setMessages((current) => [ - ...current, - { role: 'assistant', text: '画板项目 ID 不能包含控制字符。' }, - ]); - return; - } - if (!isSafeProjectRelativePath(localPath)) { - setMessages((current) => [ - ...current, - { - role: 'assistant', - text: '画板资产路径必须是项目内相对路径。', - }, - ]); - return; - } - const canvasAssetObjectId = canvasAssetId.startsWith('object:') - ? canvasAssetId.slice('object:'.length).trim() - : ''; - if (canvasAssetId.startsWith('object:') && !canvasAssetObjectId) { - setMessages((current) => [ - ...current, - { - role: 'assistant', - text: '请提供 object: 后面的资产对象 ID。', - }, - ]); - return; - } - queuePendingCommand({ - id: 'canvas.asset_import', - localPath, - canvasProjectId, - canvasAssetId: canvasAssetObjectId ? '' : canvasAssetId, - canvasAssetObjectId, - kind: kind ?? 'asset', - mediaType: mediaType ?? 'application/octet-stream', - }); - setMessages((current) => [ - ...current, - { role: 'assistant', text: `准备导入画板资产:${localPath}` }, - ]); - return; - } - - if (prompt.startsWith('/import-canvas-export ')) { - if (!requireChatProjectForUserAction()) { - return; - } - const [exportPath, canvasProjectId] = prompt - .slice('/import-canvas-export '.length) - .trim() - .split(/\s+/); - if (!exportPath || !canvasProjectId) { - setMessages((current) => [ - ...current, - { - role: 'assistant', - text: '格式:/import-canvas-export /绝对/画板素材.zip 画板项目ID', - }, - ]); - return; - } - if (!isSafeCanvasProjectId(canvasProjectId)) { - setMessages((current) => [ - ...current, - { role: 'assistant', text: '画板项目 ID 不能包含控制字符。' }, - ]); - return; - } - if ( - !isAbsoluteProjectPath(exportPath) || - projectPathHasControlCharacter(exportPath) - ) { - setMessages((current) => [ - ...current, - { - role: 'assistant', - text: '画板导出 ZIP 路径必须是绝对路径。', - }, - ]); - return; - } - queuePendingCommand({ - id: 'canvas.export_import', - exportPath, - canvasProjectId, - }); - setMessages((current) => [ - ...current, - { role: 'assistant', text: `准备导入画板导出包:${exportPath}` }, - ]); - return; - } - - if (prompt.startsWith('/')) { - setMessages((current) => [ - ...current, - { - role: 'assistant', - text: `未知命令:${prompt}。输入 /help 查看可用命令。`, - }, - ]); - return; - } - - const clientTurnId = createDirectProjectTurnId(); - void executeChatAgentReply({ prompt, clientTurnId }); - } - - async function executeLlmConfigStatus() { - const invoke = resolveTauriInvoke(); - if (!invoke) { - setLlmConfigStatus(null); - setMessages((current) => [ - ...current, - { role: 'assistant', text: '需要在 Tauri App 内运行。' }, - ]); - return; - } - - try { - const status = await invoke( - 'check_game_creator_llm_config', - ); - setLlmConfigStatus(status); - setCommandLog((current) => [...current, 'llm.config_check']); - const agentLines = (status.agents ?? []).map(formatLlmAgentStatusLine); - const summary = status.configured - ? `${formatLlmRouteEndpoint(status)}。` - : `官方智能服务未就绪;请登录或重新登录后重试。${formatCodexRuntimeCapabilities(status)},账号状态 ${ - status.accountCredentialState === 'login_required' - ? '需要登录' - : '暂不可用' - }。`; - setMessages((current) => [ - ...current, - { - role: 'assistant', - text: [summary, ...agentLines].join('\n'), - }, - ]); - } catch (error) { - setLlmConfigStatus(null); - setMessages((current) => [ - ...current, - { - role: 'assistant', - text: error instanceof Error ? error.message : String(error), - }, - ]); - } - } - - async function executeLlmRouteSummary() { - const invoke = resolveTauriInvoke(); - if (!invoke) { - setLlmConfigStatus(null); - setMessages((current) => [ - ...current, - { role: 'assistant', text: '需要在 Tauri App 内运行。' }, - ]); - return; - } - - try { - const status = await invoke( - 'check_game_creator_llm_config', - ); - setLlmConfigStatus(status); - setCommandLog((current) => [...current, 'llm.route_summary']); - const summary = summarizeAgentLlmRoutes(status); - setMessages((current) => [ - ...current, - { - role: 'assistant', - text: summary.text, - draftCommand: summary.draftCommand, - draftCommandLabel: summary.draftCommandLabel, - }, - ]); - } catch (error) { - setLlmConfigStatus(null); - setMessages((current) => [ - ...current, - { - role: 'assistant', - text: error instanceof Error ? error.message : String(error), - }, - ]); - } - } - - async function executeAgentCapabilitiesChat() { - const invoke = resolveTauriInvoke(); - let capabilities: readonly GameCreationAgentCapabilityDescriptor[] = - GAME_CREATION_AGENT_CAPABILITIES; - if (invoke) { - try { - const nativeCapabilities = await invoke< - GameCreationAgentCapabilityDescriptor[] - >('get_game_creation_agent_capabilities'); - if (nativeCapabilities.length > 0) { - capabilities = nativeCapabilities; - } - } catch { - capabilities = GAME_CREATION_AGENT_CAPABILITIES; - } - } - setCommandLog((current) => [...current, 'agent.capabilities']); - setMessages((current) => [ - ...current, - { role: 'assistant', text: summarizeAgentCapabilities(capabilities) }, - ]); - } - - async function resolveProjectSupervisorSessionForSubmission( - invoke: TauriInvoke, - nextProjectPath: string, - ) { - const currentSessionId = projectSupervisorSessionIdRef.current; - if (currentSessionId) { - return currentSessionId; - } - const sessionId = await ensureProjectSupervisorActiveSessionId( - invoke, - nextProjectPath, - ); - if (!sessionId) { - throw new Error('项目总控 Agent active Session 不可用'); - } - if (localProjectPathRef.current !== nextProjectPath) { - return null; - } - projectSupervisorSessionIdRef.current = sessionId; - setProjectSupervisorSessionId(sessionId); - return sessionId; - } - /** * DirectProject 聊天的首轮需求:入口 latch 里那份原文与附件,项目匹配后由聊天认领。 * - * 认领记录与 Supervisor / Design / Planning 入口共用一份(按页面保存),因此同一条 + * 认领记录与 Design / Planning 入口共用一份(按页面保存),因此同一条 * 首轮需求不会被两个入口各发一次。 */ const initialDirectTurn: DirectProjectInitialTurn | null = (() => { - const latch = initialSupervisorMessageLatchRef.current; + const latch = initialPlanningPromptLatchRef.current; if (!latch.prompt || !latch.projectPath) return null; return { projectPath: latch.projectPath, @@ -6037,7 +2000,7 @@ export function App({ return true; } catch (error) { if (localProjectPathRef.current === input.projectPath) { - setProjectSupervisorRuntimeError( + setProjectChatError( `DirectProject 对话权限检查失败:${ error instanceof Error ? error.message : String(error) }`, @@ -6051,7 +2014,7 @@ export function App({ * 工作台壳要把一句结果说给用户在项目对话里听。 * * DirectProject 的会话由聊天容器持有,壳只把这句话交给聊天的本地消息流; - * Supervisor 路径仍写壳自己的 `messages`。 + * 立项策划路径仍写壳自己的 `messages`。 */ function announceProjectChatMessage(text: string) { if (directProjectMode) { @@ -6070,19 +2033,15 @@ export function App({ const invoke = resolveTauriInvoke(); const normalizedPrompt = prompt.trim(); if (!invoke) { - setProjectSupervisorRuntimeError('需要在 Tauri App 内运行。'); + setProjectChatError('需要在 Tauri App 内运行。'); return; } if (!nextProjectPath.trim() || !normalizedPrompt) { return; } const currentSessionId = planningV2SessionRef.current?.session.sessionId; - planningV2TurnRef.current = { - projectPath: nextProjectPath, - clientTurnId, - }; setChatAgentBusy(true); - setProjectSupervisorRuntimeError(''); + setProjectChatError(''); setPlanningV2TransientReplyTarget(''); setPlanningV2Reasoning(''); try { @@ -6112,10 +2071,6 @@ export function App({ return; } applyPlanningV2CommandResult(result, clientTurnId); - setCommandLog((current) => [ - ...current, - currentSessionId ? 'planning.v2.continue' : 'planning.v2.start', - ]); } catch (error) { if (localProjectPathRef.current !== nextProjectPath) { return; @@ -6124,7 +2079,7 @@ export function App({ if (isRuntimeConfigMissingError(message)) { requestRuntimeConfigOpen(); } - setProjectSupervisorRuntimeError(message); + setProjectChatError(message); setMessages((current) => [ ...current, { @@ -6136,7 +2091,6 @@ export function App({ }, ]); } finally { - planningV2TurnRef.current = null; setPlanningV2TransientReplyTarget(''); setChatAgentBusy(false); } @@ -6146,179 +2100,30 @@ export function App({ prompt, clientTurnId: directConversationTurnId, }: ExecuteChatAgentReplyInput) { - if (planningV2ActiveRef.current || planningStartMode) { - const nextProjectPath = resolveChatProjectPath(localProject); - if (!nextProjectPath) { - setProjectSupervisorRuntimeError('请先初始化本地项目'); - return; - } - if (designAgentLaneRef.current) { - await executeDesignAgentTurn( - nextProjectPath, - { type: 'message', text: prompt }, - directConversationTurnId ?? createAgentChatRunId('design-agent-turn'), - ); - return; - } - await executePlanningV2Turn( - nextProjectPath, - prompt, - directConversationTurnId ?? createAgentChatRunId('planning-v2-turn'), - ); - return; - } - if (agentRuntimeNeedsUserInput(projectSupervisorRuntimeRef.current)) { - setProjectSupervisorRuntimeError('请先回答项目总控 Agent 当前的澄清问题'); - return; - } - const nextProjectPath = requireChatProjectForUserAction(); + const nextProjectPath = resolveChatProjectPath(localProject); if (!nextProjectPath) { + setProjectChatError('请先初始化本地项目'); return; } - const invoke = resolveTauriInvoke(); - if (!invoke) { - setMessages((current) => [ - ...current, - { role: 'assistant', text: '需要在 Tauri App 内运行。' }, - ]); - return; - } - - setChatAgentBusy(true); - setProjectSupervisorRuntimeError(''); - try { - const sessionId = await resolveProjectSupervisorSessionForSubmission( - invoke, + if (designAgentLaneRef.current) { + await executeDesignAgentTurn( nextProjectPath, + { type: 'message', text: prompt }, + directConversationTurnId ?? createAgentChatRunId('design-agent-turn'), ); - if (!sessionId || localProjectPathRef.current !== nextProjectPath) { - return; - } - const runtimeAtSubmission = projectSupervisorRuntimeRef.current; - // 做方案入口独立成链。首次提交时本项目还没有任何 Supervisor run,只能靠首页 - // 带下来的 startMode 判断;之后由 plan 根 run 自己的 source 接管,做游戏与 - // 做素材两条链路不受影响。 - const planningEntry = - workspaceProjectKind === 'web' && - (runtimeAtSubmission?.source === PROJECT_SUPERVISOR_PLAN_SOURCE || - (planningStartMode && !runtimeAtSubmission)); - if ( - planningEntry && - runtimeAtSubmission && - runtimeAtSubmission.source === PROJECT_SUPERVISOR_PLAN_SOURCE && - !isAgentRuntimeTerminalState(runtimeAtSubmission) - ) { - setProjectSupervisorRuntimeError('请先完成当前立项策划步骤'); - return; - } - const submissionRoute = resolveProjectSupervisorRuntimeSubmission({ - workspaceProjectKind, - orchestrationMode, - supervisorChatOnly, - planningEntry, - }); - const submission = await submitProjectSupervisorRuntimeTask({ - invoke, - projectPath: nextProjectPath, - sessionId, - prompt, - runtime: runtimeAtSubmission, - runProfile: submissionRoute.runProfile, - source: submissionRoute.source, - }); - const runtimeResult = submission.runtimeResult; - const acceptedRunId = submission.acceptedRunId.trim(); - if (!acceptedRunId) { - throw new Error('项目总控 Agent 请求未进入后台队列'); - } - setCommandLog((current) => [ - ...current, - `agent.runtime.${submission.mode} project-supervisor`, - ]); - const runtime = agentRuntimeStateFromResult( - runtimeResult, - projectSupervisorRuntimeRef.current, - ); - if ( - localProjectPathRef.current !== nextProjectPath || - projectSupervisorSessionIdRef.current !== sessionId - ) { - return; - } - if ( - runtime.agentId !== PROJECT_SUPERVISOR_AGENT_ID || - runtime.sessionId !== sessionId - ) { - throw new Error('项目总控 Agent Runtime Session 身份不匹配'); - } - const acceptedRuntimeReady = runtime.runId === acceptedRunId; - if (!acceptedRuntimeReady) { - projectSupervisorExpectedRunIdRef.current = acceptedRunId; - setProjectSupervisorExpectedRunId(acceptedRunId); - } - if (acceptedRuntimeReady) { - projectSupervisorExpectedRunIdRef.current = null; - setProjectSupervisorExpectedRunId(null); - updateProjectSupervisorRuntime(runtime); - updateProjectSupervisorResponseStream( - runtimeResult.responseStream, - runtime, - ); - } - setProjectSupervisorRuntimeError(''); - const refreshConversation = - projectSupervisorRefreshConversationRef.current; - if (refreshConversation) { - void refreshConversation(invoke, nextProjectPath, sessionId).catch( - (error) => { - if ( - localProjectPathRef.current === nextProjectPath && - projectSupervisorSessionIdRef.current === sessionId - ) { - setProjectSupervisorRuntimeError( - `项目总控 Agent 对话刷新失败:${ - error instanceof Error ? error.message : String(error) - }`, - ); - } - }, - ); - } - if (acceptedRuntimeReady) { - syncTerminalProjectSupervisorConversation( - invoke, - nextProjectPath, - runtime, - ); - } - } catch (error) { - if (localProjectPathRef.current !== nextProjectPath) { - return; - } - void captureAgentRuntimeError(error, PROJECT_SUPERVISOR_AGENT_ID); - const message = error instanceof Error ? error.message : String(error); - if (isRuntimeConfigMissingError(message)) { - requestRuntimeConfigOpen(); - } - setProjectSupervisorRuntimeError(message); - setMessages((current) => [ - ...current, - { - role: 'assistant', - text: message, - runtimeOwned: true, - updatedAt: Date.now(), - }, - ]); - } finally { - setChatAgentBusy(false); + return; } + await executePlanningV2Turn( + nextProjectPath, + prompt, + directConversationTurnId ?? createAgentChatRunId('planning-v2-turn'), + ); } executeChatAgentReplyRef.current = executeChatAgentReply; useEffect(() => { - const latch = initialSupervisorMessageLatchRef.current; + const latch = initialPlanningPromptLatchRef.current; if (!planningStartMode || !latch.prompt || !localProject) { return; } @@ -6330,11 +2135,11 @@ export function App({ } if ( chatAgentBusy || - !claimInitialSupervisorMessageForPage(latch.projectPath, latch.claimScope) + !claimInitialTurnForPage(latch.projectPath, latch.claimScope) ) { return; } - supervisorChatShouldFollowLatestRef.current = true; + planningChatShouldFollowLatestRef.current = true; setMessages((current) => [ ...current, { @@ -6351,518 +2156,50 @@ export function App({ }, [ chatAgentBusy, initialCreationType, - initialSupervisorMessage, + initialPlanningPrompt, localProject, planningStartMode, ]); - async function handleProjectSupervisorToolAction( - decision: 'confirm' | 'reject', - ) { - const nextProjectPath = resolveChatProjectPath(localProject); - const runtime = projectSupervisorRuntimeRef.current; - const pendingAction = runtime?.pendingToolAction; - const sessionId = projectSupervisorSessionIdRef.current; - const invoke = resolveTauriInvoke(); - if ( - !invoke || - !nextProjectPath || - !runtime || - !pendingAction?.actionId || - !sessionId || - chatAgentBusy - ) { - return; - } - setChatAgentBusy(true); - setProjectSupervisorRuntimeError(''); - try { - const command = - decision === 'confirm' - ? 'confirm_game_creator_agent_runtime_task' - : 'reject_game_creator_agent_runtime_task'; - const result = await invoke(command, { - projectPath: nextProjectPath, - agentId: PROJECT_SUPERVISOR_AGENT_ID, - runId: runtime.runId, - actionId: pendingAction.actionId, - note: - decision === 'confirm' - ? '用户已确认待执行工具动作' - : '用户已拒绝待执行工具动作', - }); - const nextRuntime = agentRuntimeStateFromResult( - result, - projectSupervisorRuntimeRef.current, - ); - if ( - localProjectPathRef.current !== nextProjectPath || - projectSupervisorSessionIdRef.current !== sessionId || - nextRuntime.sessionId !== sessionId || - nextRuntime.runId !== runtime.runId - ) { - return; - } - updateProjectSupervisorRuntime(nextRuntime); - updateProjectSupervisorResponseStream(result.responseStream, nextRuntime); - setCommandLog((current) => [ - ...current, - `agent.runtime.${decision} project-supervisor`, - ]); - syncTerminalProjectSupervisorConversation( - invoke, - nextProjectPath, - nextRuntime, - ); - } catch (error) { - if ( - localProjectPathRef.current !== nextProjectPath || - projectSupervisorSessionIdRef.current !== sessionId - ) { - return; - } - const message = error instanceof Error ? error.message : String(error); - if (isRuntimeConfigMissingError(message)) { - requestRuntimeConfigOpen(); - } - setProjectSupervisorRuntimeError(message); - } finally { - setChatAgentBusy(false); - } - } - - async function handleProjectProfessionalAgentToolAction( - runtime: AgentRuntimeState, - decision: 'confirm' | 'reject', - ) { - const nextProjectPath = resolveChatProjectPath(localProject); - const currentRuntime = agentRuntimeById[runtime.agentId]; - const pendingAction = currentRuntime?.pendingToolAction; - const supervisorRunId = projectSupervisorRuntimeRef.current?.runId; - const invoke = resolveTauriInvoke(); - if ( - !invoke || - !nextProjectPath || - !currentRuntime || - !pendingAction?.actionId || - chatAgentBusy - ) { - throw new Error('专业 Agent 待确认动作已变化,请等待状态刷新'); - } - if ( - currentRuntime.runId !== runtime.runId || - pendingAction.actionId !== runtime.pendingToolAction?.actionId || - currentRuntime.parentAgentId !== PROJECT_SUPERVISOR_AGENT_ID || - currentRuntime.parentRunId !== supervisorRunId - ) { - throw new Error('专业 Agent Runtime 身份已变化,请等待状态刷新'); - } - setChatAgentBusy(true); - try { - const command = - decision === 'confirm' - ? 'confirm_game_creator_agent_runtime_task' - : 'reject_game_creator_agent_runtime_task'; - const result = await invoke(command, { - projectPath: nextProjectPath, - agentId: currentRuntime.agentId, - runId: currentRuntime.runId, - actionId: pendingAction.actionId, - note: - decision === 'confirm' - ? '用户已确认专业 Agent 待执行工具动作' - : '用户已拒绝专业 Agent 待执行工具动作', - }); - if ( - localProjectPathRef.current !== nextProjectPath || - projectSupervisorRuntimeRef.current?.runId !== supervisorRunId - ) { - return; - } - const nextRuntime = agentRuntimeStateFromResult(result, currentRuntime); - if ( - nextRuntime.agentId !== currentRuntime.agentId || - nextRuntime.runId !== currentRuntime.runId - ) { - throw new Error('专业 Agent 操作后 Runtime 身份不匹配'); - } - rememberAgentRuntimeState(nextRuntime); - setCommandLog((current) => [ - ...current, - `agent.runtime.${decision} ${currentRuntime.agentId}`, - ]); - } catch (error) { - throw new Error( - `专业 Agent 操作失败:${ - error instanceof Error ? error.message : String(error) - }`, - ); - } finally { - setChatAgentBusy(false); - } - } - - async function handleProjectProfessionalAgentRetry( - runtime: AgentRuntimeState, - ): Promise { - const nextProjectPath = resolveChatProjectPath(localProject); - const currentRuntime = agentRuntimeById[runtime.agentId]; - const supervisorRunId = projectSupervisorRuntimeRef.current?.runId; - const invoke = resolveTauriInvoke(); - if (!invoke || !nextProjectPath || !currentRuntime || chatAgentBusy) { - throw new Error('专业 Agent 状态已变化,请等待刷新后重试'); - } - const supervisorRuntime = projectSupervisorRuntimeRef.current; - if (!supervisorRuntime || isAgentRuntimeTerminalState(supervisorRuntime)) { - throw new Error('项目总控已停止,请先重试项目总控'); - } - if ( - currentRuntime.runId !== runtime.runId || - currentRuntime.parentAgentId !== PROJECT_SUPERVISOR_AGENT_ID || - currentRuntime.parentRunId !== supervisorRunId || - !( - currentRuntime.status === 'failed' || currentRuntime.phase === 'failed' - ) || - currentRuntime.pendingToolAction - ) { - throw new Error('专业 Agent 当前状态不允许直接重试'); - } - if ( - projectSupervisorPendingRepairMatchesProfessional( - projectSupervisorRuntimeRef.current?.pendingToolAction, - currentRuntime, - ) - ) { - await handleProjectSupervisorToolAction('confirm'); - return `已确认项目总控安排${projectProfessionalAgentLabel( - currentRuntime.agentId, - )}返工,正在同步新一轮状态`; - } - setChatAgentBusy(true); - try { - const result = await invoke( - 'confirm_retry_game_creator_agent_runtime_task', - { - projectPath: nextProjectPath, - agentId: currentRuntime.agentId, - runId: currentRuntime.runId, - nextRunId: createAgentChatRunId('project-professional-retry'), - }, - ); - if ( - localProjectPathRef.current !== nextProjectPath || - projectSupervisorRuntimeRef.current?.runId !== supervisorRunId - ) { - return '项目已切换,未把旧项目的重试状态合并到当前界面'; - } - const nextRuntime = agentRuntimeStateFromResult(result, currentRuntime); - if ( - nextRuntime.agentId !== currentRuntime.agentId || - nextRuntime.parentAgentId !== PROJECT_SUPERVISOR_AGENT_ID || - nextRuntime.parentRunId !== supervisorRunId - ) { - throw new Error('专业 Agent 重试后的 Runtime 身份不匹配'); - } - rememberAgentRuntimeState(nextRuntime); - setCommandLog((current) => [ - ...current, - `agent.runtime.retry ${currentRuntime.agentId}`, - ]); - return '重试请求已受理,正在同步新一轮状态'; - } catch (error) { - throw new Error( - `专业 Agent 重试失败:${ - error instanceof Error ? error.message : String(error) - }`, - ); - } finally { - setChatAgentBusy(false); - } - } - - async function handleProjectSupervisorRetry( - runtime: AgentRuntimeState, - ): Promise { - const nextProjectPath = resolveChatProjectPath(localProject); - const currentRuntime = projectSupervisorRuntimeRef.current; - const invoke = resolveTauriInvoke(); - if (!invoke || !nextProjectPath || !currentRuntime || chatAgentBusy) { - throw new Error('项目总控状态已变化,请等待刷新后重试'); - } - const needsReconciliation = - currentRuntime.status === 'needs-reconciliation' || - currentRuntime.phase === 'needs-reconciliation'; - if (needsReconciliation) { - if ( - currentRuntime.runId !== runtime.runId || - currentRuntime.agentId !== PROJECT_SUPERVISOR_AGENT_ID - ) { - throw new Error('项目总控待核对任务已变化,请等待刷新'); - } - const previousRunId = currentRuntime.runId; - setChatAgentBusy(true); - setProjectSupervisorRuntimeError(''); - try { - const result = await invoke( - 'cancel_game_creator_agent_runtime_task', - { - projectPath: nextProjectPath, - agentId: PROJECT_SUPERVISOR_AGENT_ID, - runId: previousRunId, - }, - ); - if ( - localProjectPathRef.current !== nextProjectPath || - projectSupervisorRuntimeRef.current?.runId !== previousRunId - ) { - return '项目已切换,未把旧项目的取消状态合并到当前界面'; - } - const nextRuntime = agentRuntimeStateFromResult(result, currentRuntime); - if ( - nextRuntime.agentId !== PROJECT_SUPERVISOR_AGENT_ID || - nextRuntime.runId !== previousRunId || - nextRuntime.sessionId !== currentRuntime.sessionId - ) { - throw new Error('项目总控取消后的 Runtime 身份不匹配'); - } - updateProjectSupervisorRuntime(nextRuntime); - updateProjectSupervisorResponseStream( - result.responseStream, - nextRuntime, - ); - setCommandLog((current) => [ - ...current, - 'agent.runtime.cancel project-supervisor reconciliation', - ]); - const queuePending = nextRuntime.taskQueue?.pending ?? 0; - if ( - nextRuntime.status === 'cancelled' || - nextRuntime.phase === 'cancelled' - ) { - return queuePending > 0 - ? `旧任务已结束,队列中还有 ${queuePending} 个待处理任务,队列将继续处理` - : '旧任务已结束,当前队列为空,可重新启动项目总控'; - } - return '已提交结束旧任务请求,正在同步取消状态'; - } catch (error) { - throw new Error( - `项目总控旧任务结束失败:${ - error instanceof Error ? error.message : String(error) - }`, - ); - } finally { - setChatAgentBusy(false); - } - } - const cancelledWithEmptyQueue = - (currentRuntime.status === 'cancelled' || - currentRuntime.phase === 'cancelled') && - (currentRuntime.taskQueue?.pending ?? 0) === 0; - if ( - currentRuntime.runId !== runtime.runId || - currentRuntime.agentId !== PROJECT_SUPERVISOR_AGENT_ID || - !( - currentRuntime.status === 'failed' || - currentRuntime.phase === 'failed' || - cancelledWithEmptyQueue - ) || - currentRuntime.pendingToolAction - ) { - throw new Error('项目总控当前状态不允许重试'); - } - const previousRunId = currentRuntime.runId; - const nextRunId = createAgentChatRunId('project-supervisor-retry'); - setChatAgentBusy(true); - setProjectSupervisorRuntimeError(''); - try { - const result = await invoke( - 'confirm_retry_game_creator_agent_runtime_task', - { - projectPath: nextProjectPath, - agentId: PROJECT_SUPERVISOR_AGENT_ID, - runId: previousRunId, - nextRunId, - }, - ); - if ( - localProjectPathRef.current !== nextProjectPath || - projectSupervisorRuntimeRef.current?.runId !== previousRunId - ) { - return '项目已切换,未把旧项目的重试状态合并到当前界面'; - } - const inferredRunId = agentRuntimeStartedRunId(result, nextRunId); - const acceptedRunId = - result.acceptedRunId?.trim() || - (inferredRunId === nextRunId ? nextRunId : ''); - if (!acceptedRunId) { - throw new Error('项目总控重试请求未进入后台队列'); - } - projectSupervisorExpectedRunIdRef.current = acceptedRunId; - setProjectSupervisorExpectedRunId(acceptedRunId); - const nextRuntime = agentRuntimeStateFromResult(result, currentRuntime); - if (nextRuntime.runId === acceptedRunId) { - if ( - nextRuntime.agentId !== PROJECT_SUPERVISOR_AGENT_ID || - nextRuntime.sessionId !== currentRuntime.sessionId - ) { - throw new Error('项目总控重试后的 Runtime 身份不匹配'); - } - updateProjectSupervisorRuntime(nextRuntime); - updateProjectSupervisorResponseStream( - result.responseStream, - nextRuntime, - ); - projectSupervisorExpectedRunIdRef.current = null; - setProjectSupervisorExpectedRunId(null); - } - setCommandLog((current) => [ - ...current, - 'agent.runtime.retry project-supervisor', - ]); - return nextRuntime.runId === acceptedRunId - ? '已在当前项目重新启动项目总控' - : '重试已受理,正在同步新一轮项目总控状态'; - } catch (error) { - throw new Error( - `项目总控重试失败:${ - error instanceof Error ? error.message : String(error) - }`, - ); - } finally { - setChatAgentBusy(false); - } - } - - async function handleProjectSupervisorUserInput( + async function handlePlanningUserInput( request: AgentRuntimeUserInputRequest, responseId: string, answers: Record, ) { const nextProjectPath = resolveChatProjectPath(localProject); - if (planningV2ActiveRef.current || planningStartMode) { - const current = planningV2SessionRef.current; - const currentQuestion = current?.session.currentQuestion; - const answer = Object.values(answers) - .map((value) => value.trim()) - .find((value) => value.length > 0); - if ( - !nextProjectPath || - !current || - !currentQuestion || - request.sessionId !== current.session.sessionId || - request.questions[0]?.id !== currentQuestion.id || - !answer || - chatAgentBusy - ) { - setProjectSupervisorRuntimeError('待回答问题已变更,请刷新策划状态'); - return; - } - setMessages((current) => [ - ...current, - { - role: 'user', - text: answer, - runtimeOwned: true, - messageId: `planning-v2:${responseId}:user`, - updatedAt: Date.now(), - }, - ]); - await executePlanningV2Turn( - nextProjectPath, - answer, - responseId, - request.questions[0].id, - ); - return; - } - const runtime = projectSupervisorRuntimeRef.current; - const sessionId = projectSupervisorSessionIdRef.current; - const currentRequest = runtime?.userInputRequest; - const invoke = resolveTauriInvoke(); + const current = planningV2SessionRef.current; + const currentQuestion = current?.session.currentQuestion; + const answer = Object.values(answers) + .map((value) => value.trim()) + .find((value) => value.length > 0); if ( - !invoke || !nextProjectPath || - !runtime || - !sessionId || - !responseId || + !current || + !currentQuestion || + request.sessionId !== current.session.sessionId || + request.questions[0]?.id !== currentQuestion.id || + !answer || chatAgentBusy ) { + setProjectChatError('待回答问题已变更,请刷新策划状态'); return; } - if ( - request.agentId !== PROJECT_SUPERVISOR_AGENT_ID || - request.sessionId !== sessionId || - request.runId !== runtime.runId || - currentRequest?.requestId !== request.requestId || - currentRequest?.actionId !== request.actionId - ) { - setProjectSupervisorRuntimeError('待回答请求已变更,请刷新 Runtime 状态'); - return; - } - setChatAgentBusy(true); - setProjectSupervisorRuntimeError(''); - try { - const result = await invoke( - 'answer_game_creator_agent_runtime_user_input', - { - projectPath: nextProjectPath, - agentId: PROJECT_SUPERVISOR_AGENT_ID, - runId: request.runId, - actionId: request.actionId, - requestId: request.requestId, - responseId, - answers, - }, - ); - const nextRuntime = agentRuntimeStateFromResult( - result, - projectSupervisorRuntimeRef.current, - ); - if ( - localProjectPathRef.current !== nextProjectPath || - projectSupervisorSessionIdRef.current !== sessionId - ) { - return; - } - if ( - nextRuntime.agentId !== PROJECT_SUPERVISOR_AGENT_ID || - nextRuntime.sessionId !== sessionId || - nextRuntime.runId !== request.runId - ) { - throw new Error('项目总控 Agent 回答后 Runtime 身份不匹配'); - } - updateProjectSupervisorRuntime(nextRuntime); - updateProjectSupervisorResponseStream(result.responseStream, nextRuntime); - const refreshConversation = - projectSupervisorRefreshConversationRef.current; - if (refreshConversation) { - await refreshConversation(invoke, nextProjectPath, sessionId); - } - setCommandLog((current) => [ - ...current, - 'agent.runtime.user_input.answered project-supervisor', - ]); - setProjectSupervisorRuntimeError(''); - syncTerminalProjectSupervisorConversation( - invoke, - nextProjectPath, - nextRuntime, - ); - } catch (error) { - if ( - localProjectPathRef.current !== nextProjectPath || - projectSupervisorSessionIdRef.current !== sessionId - ) { - return; - } - const message = error instanceof Error ? error.message : String(error); - if (isRuntimeConfigMissingError(message)) { - requestRuntimeConfigOpen(); - } - setProjectSupervisorRuntimeError(`回答提交失败:${message}`); - } finally { - setChatAgentBusy(false); - } + setMessages((current) => [ + ...current, + { + role: 'user', + text: answer, + runtimeOwned: true, + messageId: `planning-v2:${responseId}:user`, + updatedAt: Date.now(), + }, + ]); + await executePlanningV2Turn( + nextProjectPath, + answer, + responseId, + request.questions[0].id, + ); } async function executeGameDraft(prompt: string) { @@ -6880,8 +2217,6 @@ export function App({ } try { - setCommandLog((current) => [...current, 'game.generate_draft']); - setProjectStatus('正在调用 LLM'); setMessages((current) => [ ...current, { role: 'assistant', text: gameDraftStartedMessage() }, @@ -6897,7 +2232,6 @@ export function App({ projectPathHasControlCharacter(generatedProjectPath) ) { const message = '生成结果项目路径无效'; - setProjectStatus(message); setMessages((current) => [ ...current, { role: 'assistant', text: message }, @@ -6910,12 +2244,6 @@ export function App({ manifest: result.manifest, }); setManifest(result.manifest); - setProjectStatus('已生成可试玩原型'); - setCommandLog((current) => [ - ...current, - 'memory.write', - 'file.write game/index.html', - ]); const completionSummary = await refreshAgentRunTrace(generatedProjectPath); @@ -6925,8 +2253,6 @@ export function App({ { projectPath: generatedProjectPath }, ); updateClientPreview(previewResult); - setPreviewStatus(`运行中:127.0.0.1:${previewResult.port}`); - setCommandLog((current) => [...current, 'preview.start']); void refreshManifest(generatedProjectPath); setMessages((current) => [ ...current, @@ -6979,43 +2305,34 @@ export function App({ function markPendingCommandCanceled(command: PendingCommand) { if (command.id === 'project.create') { setWorkspaceStatus('已取消'); - setProjectStatus('已取消'); return; } if (command.id === 'game.generate_draft') { - setProjectStatus('已取消生成游戏草案'); return; } if ( command.id === 'game.run_local' || command.id === 'command.run_limited' ) { - setLimitedCommandStatus('已取消运行'); return; } if (command.id === 'project.checkpoint') { - setFileStatus('已取消保存 checkpoint'); return; } if (command.id === 'project.export_package') { - setFileStatus('已取消导出本地试玩包'); return; } if (command.id === 'project.index') { - setFileStatus('已取消索引项目'); setWorkspaceStatus('已取消索引项目'); return; } if (command.id === 'project.restore') { - setFileStatus('已取消回滚项目'); return; } if (command.id === 'project.policy_write') { - setProjectStatus('已取消修改权限策略'); return; } if (command.id === 'preview.start' || command.id === 'preview.open') { - setPreviewStatus('已取消预览操作'); setWorkspaceStatus('已取消预览操作'); return; } @@ -7024,44 +2341,32 @@ export function App({ command.id === 'agent.retry' || command.id === 'agent.resume' ) { - setAgentRunStatus('已取消 Agent run 操作'); return; } if (command.id === 'memory.write') { - setMemoryStatus('已取消写入项目记忆'); return; } if (command.id === 'memory.delete') { - setMemoryStatus('已取消删除项目记忆'); return; } if (command.id === 'asset.upload') { - setAssetStatus('已取消上传资产'); return; } if (command.id === 'asset.register') { - setAssetStatus('已取消登记资产'); return; } if (command.id === 'canvas.project_open') { - setAssetStatus('已取消打开画板'); return; } if (command.id === 'canvas.project_sync') { - setAssetStatus('已取消同步画板项目'); return; } if (command.id === 'canvas.asset_import') { - setAssetStatus('已取消导入画板资产'); return; } if (command.id === 'canvas.asset_generate') { - setAssetStatus('已取消生成美术素材'); return; } - if (command.id === 'canvas.export_import') { - setAssetStatus('已取消导入画板导出包'); - } } function handlePendingCommandCancel() { @@ -7074,10 +2379,6 @@ export function App({ 'permission.cancel', command.id, ); - setCommandLog((current) => [ - ...current, - `permission.cancel ${command.id}`, - ]); } setMessages((current) => [ ...current, @@ -7100,10 +2401,6 @@ export function App({ 'permission.cancel', command.id, ); - setCommandLog((current) => [ - ...current, - `permission.cancel ${command.id} missing-project`, - ]); requireChatProjectForUserAction(); return; } @@ -7122,10 +2419,6 @@ export function App({ 'permission.confirm', command.id, ); - setCommandLog((current) => [ - ...current, - `permission.confirm ${command.id}`, - ]); if (command.id === 'asset.upload') { void executeAssetUpload(command.file); } else if (command.id === 'asset.register') { @@ -7229,7 +2522,6 @@ export function App({ announceToChat, }); setWorkspaceStatus('目标文件夹不是空的'); - setProjectStatus('等待确认'); return; } } catch { @@ -7243,7 +2535,6 @@ export function App({ const pendingCreate = pendingNonEmptyProjectCreate; setPendingNonEmptyProjectCreate(null); setWorkspaceStatus('已取消'); - setProjectStatus('已取消'); if (pendingCreate?.announceToChat) { setMessages((current) => [ ...current, @@ -7255,89 +2546,9 @@ export function App({ } } - function confirmProjectCreateInNonEmptyFolder() { - const pendingCreate = pendingNonEmptyProjectCreate; - if (!pendingCreate) { - return; - } - setPendingNonEmptyProjectCreate(null); - void openWorkspace(pendingCreate.projectPath, pendingCreate.announceToChat); - } - - async function executeProjectStatus( - announceToChat: boolean, - skipPolicyConfirm = false, - ) { - const invoke = resolveTauriInvoke(); - if (!invoke) { - setProjectStatus('需要在 Tauri App 内运行'); - if (announceToChat) { - setMessages((current) => [ - ...current, - { role: 'assistant', text: '需要在 Tauri App 内运行。' }, - ]); - } - return; - } - const nextProjectPath = resolveChatProjectPath(localProject); - if (!nextProjectPath) { - setProjectStatus('请先初始化本地项目'); - if (announceToChat) { - setMessages((current) => [ - ...current, - { role: 'assistant', text: '请先用 /project 设置本地项目。' }, - ]); - } - return; - } - - try { - if ( - announceToChat && - !skipPolicyConfirm && - (await queueProjectPolicyConfirmationIfNeeded( - invoke, - 'project.status', - nextProjectPath, - `读取 ${nextProjectPath} 的项目状态`, - '准备读取项目状态。', - () => void executeProjectStatus(true, true), - )) - ) { - return; - } - const nextManifest = await invoke( - 'get_local_game_manifest', - { projectPath: nextProjectPath, commandId: 'project.status' }, - ); - setManifest(nextManifest); - setProjectStatus('已读取状态'); - setCommandLog((current) => [...current, 'project.status']); - if (announceToChat) { - setMessages((current) => [ - ...current, - { - role: 'assistant', - text: summarizeProjectStatus(nextManifest, nextProjectPath), - }, - ]); - } - } catch (error) { - const message = error instanceof Error ? error.message : String(error); - setProjectStatus(message); - if (announceToChat) { - setMessages((current) => [ - ...current, - { role: 'assistant', text: message }, - ]); - } - } - } - async function executeProjectIndex(announceToChat: boolean) { const invoke = resolveTauriInvoke(); if (!invoke) { - setFileStatus('需要在 Tauri App 内运行'); if (announceToChat) { setMessages((current) => [ ...current, @@ -7350,7 +2561,6 @@ export function App({ ? requireChatProjectForUserAction() : resolveChatProjectPath(localProject); if (!nextProjectPath) { - setLimitedCommandStatus('请先初始化本地项目'); return; } @@ -7359,8 +2569,6 @@ export function App({ 'build_local_project_index', { projectPath: nextProjectPath }, ); - setFileStatus(`已索引 ${result.fileCount} 个文件`); - setCommandLog((current) => [...current, 'project.index']); if (announceToChat) { setMessages((current) => [ ...current, @@ -7369,7 +2577,6 @@ export function App({ } } catch (error) { const message = error instanceof Error ? error.message : String(error); - setFileStatus(message); if (announceToChat) { setMessages((current) => [ ...current, @@ -7379,44 +2586,9 @@ export function App({ } } - async function queueOrExecuteProjectIndex() { - const invoke = resolveTauriInvoke(); - if (!invoke) { - void executeProjectIndex(true); - return; - } - const nextProjectPath = requireChatProjectForUserAction(); - if (!nextProjectPath) { - return; - } - - try { - const result = await invoke( - 'read_project_permission_policy', - { projectPath: nextProjectPath }, - ); - if (result.policy.confirmCommands.includes('project.index')) { - queuePendingCommand({ id: 'project.index' }); - setMessages((current) => [ - ...current, - { role: 'assistant', text: '准备刷新本地项目索引。' }, - ]); - return; - } - void executeProjectIndex(true); - } catch (error) { - const message = error instanceof Error ? error.message : String(error); - setMessages((current) => [ - ...current, - { role: 'assistant', text: message }, - ]); - } - } - async function executeProjectCheckpoint(announceToChat: boolean) { const invoke = resolveTauriInvoke(); if (!invoke) { - setFileStatus('需要在 Tauri App 内运行'); if (announceToChat) { setMessages((current) => [ ...current, @@ -7437,8 +2609,6 @@ export function App({ 'create_local_project_checkpoint', { projectPath: nextProjectPath }, ); - setFileStatus(`已保存 checkpoint:${result.checkpointId}`); - setCommandLog((current) => [...current, 'project.checkpoint']); if (announceToChat) { setMessages((current) => [ ...current, @@ -7447,7 +2617,6 @@ export function App({ } } catch (error) { const message = error instanceof Error ? error.message : String(error); - setFileStatus(message); if (announceToChat) { setMessages((current) => [ ...current, @@ -7460,7 +2629,6 @@ export function App({ async function executeProjectExportPackage(announceToChat: boolean) { const invoke = resolveTauriInvoke(); if (!invoke) { - setFileStatus('需要在 Tauri App 内运行'); if (announceToChat) { setMessages((current) => [ ...current, @@ -7481,8 +2649,6 @@ export function App({ 'export_local_project_package', { projectPath: nextProjectPath }, ); - setFileStatus(`已导出本地试玩包:${result.packageRelativePath}`); - setCommandLog((current) => [...current, 'project.export_package']); void refreshManifest(nextProjectPath); if (announceToChat) { setMessages((current) => [ @@ -7497,240 +2663,6 @@ export function App({ } } catch (error) { const message = error instanceof Error ? error.message : String(error); - setFileStatus(message); - if (announceToChat) { - setMessages((current) => [ - ...current, - { role: 'assistant', text: message }, - ]); - } - } - } - - async function executeProjectExportPackages( - announceToChat: boolean, - skipPolicyConfirm = false, - ) { - const invoke = resolveTauriInvoke(); - if (!invoke) { - setFileStatus('需要在 Tauri App 内运行'); - if (announceToChat) { - setMessages((current) => [ - ...current, - { role: 'assistant', text: '需要在 Tauri App 内运行。' }, - ]); - } - return; - } - const nextProjectPath = announceToChat - ? requireChatProjectForUserAction() - : resolveChatProjectPath(localProject); - if (!nextProjectPath) { - return; - } - - try { - if ( - announceToChat && - !skipPolicyConfirm && - (await queueProjectPolicyConfirmationIfNeeded( - invoke, - 'project.export_list', - nextProjectPath, - `列出 ${nextProjectPath} 的本地试玩包`, - '准备列出本地试玩包。', - () => void executeProjectExportPackages(true, true), - )) - ) { - return; - } - const result = await invoke( - 'list_local_project_export_packages', - { projectPath: nextProjectPath }, - ); - setFileStatus(`已列出 ${result.packages.length} 个本地试玩包`); - setCommandLog((current) => [...current, 'project.export_list']); - if (announceToChat) { - setMessages((current) => [ - ...current, - { - role: 'assistant', - text: summarizeProjectExportPackages(result), - draftCommand: - result.packages.length > 0 ? '/open-project' : '/export', - draftCommandLabel: - result.packages.length > 0 ? '显示目录' : '导出试玩包', - }, - ]); - } - } catch (error) { - const message = error instanceof Error ? error.message : String(error); - setFileStatus(message); - if (announceToChat) { - setMessages((current) => [ - ...current, - { role: 'assistant', text: message }, - ]); - } - } - } - - async function executeProjectCheckpoints( - announceToChat: boolean, - skipListPolicyConfirm = false, - skipReadPolicyConfirm = false, - ) { - const invoke = resolveTauriInvoke(); - if (!invoke) { - setFileStatus('需要在 Tauri App 内运行'); - if (announceToChat) { - setMessages((current) => [ - ...current, - { role: 'assistant', text: '需要在 Tauri App 内运行。' }, - ]); - } - return; - } - const nextProjectPath = announceToChat - ? requireChatProjectForUserAction() - : resolveChatProjectPath(localProject); - if (!nextProjectPath) { - return; - } - - try { - if ( - announceToChat && - !skipListPolicyConfirm && - (await queueProjectPolicyConfirmationIfNeeded( - invoke, - 'file.list', - nextProjectPath, - `列出 ${nextProjectPath} 的 checkpoint`, - '准备列出项目 checkpoint。', - () => void executeProjectCheckpoints(true, true, false), - )) - ) { - return; - } - const result = await invoke( - 'list_local_project_files', - { projectPath: nextProjectPath }, - ); - const manifestFiles = sortCheckpointManifestFiles(result.files); - const visibleFiles = manifestFiles.slice(0, 5); - if ( - announceToChat && - visibleFiles.length > 0 && - !skipReadPolicyConfirm && - (await queueProjectPolicyConfirmationIfNeeded( - invoke, - 'file.read', - nextProjectPath, - `读取 ${nextProjectPath} 的 checkpoint manifest`, - '准备读取 checkpoint manifest。', - () => void executeProjectCheckpoints(true, true, true), - )) - ) { - return; - } - const checkpoints: LocalProjectCheckpointSummary[] = []; - for (const file of visibleFiles) { - const manifest = await invoke( - 'read_local_project_file', - { - projectPath: nextProjectPath, - relativePath: file.path, - commandId: 'file.read', - }, - ); - checkpoints.push(checkpointSummaryFromManifest(file, manifest.content)); - } - setProjectFiles(result.files); - setProjectCheckpoints(checkpoints); - setFileStatus(`已列出 ${manifestFiles.length} 个 checkpoint`); - setCommandLog((current) => [ - ...current, - 'file.list', - ...(visibleFiles.length > 0 ? ['file.read checkpoint manifests'] : []), - ]); - if (announceToChat) { - setMessages((current) => [ - ...current, - { - role: 'assistant', - text: summarizeProjectCheckpoints( - checkpoints, - manifestFiles.length - visibleFiles.length, - ), - }, - ]); - } - } catch (error) { - const message = error instanceof Error ? error.message : String(error); - setFileStatus(message); - if (announceToChat) { - setMessages((current) => [ - ...current, - { role: 'assistant', text: message }, - ]); - } - } - } - - async function executeProjectDiff( - checkpointId: string, - announceToChat: boolean, - skipPolicyConfirm = false, - ) { - const invoke = resolveTauriInvoke(); - if (!invoke) { - setFileStatus('需要在 Tauri App 内运行'); - if (announceToChat) { - setMessages((current) => [ - ...current, - { role: 'assistant', text: '需要在 Tauri App 内运行。' }, - ]); - } - return; - } - const nextProjectPath = announceToChat - ? requireChatProjectForUserAction() - : projectPath; - if (!nextProjectPath) { - return; - } - - try { - if ( - announceToChat && - !skipPolicyConfirm && - (await queueProjectPolicyConfirmationIfNeeded( - invoke, - 'project.diff', - nextProjectPath, - `对比 ${nextProjectPath} 的 checkpoint:${checkpointId}`, - '准备对比项目 checkpoint。', - () => void executeProjectDiff(checkpointId, true, true), - )) - ) { - return; - } - const result = await invoke( - 'diff_local_project_checkpoint', - { projectPath: nextProjectPath, checkpointId }, - ); - setFileStatus(`已对比 checkpoint:${result.checkpointId}`); - setCommandLog((current) => [...current, 'project.diff']); - if (announceToChat) { - setMessages((current) => [ - ...current, - { role: 'assistant', text: summarizeProjectDiff(result) }, - ]); - } - } catch (error) { - const message = error instanceof Error ? error.message : String(error); - setFileStatus(message); if (announceToChat) { setMessages((current) => [ ...current, @@ -7746,7 +2678,6 @@ export function App({ ) { const invoke = resolveTauriInvoke(); if (!invoke) { - setFileStatus('需要在 Tauri App 内运行'); if (announceToChat) { setMessages((current) => [ ...current, @@ -7767,10 +2698,6 @@ export function App({ 'restore_local_project_checkpoint', { projectPath: nextProjectPath, checkpointId }, ); - setFileStatus( - `已回滚 ${result.restoredCount} 个文件,删除 ${result.deletedCount} 个新增文件`, - ); - setCommandLog((current) => [...current, 'project.restore']); void refreshManifest(nextProjectPath); if (announceToChat) { setMessages((current) => [ @@ -7783,7 +2710,6 @@ export function App({ } } catch (error) { const message = error instanceof Error ? error.message : String(error); - setFileStatus(message); if (announceToChat) { setMessages((current) => [ ...current, @@ -7793,291 +2719,12 @@ export function App({ } } - async function executeProjectPolicyRead(announceToChat: boolean) { - const invoke = resolveTauriInvoke(); - if (!invoke) { - setProjectStatus('需要在 Tauri App 内运行'); - if (announceToChat) { - setMessages((current) => [ - ...current, - { role: 'assistant', text: '需要在 Tauri App 内运行。' }, - ]); - } - return; - } - const nextProjectPath = announceToChat - ? requireChatProjectForUserAction() - : projectPath; - if (!nextProjectPath) { - return; - } - - try { - const result = await invoke( - 'read_project_permission_policy', - { projectPath: nextProjectPath }, - ); - setProjectStatus('已读取权限策略'); - setCommandLog((current) => [...current, 'project.policy_read']); - if (announceToChat) { - setMessages((current) => [ - ...current, - { role: 'assistant', text: summarizeProjectPolicy(result) }, - ]); - } - } catch (error) { - const message = error instanceof Error ? error.message : String(error); - setProjectStatus(message); - if (announceToChat) { - setMessages((current) => [ - ...current, - { role: 'assistant', text: message }, - ]); - } - } - } - - async function queueProjectPolicyMutation( - commandId: string, - mode: 'deny' | 'allow' | 'confirm' | 'auto', - ) { - const invoke = resolveTauriInvoke(); - if (!invoke) { - setMessages((current) => [ - ...current, - { role: 'assistant', text: '需要在 Tauri App 内运行。' }, - ]); - return; - } - const nextProjectPath = requireChatProjectForUserAction(); - if (!nextProjectPath) { - return; - } - - try { - const result = await invoke( - 'read_project_permission_policy', - { projectPath: nextProjectPath }, - ); - const alreadyDenied = result.policy.deniedCommands.includes(commandId); - const alreadyConfirmed = - result.policy.confirmCommands.includes(commandId); - if (mode === 'deny' && alreadyDenied) { - setMessages((current) => [ - ...current, - { role: 'assistant', text: `命令已在拒绝列表中:${commandId}` }, - ]); - return; - } - if (mode === 'allow' && !alreadyDenied) { - setMessages((current) => [ - ...current, - { role: 'assistant', text: `命令不在拒绝列表中:${commandId}` }, - ]); - return; - } - if (mode === 'confirm' && alreadyConfirmed) { - setMessages((current) => [ - ...current, - { role: 'assistant', text: `命令已在确认列表中:${commandId}` }, - ]); - return; - } - if (mode === 'auto' && !alreadyConfirmed) { - setMessages((current) => [ - ...current, - { role: 'assistant', text: `命令不在确认列表中:${commandId}` }, - ]); - return; - } - const deniedCommands = - mode === 'deny' - ? [...new Set([...result.policy.deniedCommands, commandId])] - : mode === 'allow' - ? result.policy.deniedCommands.filter( - (value) => value !== commandId, - ) - : mode === 'confirm' - ? result.policy.deniedCommands.filter( - (value) => value !== commandId, - ) - : result.policy.deniedCommands; - const confirmCommands = - mode === 'confirm' - ? [...new Set([...result.policy.confirmCommands, commandId])] - : mode === 'deny' - ? result.policy.confirmCommands.filter( - (value) => value !== commandId, - ) - : mode === 'auto' - ? result.policy.confirmCommands.filter( - (value) => value !== commandId, - ) - : result.policy.confirmCommands; - queuePendingCommand({ - id: 'project.policy_write', - policy: { ...result.policy, deniedCommands, confirmCommands }, - }); - const action = - mode === 'deny' - ? '拒绝' - : mode === 'allow' - ? '允许' - : mode === 'confirm' - ? '确认' - : '自动执行'; - setMessages((current) => [ - ...current, - { - role: 'assistant', - text: `准备${action}命令:${commandId}`, - }, - ]); - } catch (error) { - setMessages((current) => [ - ...current, - { - role: 'assistant', - text: error instanceof Error ? error.message : String(error), - }, - ]); - } - } - - async function queueAgentPolicyMutation( - agentId: string, - commandId: string, - mode: 'deny' | 'allow' | 'confirm' | 'auto', - ) { - const invoke = resolveTauriInvoke(); - if (!invoke) { - setMessages((current) => [ - ...current, - { role: 'assistant', text: '需要在 Tauri App 内运行。' }, - ]); - return; - } - const nextProjectPath = requireChatProjectForUserAction(); - if (!nextProjectPath) { - return; - } - - try { - const result = await invoke( - 'read_project_permission_policy', - { projectPath: nextProjectPath }, - ); - const agentPolicies = { ...(result.policy.agentPolicies ?? {}) }; - const agentPolicy = agentPolicies[agentId] ?? { - deniedCommands: [], - confirmCommands: [], - }; - const alreadyDenied = agentPolicy.deniedCommands.includes(commandId); - const alreadyConfirmed = agentPolicy.confirmCommands.includes(commandId); - if (mode === 'deny' && alreadyDenied) { - setMessages((current) => [ - ...current, - { - role: 'assistant', - text: `Agent ${agentId} 的命令已在拒绝列表中:${commandId}`, - }, - ]); - return; - } - if (mode === 'allow' && !alreadyDenied) { - setMessages((current) => [ - ...current, - { - role: 'assistant', - text: `Agent ${agentId} 的命令不在拒绝列表中:${commandId}`, - }, - ]); - return; - } - if (mode === 'confirm' && alreadyConfirmed) { - setMessages((current) => [ - ...current, - { - role: 'assistant', - text: `Agent ${agentId} 的命令已在确认列表中:${commandId}`, - }, - ]); - return; - } - if (mode === 'auto' && !alreadyConfirmed) { - setMessages((current) => [ - ...current, - { - role: 'assistant', - text: `Agent ${agentId} 的命令不在确认列表中:${commandId}`, - }, - ]); - return; - } - const deniedCommands = - mode === 'deny' - ? [...new Set([...agentPolicy.deniedCommands, commandId])] - : mode === 'allow' - ? agentPolicy.deniedCommands.filter((value) => value !== commandId) - : mode === 'confirm' - ? agentPolicy.deniedCommands.filter( - (value) => value !== commandId, - ) - : agentPolicy.deniedCommands; - const confirmCommands = - mode === 'confirm' - ? [...new Set([...agentPolicy.confirmCommands, commandId])] - : mode === 'deny' - ? agentPolicy.confirmCommands.filter((value) => value !== commandId) - : mode === 'auto' - ? agentPolicy.confirmCommands.filter( - (value) => value !== commandId, - ) - : agentPolicy.confirmCommands; - agentPolicies[agentId] = { - deniedCommands, - confirmCommands, - }; - queuePendingCommand({ - id: 'project.policy_write', - policy: { - ...result.policy, - agentPolicies, - }, - }); - const action = - mode === 'deny' - ? '拒绝' - : mode === 'allow' - ? '允许' - : mode === 'confirm' - ? '确认' - : '自动执行'; - setMessages((current) => [ - ...current, - { - role: 'assistant', - text: `准备${action} Agent ${agentId} 命令:${commandId}`, - }, - ]); - } catch (error) { - setMessages((current) => [ - ...current, - { - role: 'assistant', - text: error instanceof Error ? error.message : String(error), - }, - ]); - } - } - async function executeProjectPolicyWrite( policy: ProjectPermissionPolicy, announceToChat: boolean, ) { const invoke = resolveTauriInvoke(); if (!invoke) { - setProjectStatus('需要在 Tauri App 内运行'); if (announceToChat) { setMessages((current) => [ ...current, @@ -8098,8 +2745,6 @@ export function App({ 'write_project_permission_policy', { projectPath: nextProjectPath, policy }, ); - setProjectStatus('已写入权限策略'); - setCommandLog((current) => [...current, 'project.policy_write']); if (announceToChat) { setMessages((current) => [ ...current, @@ -8108,7 +2753,6 @@ export function App({ } } catch (error) { const message = error instanceof Error ? error.message : String(error); - setProjectStatus(message); if (announceToChat) { setMessages((current) => [ ...current, @@ -8118,345 +2762,6 @@ export function App({ } } - async function executeProjectFiles( - announceToChat: boolean, - skipPolicyConfirm = false, - ) { - const invoke = resolveTauriInvoke(); - if (!invoke) { - setFileStatus('需要在 Tauri App 内运行'); - if (announceToChat) { - setMessages((current) => [ - ...current, - { role: 'assistant', text: '需要在 Tauri App 内运行。' }, - ]); - } - return; - } - const nextProjectPath = resolveChatProjectPath(localProject); - if (!nextProjectPath) { - setFileStatus('请先初始化本地项目'); - if (announceToChat) { - setMessages((current) => [ - ...current, - { role: 'assistant', text: '请先用 /project 设置本地项目。' }, - ]); - } - return; - } - - try { - if ( - announceToChat && - !skipPolicyConfirm && - (await queueProjectPolicyConfirmationIfNeeded( - invoke, - 'file.list', - nextProjectPath, - `列出 ${nextProjectPath} 的项目文件`, - '准备列出项目文件。', - () => void executeProjectFiles(true, true), - )) - ) { - return; - } - const result = await invoke( - 'list_local_project_files', - { projectPath: nextProjectPath }, - ); - setProjectFiles(result.files); - setFileStatus(`已列出 ${result.files.length} 项`); - setCommandLog((current) => [...current, 'file.list']); - if (announceToChat) { - setMessages((current) => [ - ...current, - { role: 'assistant', text: summarizeProjectFiles(result.files) }, - ]); - } - } catch (error) { - const message = error instanceof Error ? error.message : String(error); - setFileStatus(message); - if (announceToChat) { - setMessages((current) => [ - ...current, - { role: 'assistant', text: message }, - ]); - } - } - } - - async function executeProjectAssets( - announceToChat: boolean, - skipPolicyConfirm = false, - ) { - const invoke = resolveTauriInvoke(); - if (!invoke) { - setAssetStatus('需要在 Tauri App 内运行'); - if (announceToChat) { - setMessages((current) => [ - ...current, - { role: 'assistant', text: '需要在 Tauri App 内运行。' }, - ]); - } - return; - } - const nextProjectPath = resolveChatProjectPath(localProject); - if (!nextProjectPath) { - setAssetStatus('请先初始化本地项目'); - if (announceToChat) { - setMessages((current) => [ - ...current, - { role: 'assistant', text: '请先用 /project 设置本地项目。' }, - ]); - } - return; - } - - try { - if ( - announceToChat && - !skipPolicyConfirm && - (await queueProjectPolicyConfirmationIfNeeded( - invoke, - 'asset.list', - nextProjectPath, - `列出 ${nextProjectPath} 的项目资产`, - '准备列出项目资产。', - () => void executeProjectAssets(true, true), - )) - ) { - return; - } - const nextManifest = await invoke( - 'get_local_game_manifest', - { projectPath: nextProjectPath, commandId: 'asset.list' }, - ); - setManifest(nextManifest); - setAssetStatus(`已列出 ${nextManifest.assets.length} 个资产`); - setCommandLog((current) => [...current, 'asset.list']); - if (announceToChat) { - const firstReadableAsset = firstReadableProjectAssetPath(nextManifest); - setMessages((current) => [ - ...current, - { - role: 'assistant', - text: summarizeProjectAssets(nextManifest), - draftCommand: firstReadableAsset - ? `/read ${firstReadableAsset}` - : undefined, - draftCommandLabel: firstReadableAsset ? '读取首个资产' : undefined, - }, - ]); - } - } catch (error) { - const message = error instanceof Error ? error.message : String(error); - setAssetStatus(message); - if (announceToChat) { - setMessages((current) => [ - ...current, - { role: 'assistant', text: message }, - ]); - } - } - } - - async function executeProjectFileReadChat( - relativePath: string, - skipPolicyConfirm = false, - ) { - const invoke = resolveTauriInvoke(); - if (!invoke) { - setFileStatus('需要在 Tauri App 内运行'); - setMessages((current) => [ - ...current, - { role: 'assistant', text: '需要在 Tauri App 内运行。' }, - ]); - return; - } - const nextProjectPath = resolveChatProjectPath(localProject); - if (!nextProjectPath) { - setFileStatus('请先初始化本地项目'); - setMessages((current) => [ - ...current, - { role: 'assistant', text: '请先用 /project 设置本地项目。' }, - ]); - return; - } - - try { - const commandId = isAgentTraceFilePath(relativePath) - ? 'agent.trace_read' - : 'file.read'; - if ( - !skipPolicyConfirm && - (await queueProjectPolicyConfirmationIfNeeded( - invoke, - commandId, - nextProjectPath, - `读取 ${nextProjectPath} 的 ${relativePath}`, - '准备读取项目文件。', - () => void executeProjectFileReadChat(relativePath, true), - )) - ) { - return; - } - const result = await invoke( - 'read_local_project_file', - { projectPath: nextProjectPath, relativePath, commandId }, - ); - setFilePath(result.path); - setFileDraft(result.content); - setFileStatus(`已读取:${result.path}`); - setCommandLog((current) => [...current, 'file.read']); - setMessages((current) => [ - ...current, - { role: 'assistant', text: summarizeProjectFileContent(result) }, - ]); - } catch (error) { - const message = error instanceof Error ? error.message : String(error); - setFileStatus(message); - setMessages((current) => [ - ...current, - { role: 'assistant', text: message }, - ]); - } - } - - async function executeProjectTasks( - announceToChat: boolean, - skipPolicyConfirm = false, - ) { - const invoke = resolveTauriInvoke(); - if (!invoke) { - setProjectStatus('需要在 Tauri App 内运行'); - if (announceToChat) { - setMessages((current) => [ - ...current, - { role: 'assistant', text: '需要在 Tauri App 内运行。' }, - ]); - } - return; - } - const nextProjectPath = resolveChatProjectPath(localProject); - if (!nextProjectPath) { - setProjectStatus('请先初始化本地项目'); - if (announceToChat) { - setMessages((current) => [ - ...current, - { role: 'assistant', text: '请先用 /project 设置本地项目。' }, - ]); - } - return; - } - - try { - if ( - announceToChat && - !skipPolicyConfirm && - (await queueProjectPolicyConfirmationIfNeeded( - invoke, - 'task.list', - nextProjectPath, - `读取 ${nextProjectPath} 的任务拆分`, - '准备读取任务拆分。', - () => void executeProjectTasks(true, true), - )) - ) { - return; - } - const nextManifest = await invoke( - 'get_local_game_manifest', - { projectPath: nextProjectPath, commandId: 'task.list' }, - ); - setManifest(nextManifest); - setProjectStatus('已读取任务拆分'); - setCommandLog((current) => [...current, 'task.list']); - if (announceToChat) { - setMessages((current) => [ - ...current, - { role: 'assistant', text: summarizeProjectTasks(nextManifest) }, - ]); - } - } catch (error) { - const message = error instanceof Error ? error.message : String(error); - setProjectStatus(message); - if (announceToChat) { - setMessages((current) => [ - ...current, - { role: 'assistant', text: message }, - ]); - } - } - } - - async function executeAgentTraceChat(skipPolicyConfirm = false) { - const invoke = resolveTauriInvoke(); - if (!invoke) { - setAgentRunStatus('需要在 Tauri App 内运行'); - setMessages((current) => [ - ...current, - { role: 'assistant', text: '需要在 Tauri App 内运行。' }, - ]); - return; - } - const nextProjectPath = resolveChatProjectPath(localProject); - if (!nextProjectPath) { - setAgentRunStatus('请先初始化本地项目'); - setMessages((current) => [ - ...current, - { role: 'assistant', text: '请先用 /project 设置本地项目。' }, - ]); - return; - } - - try { - if ( - !skipPolicyConfirm && - (await queueProjectPolicyConfirmationIfNeeded( - invoke, - 'agent.trace_read', - nextProjectPath, - `读取 ${nextProjectPath} 的最近 Agent run trace`, - '准备读取 Agent run trace。', - () => void executeAgentTraceChat(true), - )) - ) { - return; - } - const result = await invoke( - 'read_local_project_file', - { - projectPath: nextProjectPath, - relativePath: '.agent/run.latest.json', - commandId: 'agent.trace_read', - }, - ); - const trace = parseAgentRunTrace(result.content); - setAgentRunTrace(trace); - setAgentRunStatus(formatAgentRunStatus(trace)); - setCommandLog((current) => [ - ...current, - 'agent.trace_read', - 'file.read .agent/run.latest.json', - ]); - const traceSummary = summarizeAgentRunCompletionForChat(trace); - setMessages((current) => [ - ...current, - { role: 'assistant', ...traceSummary }, - ]); - } catch (error) { - const rawMessage = error instanceof Error ? error.message : String(error); - const message = isMissingAgentRunTraceError(rawMessage) - ? '暂无最近 Agent trace。先生成一次游戏草案后再查看。' - : rawMessage; - setAgentRunStatus(message); - setMessages((current) => [ - ...current, - { role: 'assistant', text: message }, - ]); - } - } - async function executeAgentRunControl( action: string, detail: string | undefined, @@ -8465,7 +2770,6 @@ export function App({ ) { const invoke = resolveTauriInvoke(); if (!invoke) { - setAgentRunStatus('需要在 Tauri App 内运行'); if (announceToChat) { setMessages((current) => [ ...current, @@ -8507,16 +2811,6 @@ export function App({ action, detail, }); - setAgentRunStatus( - `${result.status} · ${result.lifecycleStatus} · ${result.nextStep}`, - ); - setCommandLog((current) => [ - ...current, - commandId, - 'file.write .agent/activity.jsonl', - 'file.write .agent/output.jsonl', - 'file.write .agent/context.bundle.json', - ]); if (announceToChat && action === 'status') { appendLocalPermissionLog( nextProjectPath, @@ -8565,7 +2859,6 @@ export function App({ } catch (error) { const rawMessage = error instanceof Error ? error.message : String(error); const message = formatAgentRunControlError(action, rawMessage); - setAgentRunStatus(message); if (announceToChat) { setMessages((current) => [ ...current, @@ -8575,172 +2868,12 @@ export function App({ } } - function queueAgentRunControlFromPanel(action: 'kill' | 'retry' | 'resume') { - if (!requireChatProjectForUserAction()) { - return; - } - queuePendingCommand({ id: `agent.${action}` }); - setAgentRunStatus('等待确认'); - } - - async function executeAgentAuditChat( - confirmedPolicyCommands: GameCreationAppCommandDescriptor['id'][] = [], - ) { - const invoke = resolveTauriInvoke(); - if (!invoke) { - setAgentRunStatus('需要在 Tauri App 内运行'); - setMessages((current) => [ - ...current, - { role: 'assistant', text: '需要在 Tauri App 内运行。' }, - ]); - return; - } - const nextProjectPath = resolveChatProjectPath(localProject); - if (!nextProjectPath) { - setAgentRunStatus('请先初始化本地项目'); - setMessages((current) => [ - ...current, - { role: 'assistant', text: '请先用 /project 设置本地项目。' }, - ]); - return; - } - - try { - if ( - !confirmedPolicyCommands.includes('agent.audit') && - (await queueProjectPolicyConfirmationIfNeeded( - invoke, - 'agent.audit', - nextProjectPath, - `审计 ${nextProjectPath} 的 Agent 能力证据`, - '准备审计 Agent 能力证据。', - () => void executeAgentAuditChat(['agent.audit']), - )) - ) { - return; - } - const policyView = await invoke( - 'read_project_permission_policy', - { projectPath: nextProjectPath }, - ); - const auditReadCommands: Array = [ - 'project.status', - 'file.list', - 'file.read', - 'agent.trace_read', - ]; - const confirmCommandId = auditReadCommands.find( - (commandId) => - policyView.policy.confirmCommands.includes(commandId) && - !confirmedPolicyCommands.includes(commandId), - ); - if (confirmCommandId) { - requestProjectPolicyConfirmation( - confirmCommandId, - nextProjectPath, - `审计 ${nextProjectPath} 需要读取 ${confirmCommandId}`, - () => - void executeAgentAuditChat([ - ...confirmedPolicyCommands, - confirmCommandId, - ]), - ); - setMessages((current) => [ - ...current, - { role: 'assistant', text: `准备确认审计读取:${confirmCommandId}` }, - ]); - return; - } - const nextManifest = await invoke( - 'get_local_game_manifest', - { projectPath: nextProjectPath, commandId: 'agent.audit' }, - ); - const fileResult = await invoke( - 'list_local_project_files', - { projectPath: nextProjectPath }, - ); - let commandLogContent = ''; - try { - const commandLogResult = await invoke( - 'read_local_project_file', - { - projectPath: nextProjectPath, - relativePath: '.agent/logs/command.log', - commandId: 'file.read', - }, - ); - commandLogContent = commandLogResult.content; - } catch { - commandLogContent = ''; - } - let trace: GameCreationAgentRunTrace | null = null; - try { - const traceResult = await invoke( - 'read_local_project_file', - { - projectPath: nextProjectPath, - relativePath: '.agent/run.latest.json', - commandId: 'agent.trace_read', - }, - ); - trace = parseAgentRunTrace(traceResult.content); - } catch { - trace = null; - } - - setManifest(nextManifest); - setProjectFiles(fileResult.files); - setAgentRunTrace(trace); - setAgentRunStatus( - trace ? formatAgentRunStatus(trace) : '还没有最近一次 Agent run', - ); - setCommandLog((current) => [ - ...current, - 'agent.audit', - 'project.status', - 'file.list', - ...(commandLogContent ? ['file.read .agent/logs/command.log'] : []), - ...(trace ? ['agent.trace_read'] : []), - ]); - setMessages((current) => [ - ...current, - { - role: 'assistant', - text: summarizeAgentAudit( - nextManifest, - nextProjectPath, - fileResult.files, - trace, - commandLogContent, - ), - }, - ]); - } catch (error) { - const message = error instanceof Error ? error.message : String(error); - setAgentRunStatus(message); - setMessages((current) => [ - ...current, - { role: 'assistant', text: message }, - ]); - } - } - - async function handlePreviewStart() { - const nextProjectPath = resolveChatProjectPath(localProject) ?? projectPath; - requestCommandConfirmation( - 'preview.start', - `启动 ${nextProjectPath}/game/ 并载入客户端运行视图`, - () => void executePreviewStart(false), - ); - } - async function executePreviewStart( announceToChat: boolean, skipPolicyConfirm = false, ) { const invoke = resolveTauriInvoke(); if (!invoke) { - setPreviewStatus('需要在 Tauri App 内运行'); if (announceToChat) { setMessages((current) => [ ...current, @@ -8751,7 +2884,6 @@ export function App({ } const nextProjectPath = resolveChatProjectPath(localProject); if (!nextProjectPath) { - setPreviewStatus('请先初始化本地项目'); if (announceToChat) { setMessages((current) => [ ...current, @@ -8761,7 +2893,6 @@ export function App({ return; } - setPreviewStatus('正在启动'); try { if ( announceToChat && @@ -8782,9 +2913,7 @@ export function App({ { projectPath: nextProjectPath }, ); updateClientPreview(result); - setPreviewStatus(`运行中:127.0.0.1:${result.port}`); void refreshManifest(nextProjectPath); - setCommandLog((current) => [...current, 'preview.start']); if (announceToChat) { setMessages((current) => [ ...current, @@ -8796,7 +2925,6 @@ export function App({ } } catch (error) { const message = error instanceof Error ? error.message : String(error); - setPreviewStatus(message); if (announceToChat) { setMessages((current) => [ ...current, @@ -8812,7 +2940,6 @@ export function App({ ) { const invoke = resolveTauriInvoke(); if (!invoke) { - setPreviewStatus('需要在 Tauri App 内运行'); if (announceToChat) { setMessages((current) => [ ...current, @@ -8859,9 +2986,7 @@ export function App({ port: result.port, root: result.root, }); - setPreviewStatus(`运行中:127.0.0.1:${result.port}`); } - setCommandLog((current) => [...current, 'preview.open']); if (announceToChat) { setMessages((current) => [ ...current, @@ -8875,7 +3000,6 @@ export function App({ } } catch (error) { const message = error instanceof Error ? error.message : String(error); - setPreviewStatus(message); if (announceToChat) { setMessages((current) => [ ...current, @@ -8885,361 +3009,6 @@ export function App({ } } - async function handlePreviewStatus() { - void executePreviewStatus(false); - } - - async function executePreviewStatus( - announceToChat: boolean, - skipPolicyConfirm = false, - ) { - const invoke = resolveTauriInvoke(); - if (!invoke) { - setPreviewStatus('需要在 Tauri App 内运行'); - if (announceToChat) { - setMessages((current) => [ - ...current, - { role: 'assistant', text: '需要在 Tauri App 内运行。' }, - ]); - } - return; - } - const nextProjectPath = announceToChat - ? requireChatProjectForUserAction() - : resolveChatProjectPath(localProject); - if (announceToChat && !nextProjectPath) { - return; - } - - try { - if ( - announceToChat && - nextProjectPath && - !skipPolicyConfirm && - (await queueProjectPolicyConfirmationIfNeeded( - invoke, - 'preview.status', - nextProjectPath, - `查看 ${nextProjectPath} 的预览状态`, - '准备查看预览状态。', - () => void executePreviewStatus(true, true), - )) - ) { - return; - } - const result = await invoke( - 'get_local_game_preview_status', - nextProjectPath ? { projectPath: nextProjectPath } : undefined, - ); - if ( - result.status === 'running' && - result.url && - result.port && - result.root - ) { - updateClientPreview({ - url: result.url, - port: result.port, - root: result.root, - }); - setPreviewStatus(`运行中:127.0.0.1:${result.port}`); - } else { - updateClientPreview(null); - setPreviewStatus('未启动'); - } - setCommandLog((current) => [...current, 'preview.status']); - if (announceToChat && nextProjectPath) { - appendLocalPermissionLog( - nextProjectPath, - 'command.auto', - 'preview.status', - ); - } - if (announceToChat) { - const previewRunning = result.status === 'running' && result.url; - setMessages((current) => [ - ...current, - { - role: 'assistant', - text: previewRunning ? `预览运行中:${result.url}` : '预览未启动。', - draftCommand: previewRunning ? '/open-preview' : '/preview', - draftCommandLabel: previewRunning - ? '填入打开预览命令' - : '填入启动预览命令', - }, - ]); - } - } catch (error) { - const message = error instanceof Error ? error.message : String(error); - setPreviewStatus(message); - if (announceToChat) { - setMessages((current) => [ - ...current, - { role: 'assistant', text: message }, - ]); - } - } - } - - async function handlePreviewStop() { - void executePreviewStop(false); - } - - async function executePreviewStop( - announceToChat: boolean, - skipPolicyConfirm = false, - ) { - const invoke = resolveTauriInvoke(); - if (!invoke) { - setPreviewStatus('需要在 Tauri App 内运行'); - if (announceToChat) { - setMessages((current) => [ - ...current, - { role: 'assistant', text: '需要在 Tauri App 内运行。' }, - ]); - } - return; - } - const nextProjectPath = announceToChat - ? requireChatProjectForUserAction() - : resolveChatProjectPath(localProject); - if (!nextProjectPath) { - setPreviewStatus('请先初始化本地项目'); - return; - } - - try { - if ( - announceToChat && - !skipPolicyConfirm && - (await queueProjectPolicyConfirmationIfNeeded( - invoke, - 'preview.stop', - nextProjectPath, - `停止 ${nextProjectPath} 的本地预览`, - '准备停止本地预览。', - () => void executePreviewStop(true, true), - )) - ) { - return; - } - await invoke('stop_local_game_preview', { - projectPath: nextProjectPath, - }); - updateClientPreview(null); - setPreviewStatus('已停止'); - setCommandLog((current) => [...current, 'preview.stop']); - if (announceToChat) { - appendLocalPermissionLog( - nextProjectPath, - 'command.auto', - 'preview.stop', - ); - } - if (announceToChat) { - setMessages((current) => [ - ...current, - { role: 'assistant', text: '预览已停止。' }, - ]); - } - } catch (error) { - const message = error instanceof Error ? error.message : String(error); - setPreviewStatus(message); - if (announceToChat) { - setMessages((current) => [ - ...current, - { role: 'assistant', text: message }, - ]); - } - } - } - - async function handleMemoryRead(skipPolicyConfirm = false) { - const nextProjectPath = validateMemoryPanelProjectPath(); - if (!nextProjectPath) { - return; - } - const invoke = resolveTauriInvoke(); - if (!invoke) { - setMemoryStatus('需要在 Tauri App 内运行'); - return; - } - - setMemoryStatus('正在读取'); - try { - if (!skipPolicyConfirm) { - const policyView = await invoke( - 'read_project_permission_policy', - { projectPath: nextProjectPath }, - ); - if (policyView.policy.confirmCommands.includes('memory.read')) { - requestProjectPolicyConfirmation( - 'memory.read', - nextProjectPath, - `读取 ${nextProjectPath}/${memoryScopePath(memoryScope)}`, - () => void handleMemoryRead(true), - ); - setMemoryStatus('等待确认'); - return; - } - } - const result = await invoke( - 'read_local_game_memory', - { projectPath: nextProjectPath, scope: memoryScope }, - ); - setMemoryDraft(result.content); - setMemoryStatus( - result.exists ? `已读取:${result.path}` : '记忆文件不存在', - ); - setCommandLog((current) => [...current, 'memory.read']); - } catch (error) { - setMemoryStatus(error instanceof Error ? error.message : String(error)); - } - } - - function validateMemoryPanelProjectPath() { - const nextProjectPath = projectPath.trim(); - if (!nextProjectPath || !isAbsoluteProjectPath(nextProjectPath)) { - setMemoryStatus('请提供本地项目绝对路径。'); - return null; - } - if (projectPathHasControlCharacter(nextProjectPath)) { - setMemoryStatus('本地项目路径不能包含控制字符。'); - return null; - } - return nextProjectPath; - } - - async function handleMemoryWrite() { - const nextProjectPath = validateMemoryPanelProjectPath(); - if (!nextProjectPath) { - return; - } - requestCommandConfirmation( - 'memory.write', - `保存 ${nextProjectPath}/${memoryScopePath(memoryScope)}`, - () => void executeMemoryWritePanel(nextProjectPath), - ); - } - - async function executeMemoryWritePanel(nextProjectPath: string) { - const invoke = resolveTauriInvoke(); - if (!invoke) { - setMemoryStatus('需要在 Tauri App 内运行'); - return; - } - - setMemoryStatus('正在保存'); - try { - const result = await invoke( - 'write_local_game_memory', - { - projectPath: nextProjectPath, - scope: memoryScope, - content: memoryDraft, - }, - ); - setMemoryStatus(`已保存:${result.path}`); - setCommandLog((current) => [...current, 'memory.write']); - } catch (error) { - setMemoryStatus(error instanceof Error ? error.message : String(error)); - } - } - - async function handleMemoryDelete() { - const nextProjectPath = validateMemoryPanelProjectPath(); - if (!nextProjectPath) { - return; - } - requestCommandConfirmation( - 'memory.delete', - `删除 ${nextProjectPath}/${memoryScopePath(memoryScope)}`, - () => void executeMemoryDeletePanel(nextProjectPath), - ); - } - - async function executeMemoryDeletePanel(nextProjectPath: string) { - const invoke = resolveTauriInvoke(); - if (!invoke) { - setMemoryStatus('需要在 Tauri App 内运行'); - return; - } - - setMemoryStatus('正在删除'); - try { - const result = await invoke( - 'delete_local_game_memory', - { projectPath: nextProjectPath, scope: memoryScope }, - ); - setMemoryDraft(''); - setMemoryStatus(`已删除:${result.path}`); - setCommandLog((current) => [...current, 'memory.delete']); - } catch (error) { - setMemoryStatus(error instanceof Error ? error.message : String(error)); - } - } - - async function executeMemoryReadChat( - scope: MemoryScope, - skipPolicyConfirm = false, - ) { - const invoke = resolveTauriInvoke(); - if (!invoke) { - setMemoryStatus('需要在 Tauri App 内运行'); - setMessages((current) => [ - ...current, - { role: 'assistant', text: '需要在 Tauri App 内运行。' }, - ]); - return; - } - const nextProjectPath = requireChatProjectForUserAction(); - if (!nextProjectPath) { - return; - } - - try { - if ( - !skipPolicyConfirm && - (await queueProjectPolicyConfirmationIfNeeded( - invoke, - 'memory.read', - nextProjectPath, - `读取 ${nextProjectPath}/${memoryScopePath(scope)}`, - '准备读取项目记忆。', - () => void executeMemoryReadChat(scope, true), - )) - ) { - return; - } - const result = await invoke( - 'read_local_game_memory', - { projectPath: nextProjectPath, scope }, - ); - setMemoryScope(scope); - setMemoryDraft(result.content); - setMemoryStatus( - result.exists ? `已读取:${result.path}` : '记忆文件不存在', - ); - setCommandLog((current) => [...current, 'memory.read']); - setMessages((current) => [ - ...current, - { - role: 'assistant', - text: result.exists - ? `${memoryScopeLabel(scope)}记忆:\n${result.content}` - : `${memoryScopeLabel(scope)}记忆为空。`, - }, - ]); - } catch (error) { - const message = error instanceof Error ? error.message : String(error); - setMemoryStatus(message); - setMessages((current) => [ - ...current, - { role: 'assistant', text: message }, - ]); - } - } - async function executeMemoryWrite( scope: MemoryScope, content: string, @@ -9247,7 +3016,6 @@ export function App({ ) { const invoke = resolveTauriInvoke(); if (!invoke) { - setMemoryStatus('需要在 Tauri App 内运行'); setMessages((current) => [ ...current, { role: 'assistant', text: '需要在 Tauri App 内运行。' }, @@ -9260,32 +3028,6 @@ export function App({ } try { - const currentMemory = - mode === 'append' - ? await invoke('read_local_game_memory', { - projectPath: nextProjectPath, - scope, - }) - : null; - const result = await invoke( - 'write_local_game_memory', - { - projectPath: nextProjectPath, - scope, - content: - mode === 'append' - ? appendMemoryContent(currentMemory?.content ?? '', content) - : content, - }, - ); - setMemoryScope(scope); - setMemoryDraft(result.content); - setMemoryStatus(`已保存:${result.path}`); - setCommandLog((current) => [ - ...current, - ...(mode === 'append' ? ['memory.read'] : []), - 'memory.write', - ]); setMessages((current) => [ ...current, { @@ -9298,7 +3040,6 @@ export function App({ ]); } catch (error) { const message = error instanceof Error ? error.message : String(error); - setMemoryStatus(message); setMessages((current) => [ ...current, { role: 'assistant', text: message }, @@ -9309,7 +3050,6 @@ export function App({ async function executeMemoryDeleteChat(scope: MemoryScope) { const invoke = resolveTauriInvoke(); if (!invoke) { - setMemoryStatus('需要在 Tauri App 内运行'); setMessages((current) => [ ...current, { role: 'assistant', text: '需要在 Tauri App 内运行。' }, @@ -9322,21 +3062,12 @@ export function App({ } try { - const result = await invoke( - 'delete_local_game_memory', - { projectPath: nextProjectPath, scope }, - ); - setMemoryScope(scope); - setMemoryDraft(''); - setMemoryStatus(`已删除:${result.path}`); - setCommandLog((current) => [...current, 'memory.delete']); setMessages((current) => [ ...current, { role: 'assistant', text: `已删除${memoryScopeLabel(scope)}记忆。` }, ]); } catch (error) { const message = error instanceof Error ? error.message : String(error); - setMemoryStatus(message); setMessages((current) => [ ...current, { role: 'assistant', text: message }, @@ -9344,121 +3075,9 @@ export function App({ } } - async function handleLimitedCommandRun( - command: GameCreationAppLimitedRunCommandDescriptor, - ) { - const nextProjectPath = resolveChatProjectPath(localProject) ?? projectPath; - requestCommandConfirmation( - 'command.run_limited', - `运行 ${command.title} 于 ${nextProjectPath}`, - () => void executeLimitedCommand(command.id, false), - ); - } - - async function refreshLimitedLocalCommands() { - const invoke = resolveTauriInvoke(); - if (!invoke) { - setLimitedCommandStatus('需要在 Tauri App 内运行'); - return; - } - setLimitedCommandStatus('正在读取内置命令'); - try { - const commands = await invoke< - GameCreationAppLimitedRunCommandDescriptor[] - >('get_limited_local_commands'); - setLimitedLocalCommands( - commands.length > 0 - ? commands - : [...GAME_CREATION_APP_LIMITED_RUN_COMMANDS], - ); - setLimitedCommandStatus(`已读取 ${commands.length} 个内置命令`); - setCommandLog((current) => [...current, 'command.list_limited']); - } catch (error) { - setLimitedCommandStatus( - error instanceof Error ? error.message : String(error), - ); - } - } - - async function handleProjectLogRead( - relativePath: string, - skipPolicyConfirm = false, - ) { - const invoke = resolveTauriInvoke(); - if (!invoke) { - setProjectLogStatus('需要在 Tauri App 内运行'); - return; - } - const nextProjectPath = resolveChatProjectPath(localProject); - if (!nextProjectPath) { - setProjectLogStatus('请先初始化本地项目'); - return; - } - setProjectLogStatus(`正在读取 ${relativePath}`); - try { - if ( - !skipPolicyConfirm && - (await queueProjectPolicyConfirmationIfNeeded( - invoke, - 'file.read', - nextProjectPath, - `读取 ${nextProjectPath}/${relativePath}`, - '准备读取项目日志。', - () => void handleProjectLogRead(relativePath, true), - )) - ) { - setProjectLogStatus('等待确认'); - return; - } - const result = await invoke( - 'read_local_project_file', - { - projectPath: nextProjectPath, - relativePath, - commandId: 'file.read', - }, - ); - setProjectLogContent(result.content); - setProjectLogStatus(`已读取:${result.path}`); - setCommandLog((current) => [...current, `file.read ${result.path}`]); - } catch (error) { - setProjectLogContent(''); - setProjectLogStatus( - error instanceof Error ? error.message : String(error), - ); - } - } - - async function executeLimitedCommandList() { - const invoke = resolveTauriInvoke(); - let commands: GameCreationAppLimitedRunCommandDescriptor[] = [ - ...GAME_CREATION_APP_LIMITED_RUN_COMMANDS, - ]; - if (invoke) { - try { - const nativeCommands = await invoke< - GameCreationAppLimitedRunCommandDescriptor[] - >('get_limited_local_commands'); - if (nativeCommands.length > 0) { - commands = nativeCommands; - } - } catch { - commands = [...GAME_CREATION_APP_LIMITED_RUN_COMMANDS]; - } - } - setLimitedLocalCommands(commands); - setLimitedCommandStatus(`已读取 ${commands.length} 个内置命令`); - setCommandLog((current) => [...current, 'command.list_limited']); - setMessages((current) => [ - ...current, - { role: 'assistant', text: summarizeLimitedLocalCommands(commands) }, - ]); - } - async function executeRunLocal(announceToChat: boolean) { const invoke = resolveTauriInvoke(); if (!invoke) { - setLimitedCommandStatus('需要在 Tauri App 内运行'); if (announceToChat) { announceProjectChatMessage('需要在 Tauri App 内运行。'); } @@ -9466,34 +3085,18 @@ export function App({ } const nextProjectPath = resolveChatProjectPath(localProject); if (!nextProjectPath) { - setLimitedCommandStatus('请先初始化本地项目'); if (announceToChat) { announceProjectChatMessage('请先用 /project 设置本地项目。'); } return; } - setLimitedCommandStatus('正在运行'); try { - const smoke = directProjectMode - ? null - : await invoke('run_limited_local_command', { - projectPath: nextProjectPath, - commandId: 'game.static_smoke', - }); const previewResult = await invoke( 'start_local_game_preview', { projectPath: nextProjectPath }, ); updateClientPreview(previewResult); - setPreviewStatus(`运行中:127.0.0.1:${previewResult.port}`); - setLimitedCommandStatus(smoke?.output ?? '本地预览已启动'); - setCommandLog((current) => [ - ...current, - 'game.run_local', - ...(smoke ? [`command.run_limited ${smoke.commandId}`] : []), - 'preview.start', - ]); void refreshManifest(nextProjectPath); if (!directProjectMode) { void refreshAgentRunTrace(nextProjectPath); @@ -9505,7 +3108,6 @@ export function App({ } } catch (error) { const message = error instanceof Error ? error.message : String(error); - setLimitedCommandStatus(message); if (announceToChat) { announceProjectChatMessage(message); } @@ -9518,7 +3120,6 @@ export function App({ ) { const invoke = resolveTauriInvoke(); if (!invoke) { - setLimitedCommandStatus('需要在 Tauri App 内运行'); if (announceToChat) { setMessages((current) => [ ...current, @@ -9531,22 +3132,15 @@ export function App({ ? requireChatProjectForUserAction() : resolveChatProjectPath(localProject); if (!nextProjectPath) { - setLimitedCommandStatus('请先初始化本地项目'); return; } - setLimitedCommandStatus('正在运行'); try { const result = await invoke( 'run_limited_local_command', { projectPath: nextProjectPath, commandId }, ); const summary = `${result.output}\n日志:${result.logPath}`; - setLimitedCommandStatus(result.output); - setCommandLog((current) => [ - ...current, - `command.run_limited ${result.commandId}`, - ]); if (announceToChat) { setMessages((current) => [ ...current, @@ -9556,7 +3150,6 @@ export function App({ void refreshManifest(nextProjectPath); } catch (error) { const message = error instanceof Error ? error.message : String(error); - setLimitedCommandStatus(message); if (announceToChat) { setMessages((current) => [ ...current, @@ -9566,249 +3159,6 @@ export function App({ } } - async function handleFileList(skipPolicyConfirm = false) { - const nextProjectPath = validateFilePanelProjectPath(); - if (!nextProjectPath) { - return; - } - const invoke = resolveTauriInvoke(); - if (!invoke) { - setFileStatus('需要在 Tauri App 内运行'); - return; - } - - setFileStatus('正在列出'); - try { - if (!skipPolicyConfirm) { - const policyView = await invoke( - 'read_project_permission_policy', - { projectPath: nextProjectPath }, - ); - if (policyView.policy.confirmCommands.includes('file.list')) { - requestProjectPolicyConfirmation( - 'file.list', - nextProjectPath, - `列出 ${nextProjectPath} 的项目文件`, - () => void handleFileList(true), - ); - setFileStatus('等待确认'); - return; - } - } - const result = await invoke( - 'list_local_project_files', - { projectPath: nextProjectPath }, - ); - setProjectFiles(result.files); - setFileStatus(`已列出 ${result.files.length} 项`); - setCommandLog((current) => [...current, 'file.list']); - } catch (error) { - setFileStatus(error instanceof Error ? error.message : String(error)); - } - } - - function validateFilePanelProjectPath() { - const nextProjectPath = projectPath.trim(); - if (!nextProjectPath || !isAbsoluteProjectPath(nextProjectPath)) { - setFileStatus('请提供本地项目绝对路径。'); - return null; - } - if (projectPathHasControlCharacter(nextProjectPath)) { - setFileStatus('本地项目路径不能包含控制字符。'); - return null; - } - return nextProjectPath; - } - - function validateFilePanelPath() { - if (!isSafeProjectRelativePath(filePath)) { - setFileStatus('文件路径必须是项目内相对路径。'); - return null; - } - return filePath.trim(); - } - - async function handleFileRead(skipPolicyConfirm = false) { - const nextProjectPath = validateFilePanelProjectPath(); - if (!nextProjectPath) { - return; - } - const relativePath = validateFilePanelPath(); - if (!relativePath) { - return; - } - const invoke = resolveTauriInvoke(); - if (!invoke) { - setFileStatus('需要在 Tauri App 内运行'); - return; - } - - setFileStatus('正在读取'); - try { - const commandId = isAgentTraceFilePath(relativePath) - ? 'agent.trace_read' - : 'file.read'; - if (!skipPolicyConfirm) { - const policyView = await invoke( - 'read_project_permission_policy', - { projectPath: nextProjectPath }, - ); - if (policyView.policy.confirmCommands.includes(commandId)) { - requestProjectPolicyConfirmation( - commandId, - nextProjectPath, - `读取 ${nextProjectPath} 的 ${relativePath}`, - () => void handleFileRead(true), - ); - setFileStatus('等待确认'); - return; - } - } - const result = await invoke( - 'read_local_project_file', - { - projectPath: nextProjectPath, - relativePath, - commandId, - }, - ); - setFilePath(result.path); - setFileDraft(result.content); - setFileStatus(`已读取:${result.path}`); - setCommandLog((current) => [...current, 'file.read']); - } catch (error) { - setFileStatus(error instanceof Error ? error.message : String(error)); - } - } - - async function handleFileWrite() { - const nextProjectPath = validateFilePanelProjectPath(); - if (!nextProjectPath) { - return; - } - const relativePath = validateFilePanelPath(); - if (!relativePath) { - return; - } - requestCommandConfirmation( - 'file.write', - `保存 ${nextProjectPath}/${relativePath}`, - () => void executeFileWrite(nextProjectPath, relativePath), - ); - } - - async function executeFileWrite( - nextProjectPath: string, - relativePath: string, - ) { - const invoke = resolveTauriInvoke(); - if (!invoke) { - setFileStatus('需要在 Tauri App 内运行'); - return; - } - - setFileStatus('正在保存'); - try { - const result = await invoke( - 'write_local_project_file', - { projectPath: nextProjectPath, relativePath, content: fileDraft }, - ); - setFileStatus(`已保存:${result.path}`); - setCommandLog((current) => [...current, 'file.write']); - } catch (error) { - setFileStatus(error instanceof Error ? error.message : String(error)); - } - } - - async function handleFileDelete() { - const nextProjectPath = validateFilePanelProjectPath(); - if (!nextProjectPath) { - return; - } - const relativePath = validateFilePanelPath(); - if (!relativePath) { - return; - } - requestCommandConfirmation( - 'file.delete', - `删除 ${nextProjectPath}/${relativePath}`, - () => void executeFileDelete(nextProjectPath, relativePath), - ); - } - - async function executeFileDelete( - nextProjectPath: string, - relativePath: string, - ) { - const invoke = resolveTauriInvoke(); - if (!invoke) { - setFileStatus('需要在 Tauri App 内运行'); - return; - } - - setFileStatus('正在删除'); - try { - const result = await invoke( - 'delete_local_project_file', - { projectPath: nextProjectPath, relativePath }, - ); - setFileDraft(''); - setFileStatus(result.deleted ? `已删除:${result.path}` : '文件不存在'); - setCommandLog((current) => [...current, 'file.delete']); - } catch (error) { - setFileStatus(error instanceof Error ? error.message : String(error)); - } - } - - async function handleAssetRegister() { - const nextProjectPath = projectPath.trim(); - const localPath = assetLocalPath.trim(); - const canvasProjectId = assetCanvasProjectId.trim(); - const resourceId = assetResourceId.trim(); - const nextAssetObjectId = assetObjectId.trim(); - if (!nextProjectPath || !isAbsoluteProjectPath(nextProjectPath)) { - setAssetStatus('请提供本地项目绝对路径。'); - return; - } - if (projectPathHasControlCharacter(nextProjectPath)) { - setAssetStatus('本地项目路径不能包含控制字符。'); - return; - } - if (!isSafeProjectRelativePath(localPath)) { - setAssetStatus('资产路径必须是项目内相对路径。'); - return; - } - if (assetSourceKind === 'canvas') { - if (!canvasProjectId) { - setAssetStatus('请提供画板项目 ID。'); - return; - } - if (!isSafeCanvasProjectId(canvasProjectId)) { - setAssetStatus('画板项目 ID 不能包含控制字符。'); - return; - } - if (!resourceId && !nextAssetObjectId) { - setAssetStatus('请提供资源 ID 或资产对象 ID。'); - return; - } - } - requestCommandConfirmation( - 'asset.register', - `登记 ${nextProjectPath}/${localPath}`, - () => - void executeAssetRegister( - nextProjectPath, - localPath, - assetKind, - assetMediaType, - assetSourceKind, - canvasProjectId, - resourceId, - nextAssetObjectId, - ), - ); - } - async function executeAssetRegister( nextProjectPath: string, localPath: string, @@ -9823,7 +3173,6 @@ export function App({ ) { const invoke = resolveTauriInvoke(); if (!invoke) { - setAssetStatus('需要在 Tauri App 内运行'); if (announceToChat) { setMessages((current) => [ ...current, @@ -9833,7 +3182,6 @@ export function App({ return; } - setAssetStatus('正在登记'); try { if ( announceToChat && @@ -9877,12 +3225,6 @@ export function App({ model: '', }, ); - setUploadedAssets((current) => { - const filtered = current.filter((asset) => asset.id !== result.id); - return [...filtered, result]; - }); - setAssetStatus(`已登记:${result.localPath}`); - setCommandLog((current) => [...current, 'asset.register']); void refreshManifest(nextProjectPath); if (announceToChat) { setMessages((current) => [ @@ -9892,7 +3234,6 @@ export function App({ } } catch (error) { const message = error instanceof Error ? error.message : String(error); - setAssetStatus(message); if (announceToChat) { setMessages((current) => [ ...current, @@ -9902,53 +3243,6 @@ export function App({ } } - async function handleCanvasAssetImport() { - const nextProjectPath = projectPath.trim(); - const localPath = assetLocalPath.trim(); - const canvasProjectId = assetCanvasProjectId.trim(); - const resourceId = assetResourceId.trim(); - const nextAssetObjectId = assetObjectId.trim(); - if (!nextProjectPath || !isAbsoluteProjectPath(nextProjectPath)) { - setAssetStatus('请提供本地项目绝对路径。'); - return; - } - if (projectPathHasControlCharacter(nextProjectPath)) { - setAssetStatus('本地项目路径不能包含控制字符。'); - return; - } - if (!isSafeProjectRelativePath(localPath)) { - setAssetStatus('画板资产路径必须是项目内相对路径。'); - return; - } - if (!canvasProjectId) { - setAssetStatus('请提供画板项目 ID。'); - return; - } - if (!isSafeCanvasProjectId(canvasProjectId)) { - setAssetStatus('画板项目 ID 不能包含控制字符。'); - return; - } - if (!resourceId && !nextAssetObjectId) { - setAssetStatus('请提供资源 ID 或资产对象 ID。'); - return; - } - requestCommandConfirmation( - 'canvas.asset_import', - `导入 ${nextProjectPath}/${localPath}`, - () => - void executeCanvasAssetImport( - nextProjectPath, - localPath, - assetKind, - assetMediaType, - canvasProjectId, - resourceId, - nextAssetObjectId, - false, - ), - ); - } - async function executeCanvasAssetImport( nextProjectPath: string | null, localPath: string, @@ -9962,7 +3256,6 @@ export function App({ ) { const invoke = resolveTauriInvoke(); if (!invoke) { - setAssetStatus('需要在 Tauri App 内运行'); if (announceToChat) { setMessages((current) => [ ...current, @@ -10001,10 +3294,8 @@ export function App({ ), )) ) { - setAssetStatus('等待确认导入画板资产'); return; } - setAssetStatus('正在导入画板资产'); const result = await invoke( 'import_canvas_asset', { @@ -10020,17 +3311,11 @@ export function App({ model: '', }, ); - setUploadedAssets((current) => { - const filtered = current.filter((asset) => asset.id !== result.id); - return [...filtered, result]; - }); const canvasSource = formatCanvasAssetSource({ canvasProjectId, canvasAssetId: resourceId, canvasAssetObjectId: assetObjectId, }); - setAssetStatus(`已导入画板资产 ${canvasSource}:${result.localPath}`); - setCommandLog((current) => [...current, 'canvas.asset_import']); void refreshManifest(targetProjectPath); if (announceToChat) { setMessages((current) => [ @@ -10043,7 +3328,6 @@ export function App({ } } catch (error) { const message = error instanceof Error ? error.message : String(error); - setAssetStatus(message); if (announceToChat) { setMessages((current) => [ ...current, @@ -10053,28 +3337,6 @@ export function App({ } } - function handleCanvasAssetGenerate() { - const nextProjectPath = projectPath.trim(); - const prompt = assetGenerationPrompt.trim(); - if (!nextProjectPath || !isAbsoluteProjectPath(nextProjectPath)) { - setAssetStatus('请提供本地项目绝对路径。'); - return; - } - if (projectPathHasControlCharacter(nextProjectPath)) { - setAssetStatus('本地项目路径不能包含控制字符。'); - return; - } - if (!prompt) { - setAssetStatus('请提供美术生成提示词。'); - return; - } - requestCommandConfirmation( - 'canvas.asset_generate', - `生成 ${nextProjectPath} 的首版美术素材`, - () => void executeCanvasAssetGenerate(prompt, false), - ); - } - async function executeCanvasAssetGenerate( prompt: string, announceToChat: boolean, @@ -10082,7 +3344,6 @@ export function App({ ) { const invoke = resolveTauriInvoke(); if (!invoke) { - setAssetStatus('需要在 Tauri App 内运行'); if (announceToChat) { setMessages((current) => [ ...current, @@ -10110,10 +3371,8 @@ export function App({ () => void executeCanvasAssetGenerate(prompt, announceToChat, true), )) ) { - setAssetStatus('等待确认生成美术素材'); return; } - setAssetStatus('正在生成美术素材'); if (announceToChat) { setMessages((current) => [ ...current, @@ -10127,12 +3386,6 @@ export function App({ prompt, }, ); - setUploadedAssets((current) => { - const filtered = current.filter((asset) => asset.id !== result.id); - return [...filtered, result]; - }); - setAssetStatus(`已生成美术素材:${result.localPath}`); - setCommandLog((current) => [...current, 'canvas.asset_generate']); void refreshManifest(nextProjectPath); if (announceToChat) { setMessages((current) => [ @@ -10142,7 +3395,6 @@ export function App({ } } catch (error) { const message = error instanceof Error ? error.message : String(error); - setAssetStatus(message); if (isRuntimeConfigMissingError(message)) { requestRuntimeConfigOpen(); } @@ -10155,32 +3407,6 @@ export function App({ } } - function handleCanvasProjectSync() { - const nextProjectPath = projectPath.trim(); - const canvasProjectId = assetCanvasProjectId.trim(); - if (!nextProjectPath || !isAbsoluteProjectPath(nextProjectPath)) { - setAssetStatus('请提供本地项目绝对路径。'); - return; - } - if (projectPathHasControlCharacter(nextProjectPath)) { - setAssetStatus('本地项目路径不能包含控制字符。'); - return; - } - if (!canvasProjectId) { - setAssetStatus('请提供画板项目 ID。'); - return; - } - if (!isSafeCanvasProjectId(canvasProjectId)) { - setAssetStatus('画板项目 ID 不能包含控制字符。'); - return; - } - requestCommandConfirmation( - 'canvas.project_sync', - `同步画板项目资源:${canvasProjectId}`, - () => void executeCanvasProjectSync(canvasProjectId, false), - ); - } - async function executeCanvasProjectSync( canvasProjectId: string, announceToChat: boolean, @@ -10188,7 +3414,6 @@ export function App({ ) { const invoke = resolveTauriInvoke(); if (!invoke) { - setAssetStatus('需要在 Tauri App 内运行'); if (announceToChat) { setMessages((current) => [ ...current, @@ -10221,10 +3446,8 @@ export function App({ ), )) ) { - setAssetStatus('等待确认同步画板项目'); return; } - setAssetStatus('正在同步画板项目资源'); const result = await invoke( 'sync_canvas_project_assets', { @@ -10232,17 +3455,6 @@ export function App({ canvasProjectId, }, ); - setUploadedAssets((current) => { - const byId = new Map(current.map((asset) => [asset.id, asset])); - for (const asset of result.assets) { - byId.set(asset.id, asset); - } - return [...byId.values()]; - }); - setAssetStatus( - `已同步画板项目:${result.importedCount} 个资产,${result.importRoot}`, - ); - setCommandLog((current) => [...current, 'canvas.project_sync']); void refreshManifest(nextProjectPath); if (announceToChat) { setMessages((current) => [ @@ -10255,7 +3467,6 @@ export function App({ } } catch (error) { const message = error instanceof Error ? error.message : String(error); - setAssetStatus(message); if (announceToChat) { setMessages((current) => [ ...current, @@ -10265,40 +3476,6 @@ export function App({ } } - function handleCanvasExportImport() { - const nextProjectPath = projectPath.trim(); - const exportPath = canvasExportPath.trim(); - const canvasProjectId = assetCanvasProjectId.trim(); - if (!nextProjectPath || !isAbsoluteProjectPath(nextProjectPath)) { - setAssetStatus('请提供本地项目绝对路径。'); - return; - } - if (projectPathHasControlCharacter(nextProjectPath)) { - setAssetStatus('本地项目路径不能包含控制字符。'); - return; - } - if (!canvasProjectId) { - setAssetStatus('请提供画板项目 ID。'); - return; - } - if (!isSafeCanvasProjectId(canvasProjectId)) { - setAssetStatus('画板项目 ID 不能包含控制字符。'); - return; - } - if ( - !isAbsoluteProjectPath(exportPath) || - projectPathHasControlCharacter(exportPath) - ) { - setAssetStatus('画板导出 ZIP 路径必须是绝对路径。'); - return; - } - requestCommandConfirmation( - 'canvas.export_import', - `导入画板导出包:${exportPath}`, - () => void executeCanvasExportImport(exportPath, canvasProjectId, false), - ); - } - async function executeCanvasExportImport( exportPath: string, canvasProjectId: string, @@ -10307,7 +3484,6 @@ export function App({ ) { const invoke = resolveTauriInvoke(); if (!invoke) { - setAssetStatus('需要在 Tauri App 内运行'); if (announceToChat) { setMessages((current) => [ ...current, @@ -10341,10 +3517,8 @@ export function App({ ), )) ) { - setAssetStatus('等待确认导入画板导出包'); return; } - setAssetStatus('正在导入画板导出包'); const result = await invoke( 'import_canvas_export', { @@ -10353,17 +3527,6 @@ export function App({ canvasProjectId, }, ); - setUploadedAssets((current) => { - const byId = new Map(current.map((asset) => [asset.id, asset])); - for (const asset of result.assets) { - byId.set(asset.id, asset); - } - return [...byId.values()]; - }); - setAssetStatus( - `已导入画板导出包:${result.importedCount} 个资产,${result.importRoot}`, - ); - setCommandLog((current) => [...current, 'canvas.export_import']); void refreshManifest(nextProjectPath); if (announceToChat) { setMessages((current) => [ @@ -10376,7 +3539,6 @@ export function App({ } } catch (error) { const message = error instanceof Error ? error.message : String(error); - setAssetStatus(message); if (announceToChat) { setMessages((current) => [ ...current, @@ -10386,30 +3548,12 @@ export function App({ } } - async function handleCanvasProjectOpen() { - const canvasProjectId = assetCanvasProjectId.trim(); - if (!canvasProjectId) { - setAssetStatus('请提供画板项目 ID。'); - return; - } - if (!isSafeCanvasProjectId(canvasProjectId)) { - setAssetStatus('画板项目 ID 不能包含控制字符。'); - return; - } - requestCommandConfirmation( - 'canvas.project_open', - `打开本机画板项目 ${canvasProjectId}`, - () => void executeCanvasProjectOpen(canvasProjectId, false), - ); - } - async function executeCanvasProjectOpen( canvasProjectId: string, announceToChat: boolean, ) { const invoke = resolveTauriInvoke(); if (!invoke) { - setAssetStatus('需要在 Tauri App 内运行'); if (announceToChat) { setMessages((current) => [ ...current, @@ -10419,7 +3563,6 @@ export function App({ return; } - setAssetStatus('正在打开画板'); if (announceToChat) { setMessages((current) => [ ...current, @@ -10431,11 +3574,8 @@ export function App({ 'open_canvas_project', { canvasProjectId, - editorBaseUrl, }, ); - setAssetStatus(`已打开画板:${result.url}`); - setCommandLog((current) => [...current, 'canvas.project_open']); if (announceToChat) { setMessages((current) => [ ...current, @@ -10449,7 +3589,6 @@ export function App({ } } catch (error) { const message = error instanceof Error ? error.message : String(error); - setAssetStatus(message); if (announceToChat) { setMessages((current) => [ ...current, @@ -10459,28 +3598,9 @@ export function App({ } } - async function handleAssetUpload(event: ChangeEvent) { - const file = event.currentTarget.files?.[0]; - event.currentTarget.value = ''; - if (!file) { - return; - } - if (!requireChatProjectForUserAction()) { - return; - } - - queuePendingCommand({ id: 'asset.upload', file }); - setMessages((current) => [ - ...current, - { role: 'user', text: `上传文件:${file.name}` }, - { role: 'assistant', text: `准备保存文件到本地项目:${file.name}` }, - ]); - } - async function executeAssetUpload(file: File) { const invoke = resolveTauriInvoke(); if (!invoke) { - setAssetStatus('需要在 Tauri App 内运行'); setMessages((current) => [ ...current, { role: 'assistant', text: '需要在 Tauri App 内运行。' }, @@ -10492,7 +3612,6 @@ export function App({ return; } - setAssetStatus('正在上传'); try { const bytes = Array.from(new Uint8Array(await file.arrayBuffer())); const result = await invoke( @@ -10509,20 +3628,12 @@ export function App({ manifestPath: result.manifestPath, manifest, }); - setUploadedAssets((current) => [...current, result]); - setAssetStatus(`已上传:${result.localPath}`); - setCommandLog((current) => [ - ...current, - 'asset.upload', - 'asset.register', - ]); void refreshManifest(nextProjectPath); setMessages((current) => [ ...current, { role: 'assistant', text: `已保存文件:${result.localPath}` }, ]); } catch (error) { - setAssetStatus(error instanceof Error ? error.message : String(error)); setMessages((current) => [ ...current, { @@ -10539,11 +3650,9 @@ export function App({ ) { const invoke = resolveTauriInvoke(); if (!invoke) { - setAgentRunStatus('需要在 Tauri App 内运行'); return null; } if (!nextProjectPath) { - setAgentRunStatus('请先初始化本地项目'); return null; } @@ -10559,142 +3668,9 @@ export function App({ }, ); const trace = parseAgentRunTrace(result.content); - setAgentRunTrace(trace); - setAgentRunStatus(formatAgentRunStatus(trace)); - setCommandLog((current) => [...current, `file.read ${relativePath}`]); return summarizeAgentRunCompletionForChat(trace); - } catch (error) { - setAgentRunTrace(null); - setAgentRunStatus( - relativePath === '.agent/run.latest.json' && - isMissingProjectFileError(error) - ? '还没有最近一次 Agent run' - : error instanceof Error - ? error.message - : String(error), - ); - return null; - } - } - - async function handleAgentRunTraceFileOpen( - relativePath: string, - skipPolicyConfirm = false, - ) { - const invoke = resolveTauriInvoke(); - if (!invoke) { - setAgentRunStatus('需要在 Tauri App 内运行'); - return; - } - const nextProjectPath = resolveChatProjectPath(localProject) ?? ''; - if (!nextProjectPath) { - setAgentRunStatus('请先初始化本地项目'); - return; - } - - if (!skipPolicyConfirm) { - const policyView = await invoke( - 'read_project_permission_policy', - { projectPath: nextProjectPath }, - ); - if (policyView.policy.confirmCommands.includes('agent.trace_read')) { - requestProjectPolicyConfirmation( - 'agent.trace_read', - nextProjectPath, - `读取 ${nextProjectPath} 的 ${relativePath}`, - () => void handleAgentRunTraceFileOpen(relativePath, true), - ); - setAgentRunStatus('等待确认读取 Agent trace'); - return; - } - } - - await loadAgentRunTraceFile(relativePath, nextProjectPath); - } - - async function readAgentRunHistoryItems( - invoke: TauriInvoke, - runFiles: LocalProjectFileEntry[], - nextProjectPath: string, - ) { - const history = await Promise.all( - runFiles.map(async (file) => { - try { - const traceResult = await invoke( - 'read_local_project_file', - { - projectPath: nextProjectPath, - relativePath: file.path, - commandId: 'agent.trace_read', - }, - ); - return { - path: file.path, - size: file.size, - trace: parseAgentRunTrace(traceResult.content), - } satisfies AgentRunHistoryItem; - } catch { - return null; - } - }), - ); - return history - .filter((item): item is AgentRunHistoryItem => item !== null) - .sort((left, right) => right.trace.updatedAt - left.trace.updatedAt); - } - - async function refreshAgentRunHistory( - nextProjectPath = resolveChatProjectPath(localProject) ?? '', - ) { - const invoke = resolveTauriInvoke(); - if (!invoke || !nextProjectPath) { - setAgentRunHistory([]); - setAgentRunHistoryFiles([]); - setAgentRunHistoryOverflowCount(0); - setAgentRunHistoryVisibleCount(AGENT_RUN_HISTORY_INITIAL_VISIBLE_COUNT); - setAgentRunHistoryLoadingMore(false); - agentRunHistoryLoadingMoreRef.current = false; - return; - } - - try { - const result = await invoke( - 'list_local_project_files', - { projectPath: nextProjectPath }, - ); - const runFiles = result.files - .filter( - (file) => - file.kind === 'file' && - file.path.startsWith('.agent/runs/') && - file.path.endsWith('.json'), - ) - .sort( - (left, right) => - (right.modifiedAt ?? 0) - (left.modifiedAt ?? 0) || - right.path.localeCompare(left.path), - ); - const runFilesToLoad = runFiles.slice(0, AGENT_RUN_HISTORY_MAX_COUNT); - const firstPage = await readAgentRunHistoryItems( - invoke, - runFilesToLoad.slice(0, AGENT_RUN_HISTORY_INITIAL_VISIBLE_COUNT), - nextProjectPath, - ); - setAgentRunHistory(firstPage); - setAgentRunHistoryFiles(runFilesToLoad); - setAgentRunHistoryOverflowCount( - Math.max(0, runFiles.length - runFilesToLoad.length), - ); - setAgentRunHistoryVisibleCount(AGENT_RUN_HISTORY_INITIAL_VISIBLE_COUNT); - setAgentRunHistoryLoadingMore(false); - agentRunHistoryLoadingMoreRef.current = false; } catch { - setAgentRunHistory([]); - setAgentRunHistoryFiles([]); - setAgentRunHistoryOverflowCount(0); - setAgentRunHistoryVisibleCount(AGENT_RUN_HISTORY_INITIAL_VISIBLE_COUNT); - setAgentRunHistoryLoadingMore(false); - agentRunHistoryLoadingMoreRef.current = false; + return null; } } @@ -10772,30 +3748,20 @@ export function App({ agentRuntimeStateFromResult(runtimeResult), ); } - setAgentRunStatus('已确认恢复 Agent Runtime 任务'); }) - .catch((resumeError) => { + .catch(() => { if (localProjectPathRef.current !== nextProjectPath) { return; } agentRuntimeResumeProjectPathRef.current = null; - setAgentRunStatus( - `Agent Runtime 恢复失败:${ - resumeError instanceof Error - ? resumeError.message - : String(resumeError) - }`, - ); }); }, ); - setAgentRunStatus('等待确认恢复 Agent Runtime 任务'); } else if (message.includes('项目权限策略拒绝执行:agent.resume')) { agentRuntimeResumeProjectPathRef.current = nextProjectPath; markProjectPolicyDenied('agent.resume', message); } else { agentRuntimeResumeProjectPathRef.current = null; - setAgentRunStatus(`Agent Runtime 恢复失败:${message}`); } } } @@ -10810,32 +3776,6 @@ export function App({ for (const runtimeResult of runtimes) { nextRuntimes.push(agentRuntimeStateFromResult(runtimeResult)); } - const supervisorRuntimeIndex = nextRuntimes.findIndex( - (runtime) => runtime.agentId === PROJECT_SUPERVISOR_AGENT_ID, - ); - const persistedSupervisorRuntime = - supervisorRuntimeIndex >= 0 - ? nextRuntimes[supervisorRuntimeIndex]! - : null; - if (projectSupervisorOnly && persistedSupervisorRuntime) { - const currentRuntime = projectSupervisorRuntimeRef.current; - if ( - !currentRuntime || - persistedSupervisorRuntime.updatedAt >= currentRuntime.updatedAt - ) { - if (persistedSupervisorRuntime.sessionId) { - projectSupervisorSessionIdRef.current = - persistedSupervisorRuntime.sessionId; - setProjectSupervisorSessionId(persistedSupervisorRuntime.sessionId); - } - updateProjectSupervisorRuntime(persistedSupervisorRuntime); - updateProjectSupervisorResponseStream( - runtimes[supervisorRuntimeIndex]?.responseStream, - persistedSupervisorRuntime, - ); - setProjectSupervisorRuntimeError(''); - } - } setAgentRuntimeById((current) => nextRuntimes.reduce( (next, runtime) => mergeAgentRuntimeStateIntoMap(next, runtime, true), @@ -10853,9 +3793,7 @@ export function App({ nextProjectPath = resolveChatProjectPath(localProject) ?? '', ) { if (!nextProjectPath) { - setAgentRunStatus('请先初始化本地项目'); await refreshAgentRuntimes(nextProjectPath); - await refreshAgentRunHistory(nextProjectPath); return null; } const summary = await loadAgentRunTraceFile( @@ -10863,108 +3801,9 @@ export function App({ nextProjectPath, ); await refreshAgentRuntimes(nextProjectPath); - await refreshAgentRunHistory(nextProjectPath); return summary; } - async function handleAgentRunTraceRefresh(skipPolicyConfirm = false) { - const invoke = resolveTauriInvoke(); - if (!invoke) { - setAgentRunStatus('需要在 Tauri App 内运行'); - return; - } - const nextProjectPath = resolveChatProjectPath(localProject); - if (!nextProjectPath) { - setAgentRunStatus('请先初始化本地项目'); - return; - } - try { - if (!skipPolicyConfirm) { - const policyView = await invoke( - 'read_project_permission_policy', - { projectPath: nextProjectPath }, - ); - if (policyView.policy.confirmCommands.includes('agent.trace_read')) { - requestProjectPolicyConfirmation( - 'agent.trace_read', - nextProjectPath, - `读取 ${nextProjectPath} 的最近 Agent run trace`, - () => void handleAgentRunTraceRefresh(true), - ); - setAgentRunStatus('等待确认'); - return; - } - } - await refreshAgentRunTrace(nextProjectPath); - } catch (error) { - setAgentRunStatus(error instanceof Error ? error.message : String(error)); - } - } - - async function scheduleReadyAgentTasks(skipPolicyConfirm = false) { - const invoke = resolveTauriInvoke(); - if (!invoke) { - setAgentRunStatus('需要在 Tauri App 内运行'); - return; - } - const nextProjectPath = resolveChatProjectPath(localProject); - if (!nextProjectPath) { - setAgentRunStatus('请先初始化本地项目'); - return; - } - try { - if (!skipPolicyConfirm) { - const policyView = await invoke( - 'read_project_permission_policy', - { projectPath: nextProjectPath }, - ); - if ( - policyView.policy.confirmCommands.includes('agent.schedule_ready') - ) { - requestProjectPolicyConfirmation( - 'agent.schedule_ready', - nextProjectPath, - `调度 ${nextProjectPath} 中已 Ready 的 Agent 任务`, - () => void scheduleReadyAgentTasks(true), - ); - setAgentRunStatus('等待确认调度 Ready 任务'); - return; - } - } - setAgentRunStatus('正在调度 Ready 任务'); - const scheduledRuntimes = await invoke( - 'schedule_game_creator_agent_ready_tasks', - { projectPath: nextProjectPath, limit: 16 }, - ); - for (const runtimeResult of scheduledRuntimes) { - rememberAgentRuntimeState(agentRuntimeStateFromResult(runtimeResult)); - } - await refreshManifest(nextProjectPath); - await refreshAgentRuntimes(nextProjectPath); - const message = - scheduledRuntimes.length > 0 - ? `已调度 ${scheduledRuntimes.length} 个 Ready 任务。` - : '没有可调度的 Ready 任务。'; - setAgentRunStatus(message); - setMessages((current) => [ - ...current, - { role: 'assistant', text: message }, - ]); - } catch (error) { - const message = error instanceof Error ? error.message : String(error); - setAgentRunStatus(message); - setMessages((current) => [ - ...current, - { role: 'assistant', text: message }, - ]); - } - } - - const agentStatusCards = deriveAgentStatusCards( - manifest, - agentRunTrace ?? agentRunHistory[0]?.trace ?? null, - agentRuntimeById, - ); const professionalResultCandidates = taskRowsFromManifest(manifest).map( (task) => ({ agentId: agentConversationId(task), @@ -10978,7 +3817,7 @@ export function App({ .join('|'); useEffect(() => { const nextProjectPath = localProject?.projectPath; - if (!projectSupervisorOnly || !nextProjectPath || !onManifestChange) { + if (!nextProjectPath || !onManifestChange) { return; } const invoke = resolveTauriInvoke(); @@ -11007,7 +3846,7 @@ export function App({ onManifestChange(nextProjectPath, currentManifest, { projectId: currentManifest.projectId, revision: after.revision, - source: 'supervisor', + source: 'chat', }); } return; @@ -11016,17 +3855,11 @@ export function App({ return () => { cancelled = true; }; - }, [ - localProject?.projectPath, - manifest, - onManifestChange, - projectSupervisorOnly, - ]); + }, [localProject?.projectPath, manifest, onManifestChange]); useEffect(() => { const invoke = resolveTauriInvoke(); const nextProjectPath = localProject?.projectPath ?? null; if ( - !projectSupervisorOnly || directProjectMode || !invoke || !nextProjectPath || @@ -11112,52 +3945,27 @@ export function App({ directProjectMode, localProject?.projectPath, professionalResultCandidateKey, - projectSupervisorOnly, - projectSupervisorRuntime?.runId, + planningRuntime?.runId, ]); useEffect(() => { - if (!projectSupervisorOnly || !onAgentRuntimeSummariesChange) { + if (!onAgentRuntimeSummariesChange) { return; } onAgentRuntimeSummariesChange( - projectAgentRuntimeSummaries( - manifest, - projectSupervisorRuntime, - agentRuntimeById, - ), + projectAgentRuntimeSummaries(manifest, planningRuntime, agentRuntimeById), ); }, [ agentRuntimeById, manifest, onAgentRuntimeSummariesChange, - projectSupervisorOnly, - projectSupervisorRuntime, + planningRuntime, ]); useEffect(() => { - if (!projectSupervisorOnly || !onAgentResultsChange) { + if (!onAgentResultsChange) { return; } onAgentResultsChange(Object.values(professionalAgentResultsById)); - }, [ - onAgentResultsChange, - professionalAgentResultsById, - projectSupervisorOnly, - ]); - const mainProjectSummary = localProject - ? summarizeMainProjectHeader(manifest, agentStatusCards) - : null; - const visibleAgentRunHistory = agentRunHistory.slice( - 0, - agentRunHistoryVisibleCount, - ); - const visibleMainProjectFiles = projectFiles - .filter((file) => file.kind === 'file' && !file.path.startsWith('.agent/')) - .slice(0, 8); - const visibleMainProjectAssets = manifest.assets - .filter( - (asset) => asset.localPath && !asset.localPath.startsWith('.agent/'), - ) - .slice(0, 8); + }, [onAgentResultsChange, professionalAgentResultsById]); const chatProjectAssets = manifest.assets.filter( (asset) => asset.localPath && !asset.localPath.startsWith('.agent/'), ); @@ -11166,70 +3974,6 @@ export function App({ // 传 `null` 表示回退到 manifest 中最新的版本。 const chatProjectVersions = manifest.versions ?? []; const chatActiveVersionId = activeVersionId; - const visibleMainProjectCheckpoints = projectCheckpoints.slice(0, 5); - const currentProjectTitle = localProject - ? manifest.name.trim() || projectNameFromPath(localProject.projectPath) - : seedManifest.name; - useEffect(() => { - if (!supervisorChatOnly) { - return; - } - setWindowTitle(localProject ? currentProjectTitle : undefined); - return () => setWindowTitle(undefined); - }, [currentProjectTitle, localProject, setWindowTitle, supervisorChatOnly]); - const agentRunHistoryOmittedCount = - Math.max(0, agentRunHistoryFiles.length - agentRunHistoryVisibleCount) + - agentRunHistoryOverflowCount; - const canShowMoreAgentRunHistory = - agentRunHistoryVisibleCount < agentRunHistoryFiles.length; - - async function showMoreAgentRunHistory() { - const invoke = resolveTauriInvoke(); - const nextProjectPath = resolveChatProjectPath(localProject); - if (!invoke || agentRunHistoryLoadingMoreRef.current) { - return; - } - if (!nextProjectPath) { - setAgentRunStatus('请先初始化本地项目'); - return; - } - const nextVisibleCount = Math.min( - agentRunHistoryVisibleCount + AGENT_RUN_HISTORY_VISIBLE_STEP, - agentRunHistoryFiles.length, - ); - const nextFiles = agentRunHistoryFiles.slice( - agentRunHistoryVisibleCount, - nextVisibleCount, - ); - agentRunHistoryLoadingMoreRef.current = true; - setAgentRunHistoryLoadingMore(true); - try { - const nextItems = await readAgentRunHistoryItems( - invoke, - nextFiles, - nextProjectPath, - ); - setAgentRunHistory((current) => - [...current, ...nextItems].sort( - (left, right) => right.trace.updatedAt - left.trace.updatedAt, - ), - ); - setAgentRunHistoryVisibleCount(nextVisibleCount); - } finally { - agentRunHistoryLoadingMoreRef.current = false; - setAgentRunHistoryLoadingMore(false); - } - } - - function handleAgentRunHistoryScroll(event: UIEvent) { - if (!canShowMoreAgentRunHistory) { - return; - } - const target = event.currentTarget; - if (target.scrollHeight - target.scrollTop - target.clientHeight <= 24) { - void showMoreAgentRunHistory(); - } - } const visibleMessages = latestVisibleItems( messages, @@ -11240,40 +3984,7 @@ export function App({ messages.length - visibleMessages.length, ); const hasEarlierConversationMessages = hiddenConversationCount > 0; - const projectSupervisorTransientReply = - projectSupervisorResponseStream?.accumulatedText.trim() ?? ''; - const projectSupervisorNeedsUserInput = agentRuntimeNeedsUserInput( - projectSupervisorRuntime, - ); - const projectSupervisorHasConversationControls = Boolean( - projectSupervisorRuntime?.pendingToolAction || - projectSupervisorRuntime?.userInputRequest || - projectSupervisorNeedsUserInput, - ); - const visibleAgentConversationMessages = latestVisibleItems( - agentConversationMessages, - agentConversationVisibleCount, - ); - const hiddenAgentConversationCount = Math.max( - 0, - agentConversationMessages.length - visibleAgentConversationMessages.length, - ); - const selectedAgentLlmStatus = selectedAgent - ? formatAgentDialogLlmStatus(llmConfigStatus, selectedAgent) - : null; - const selectedAgentLlmWarning = selectedAgent - ? formatAgentLlmConfigWarning(llmConfigStatus, selectedAgent) - : null; - const selectedAgentNeedsUserInput = agentRuntimeNeedsUserInput( - agentConversationRuntime, - ); - const selectedAgentSteerRuntime = selectedAgent - ? matchingAgentRuntimeForSteer( - [agentConversationRuntime], - selectedAgent.id, - agentConversationSessionId, - ) - : null; + const planningNeedsUserInput = agentRuntimeNeedsUserInput(planningRuntime); async function showEarlierConversationMessages() { setConversationVisibleCount((current) => @@ -11281,15 +3992,6 @@ export function App({ ); } - function showEarlierAgentConversationMessages() { - setAgentConversationVisibleCount((current) => - Math.min( - agentConversationMessages.length, - current + CONVERSATION_VISIBLE_STEP, - ), - ); - } - function handleConversationScroll(event: UIEvent) { if (!hasEarlierConversationMessages) { return; @@ -11299,45 +4001,18 @@ export function App({ } } - function handleSupervisorChatScroll(event: UIEvent) { + function handlePlanningChatScroll(event: UIEvent) { handleConversationScroll(event); const messageList = event.currentTarget; const distanceFromBottom = messageList.scrollHeight - messageList.scrollTop - messageList.clientHeight; - supervisorChatShouldFollowLatestRef.current = + planningChatShouldFollowLatestRef.current = distanceFromBottom <= AGENT_CHAT_SCROLL_BOTTOM_THRESHOLD; } - function handleAgentConversationScroll(event: UIEvent) { - if (hiddenAgentConversationCount === 0) { - return; - } - if (event.currentTarget.scrollTop <= 24) { - showEarlierAgentConversationMessages(); - } - } - - useEffect(() => { - if (!selectedAgent) { - return; - } - const refreshedAgent = agentStatusCards.find( - (agent) => agent.id === selectedAgent.id, - ); - if (!refreshedAgent) { - closeAgentConversation(); - return; - } - if (!sameAgentStatusCard(selectedAgent, refreshedAgent)) { - setSelectedAgent(refreshedAgent); - } - }, [agentStatusCards, selectedAgent]); - - function handleProjectSupervisorOnlySubmit( - event: FormEvent, - ) { + function handlePlanningChatSubmit(event: FormEvent) { event.preventDefault(); const prompt = chatInput.trim(); const content = chatContent; @@ -11345,12 +4020,8 @@ export function App({ content, manifest.assets, ); - if (supervisorChatOnly && prompt.startsWith('/')) { - void handleChatSubmit(event); - return; - } - if (agentRuntimeNeedsUserInput(projectSupervisorRuntimeRef.current)) { - setProjectSupervisorRuntimeError('请先回答项目总控 Agent 当前的澄清问题'); + if (agentRuntimeNeedsUserInput(planningRuntimeRef.current)) { + setProjectChatError('请先回答项目总控 Agent 当前的澄清问题'); return; } if (!hasMeaningfulDirectCodexContent(content)) { @@ -11363,7 +4034,7 @@ export function App({ if (!canonicalPrompt || chatAgentBusy) { return; } - supervisorChatShouldFollowLatestRef.current = true; + planningChatShouldFollowLatestRef.current = true; const clientTurnId = createAgentChatRunId('planning-v2-turn'); setChatInput(''); setMessages((current) => [ @@ -11394,61 +4065,9 @@ export function App({ void executeChatAgentReply({ prompt }); } - const visibleProfessionalAgentCards = agentStatusCards.filter( - (agent) => - agent.id !== PROJECT_SUPERVISOR_AGENT_ID && - (agent.runtimeStatus !== null || agent.hasRecentEvidence), - ); const useDesignAgentSurface = Boolean(designAgentView) || (planningStartMode && !planningV2Session); - if (projectSupervisorOnly && supervisorChatOnly) { - const supervisorProjectPath = - localProject?.projectPath || initialProjectPath || projectPath; - return ( - setRuntimeConfigOpen(false)} - onConfirmConfirmation={confirmUiCommand} - onOpenRuntimeConfig={() => setRuntimeConfigOpen(true)} - onScroll={handleSupervisorChatScroll} - onShowEarlierMessages={showEarlierConversationMessages} - hasEarlierConversationMessages={hasEarlierConversationMessages} - onSubmit={handleProjectSupervisorOnlySubmit} - onToolAction={handleProjectSupervisorToolAction} - onUserInput={handleProjectSupervisorUserInput} - pendingConfirmation={pendingUiConfirmation} - pendingCommand={pendingCommand} - onCancelPendingCommand={handlePendingCommandCancel} - onConfirmPendingCommand={() => void handlePendingCommandConfirm()} - projectPath={supervisorProjectPath} - runtime={projectSupervisorRuntime} - runtimeConfigOpen={runtimeConfigOpen} - runtimeError={projectSupervisorRuntimeError} - transientReply={ - planningV2Active - ? planningV2TransientReply - : projectSupervisorTransientReply - } - hasConversationControls={projectSupervisorHasConversationControls} - hiddenConversationCount={hiddenConversationCount} - needsUserInput={projectSupervisorNeedsUserInput} - visibleMessages={visibleMessages} - workspaceStatus={workspaceStatus} - expectedRunId={projectSupervisorExpectedRunId} - versions={chatProjectVersions} - /> - ); - } - if (directProjectMode) { // 普通项目固定走 DirectProject 自己的聊天容器:订阅、历史、发送、队列和附件都由 // 容器持有,工作台壳只提供项目身份、入口首轮需求和两条权限门。 @@ -11463,10 +4082,10 @@ export function App({ ); } - if (projectSupervisorOnly) { - return ( - + void handlePendingCommandConfirm()} - onScroll={handleSupervisorChatScroll} + onScroll={handlePlanningChatScroll} onShowEarlierMessages={showEarlierConversationMessages} - onSubmit={handleProjectSupervisorOnlySubmit} + onSubmit={handlePlanningChatSubmit} pendingConfirmation={pendingUiConfirmation} pendingCommand={pendingCommand} projectPath={localProject?.projectPath ?? projectPath} conversationMessages={messages} - transientReply={ - planningV2Active - ? planningV2TransientReply - : projectSupervisorTransientReply - } + transientReply={planningV2TransientReply} showDesignReasoning={planningV2Active} designReasoning={planningV2Reasoning} designReasoningEntries={ useDesignAgentSurface ? (designAgentView?.reasoningEntries ?? []) : [] } visibleMessages={visibleMessages} - visibleProfessionalAgentCards={visibleProfessionalAgentCards} - showProfessionalCollaboration={orchestrationMode === 'professional-dag'} workspaceStatus={workspaceStatus} planGddState={planGddState} planGddHydrateBusy={planGddHydrateBusy} @@ -11508,7 +4121,6 @@ export function App({ planGddError={planGddError} onPlanGddRefresh={() => void hydratePlanGddState()} onPlanGddDecision={decidePlanGdd} - planningLane={planningV2Active} designView={useDesignAgentSurface ? designAgentView : null} onDesignApprove={ useDesignAgentSurface @@ -11523,11 +4135,6 @@ export function App({ projectPath: nextProjectPath, clientTurnId, }; - designAgentReasoningTurnRef.current = { - projectPath: nextProjectPath, - clientTurnId, - text: '', - }; designAgentPendingViewRef.current = null; setPlanningV2TransientReplyTarget(''); setPlanningV2Reasoning(''); @@ -11622,262 +4229,18 @@ export function App({ ) : undefined } - runtime={projectSupervisorRuntime} - error={projectSupervisorRuntimeError} - runtimeByAgentId={agentRuntimeById} + runtime={planningRuntime} + error={projectChatError} controlBusy={chatAgentBusy} versions={chatProjectVersions} - professionalResultsByAgentId={professionalAgentResultsById} - onToolAction={handleProjectSupervisorToolAction} - onSupervisorRetry={handleProjectSupervisorRetry} - onProfessionalToolAction={handleProjectProfessionalAgentToolAction} - onProfessionalRetry={handleProjectProfessionalAgentRetry} - onUserInput={handleProjectSupervisorUserInput} + onUserInput={handlePlanningUserInput} /> - ); - } - - return ( -
- - {runtimeConfigOpen ? ( setRuntimeConfigOpen(false)} - onLog={(entry) => setCommandLog((current) => [...current, entry])} /> ) : null} - - {selectedAgent ? ( - - ) : null} - - {devMode ? ( -
- - -
- ) : null} -
+ ); } diff --git a/apps/ai-game-creator-shell/src/app/constants.ts b/apps/ai-game-creator-shell/src/app/constants.ts index 91bb2b6f1..7cae7093c 100644 --- a/apps/ai-game-creator-shell/src/app/constants.ts +++ b/apps/ai-game-creator-shell/src/app/constants.ts @@ -9,24 +9,11 @@ export function createLocalProjectId(): string { return `local-project-${crypto.randomUUID()}`; } -export const AGENT_RUN_HISTORY_MAX_COUNT = 100; -export const AGENT_RUN_HISTORY_INITIAL_VISIBLE_COUNT = 20; -export const AGENT_RUN_HISTORY_VISIBLE_STEP = 20; export const CONVERSATION_INITIAL_VISIBLE_COUNT = 20; export const CONVERSATION_VISIBLE_STEP = 20; /** 一次翻页操作最多连拉几页:见 ADR「分页锚点取原始条目 id」。 */ export const DIRECT_HISTORY_MAX_PAGES_PER_ACTION = 5; export const AGENT_CHAT_SCROLL_BOTTOM_THRESHOLD = 48; -export const PROJECT_SUPERVISOR_AGENT_ID = 'project-supervisor'; -/** - * 立项策划链路的 run `source`。 - * - * 这是全前端唯一的字面量出处:`AgentRuntimeState.source` 在类型上只是 `string`, - * 改名不会有任何编译期提示,所以判据必须收敛到这一个常量上。`as const` 让它同时 - * 能充当 `ProjectSupervisorRuntimeSubmission['source']` 的成员。 - */ -export const PROJECT_SUPERVISOR_PLAN_SOURCE = - 'project-supervisor-plan' as const; export const launcherNotifications: Array<{ label: string; detail: string; diff --git a/apps/ai-game-creator-shell/src/app/initialSupervisorMessageClaims.ts b/apps/ai-game-creator-shell/src/app/initialSupervisorMessageClaims.ts deleted file mode 100644 index bf4aa512e..000000000 --- a/apps/ai-game-creator-shell/src/app/initialSupervisorMessageClaims.ts +++ /dev/null @@ -1,25 +0,0 @@ -/** - * 入口首轮需求的认领记录。 - * - * 首页/立项链路把同一条首轮需求带进工作台,Supervisor、Design Agent、Planning V2 与 - * DirectProject 是同一页面上的不同入口;认领按页面保存并按「项目路径 + claimScope」 - * 去重,保证同一条需求只被一个入口发出。 - */ -const initialSupervisorMessageClaimsByPage = new WeakMap>(); - -export function claimInitialSupervisorMessageForPage( - projectPath: string, - scope = '', -) { - let claimedProjectPaths = initialSupervisorMessageClaimsByPage.get(window); - if (!claimedProjectPaths) { - claimedProjectPaths = new Set(); - initialSupervisorMessageClaimsByPage.set(window, claimedProjectPaths); - } - const claimKey = `${projectPath}\u0000${scope}`; - if (claimedProjectPaths.has(claimKey)) { - return false; - } - claimedProjectPaths.add(claimKey); - return true; -} diff --git a/apps/ai-game-creator-shell/src/app/initialTurnClaims.ts b/apps/ai-game-creator-shell/src/app/initialTurnClaims.ts new file mode 100644 index 000000000..b5467f2bf --- /dev/null +++ b/apps/ai-game-creator-shell/src/app/initialTurnClaims.ts @@ -0,0 +1,22 @@ +/** + * 入口首轮需求的认领记录。 + * + * 首页/立项链路把同一条首轮需求带进工作台,DirectProject 与立项策划是同一页面上的 + * 不同入口;认领按页面保存并按「项目路径 + claimScope」去重,保证同一条需求只被一个 + * 入口发出。 + */ +const initialTurnClaimsByPage = new WeakMap>(); + +export function claimInitialTurnForPage(projectPath: string, scope = '') { + let claimedProjectPaths = initialTurnClaimsByPage.get(window); + if (!claimedProjectPaths) { + claimedProjectPaths = new Set(); + initialTurnClaimsByPage.set(window, claimedProjectPaths); + } + const claimKey = `${projectPath}\u0000${scope}`; + if (claimedProjectPaths.has(claimKey)) { + return false; + } + claimedProjectPaths.add(claimKey); + return true; +} diff --git a/apps/ai-game-creator-shell/src/features/agent-runtime/index.ts b/apps/ai-game-creator-shell/src/features/agent-runtime/index.ts index 268e61dbf..9f8ccaddf 100644 --- a/apps/ai-game-creator-shell/src/features/agent-runtime/index.ts +++ b/apps/ai-game-creator-shell/src/features/agent-runtime/index.ts @@ -1,2 +1 @@ export * from './model'; -export * from './panels'; diff --git a/apps/ai-game-creator-shell/src/features/agent-runtime/model.ts b/apps/ai-game-creator-shell/src/features/agent-runtime/model.ts index fcaef751c..f5227523d 100644 --- a/apps/ai-game-creator-shell/src/features/agent-runtime/model.ts +++ b/apps/ai-game-creator-shell/src/features/agent-runtime/model.ts @@ -2,145 +2,21 @@ import type { GameCreationAppManifest, GameCreationAppTaskState, } from '../../../../../packages/shared/src/contracts/gameCreationApp'; -import { - PROJECT_SUPERVISOR_AGENT_ID, - PROJECT_SUPERVISOR_PLAN_SOURCE, - seedManifest, -} from '../../app/constants'; +import { seedManifest } from '../../app/constants'; import type { - AgentConversationSessionListResult, - AgentConversationSessionRecord, - AgentGoalRecord, AgentRuntimeEventRecord, - AgentRuntimePendingToolActionSummary, AgentRuntimePlanStep, - AgentRuntimeResponseStream, AgentRuntimeResult, AgentRuntimeState, - AgentRuntimeSteerResult, AgentRuntimeTaskQueueSummary, AgentRuntimeTaskRecord, ChatMessage, - LocalConversationMessageRecord, - TauriInvoke, } from '../../app/types'; -const AGENT_RUNTIME_PUBLIC_STATUS_MESSAGE_ID_PREFIX = 'runtime-public-status-'; -const AGENT_RUNTIME_TASK_MESSAGE_ID_PREFIX = 'runtime-task-'; -const AGENT_RUNTIME_STEER_MESSAGE_ID_PREFIX = 'agent-steer-'; -const AGENT_RUNTIME_MESSAGE_CORRELATION_PATTERN = /^[0-9a-f]{32}$/; - -export type ProjectSupervisorRuntimeSubmission = { - runProfile: 'standard' | 'autonomous-game-build'; - source: 'project-supervisor-gui' | typeof PROJECT_SUPERVISOR_PLAN_SOURCE; -}; - -export function resolveProjectSupervisorRuntimeSubmission({ - workspaceProjectKind, - orchestrationMode, - supervisorChatOnly, - planningEntry = false, -}: { - workspaceProjectKind: 'web' | 'godot' | 'cocos'; - orchestrationMode: 'single-supervisor' | 'professional-dag'; - supervisorChatOnly: boolean; - planningEntry?: boolean; -}): ProjectSupervisorRuntimeSubmission { - // 立项策划入口独立成链:命中时固定 standard + plan source,不参与下面按 - // godot / chat / DAG 的分流,也不改动做游戏与做素材的既有路由。 - if (planningEntry && workspaceProjectKind === 'web') { - return { - runProfile: 'standard', - source: PROJECT_SUPERVISOR_PLAN_SOURCE, - }; - } - if (workspaceProjectKind === 'godot' || supervisorChatOnly) { - return { - runProfile: 'standard', - source: 'project-supervisor-gui', - }; - } - if (orchestrationMode !== 'single-supervisor') { - return { - runProfile: 'autonomous-game-build', - source: 'project-supervisor-gui', - }; - } - return { - runProfile: 'standard', - source: 'project-supervisor-gui', - }; -} - -function agentRuntimeMessageCorrelationId( - messageId: string | null | undefined, -) { - const normalized = messageId?.trim() ?? ''; - const prefix = normalized.startsWith( - AGENT_RUNTIME_PUBLIC_STATUS_MESSAGE_ID_PREFIX, - ) - ? AGENT_RUNTIME_PUBLIC_STATUS_MESSAGE_ID_PREFIX - : normalized.startsWith(AGENT_RUNTIME_TASK_MESSAGE_ID_PREFIX) - ? AGENT_RUNTIME_TASK_MESSAGE_ID_PREFIX - : normalized.startsWith(AGENT_RUNTIME_STEER_MESSAGE_ID_PREFIX) - ? AGENT_RUNTIME_STEER_MESSAGE_ID_PREFIX - : null; - if (!prefix) { - return null; - } - const correlationId = normalized.slice(prefix.length).split('-', 1)[0] ?? ''; - return AGENT_RUNTIME_MESSAGE_CORRELATION_PATTERN.test(correlationId) - ? correlationId - : null; -} - -export function createLocalConversationDraftMessage( - content: string, - updatedAt = Date.now(), -): LocalConversationMessageRecord { - return { - schemaVersion: 'game-creator-conversation.v1', - role: 'assistant', - content, - agentId: null, - updatedAt, - }; -} export function createAgentChatRunId(prefix: string) { return `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`; } -export function parseAgentGoalItems(value: string) { - return value - .split(/\r?\n/) - .map((item) => item.trim()) - .filter(Boolean); -} - -export function formatAgentConversationSessionMeta( - session: AgentConversationSessionRecord, - sessions: AgentConversationSessionRecord[], - activeSessionId: string | null, -) { - const details = [String(session.messageCount)]; - if (session.sessionId === activeSessionId) { - details.push('活动'); - } - if (session.forkedFromSessionId) { - const source = sessions.find( - (candidate) => candidate.sessionId === session.forkedFromSessionId, - ); - const sourceLabel = source?.title ?? session.forkedFromSessionId; - details.push( - session.forkedMessageCount === null || - session.forkedMessageCount === undefined - ? `分支自 ${sourceLabel}` - : `分支自 ${sourceLabel}(${session.forkedMessageCount} 条)`, - ); - } - return details.join(' · '); -} - export function agentRuntimePlanStepsFromPlan( plan: string[], ): AgentRuntimePlanStep[] { @@ -403,30 +279,6 @@ export function normalizeAgentRuntimeState( }; } -export function mergeAgentGoalRecordFromRuntime( - goal: AgentGoalRecord | null, - runtime: AgentRuntimeState, -) { - if ( - !goal || - !runtime.goalId || - goal.goalId !== runtime.goalId || - goal.agentId !== runtime.agentId || - goal.sessionId !== runtime.sessionId - ) { - return goal; - } - return { - ...goal, - revision: runtime.goalRevision ?? goal.revision, - status: runtime.goalStatus ?? goal.status, - outcome: runtime.goalOutcome ?? goal.outcome, - constraints: runtime.goalConstraints ?? goal.constraints, - verification: runtime.goalVerification ?? goal.verification, - updatedAt: Math.max(goal.updatedAt, runtime.updatedAt), - }; -} - export function mergeAgentRuntimeStateIntoMap( current: Record, incoming: AgentRuntimeState, @@ -516,149 +368,11 @@ export function agentRuntimeStateFromResult( ); } -export function normalizeProjectSupervisorResponseStream( - stream: AgentRuntimeResponseStream | null | undefined, - runtime: AgentRuntimeState, -) { - if (!stream) { - return null; - } - const integerFields = [ - stream.appliedSteerCursor, - stream.responseRevision, - stream.sequence, - stream.startedAt, - stream.updatedAt, - ]; - if ( - stream.schemaVersion !== 'game-creator-runtime-response-stream.v1' || - stream.agentId !== PROJECT_SUPERVISOR_AGENT_ID || - stream.agentId !== runtime.agentId || - stream.taskId !== runtime.taskId || - stream.sessionId !== runtime.sessionId || - stream.runId !== runtime.runId || - stream.requestKind !== 'final-reply' || - !stream.requestSlot.trim() || - integerFields.some( - (value) => - typeof value !== 'number' || !Number.isSafeInteger(value) || value < 0, - ) || - stream.startedAt <= 0 || - stream.updatedAt < stream.startedAt || - typeof stream.accumulatedText !== 'string' || - Array.from(stream.accumulatedText).length > 32_000 || - /<\/?think>/i.test(stream.accumulatedText) - ) { - return null; - } - if ( - typeof runtime.appliedSteerCursor === 'number' && - stream.appliedSteerCursor !== runtime.appliedSteerCursor - ) { - return null; - } - if ((runtime.queuedSteerCount ?? 0) > 0) { - return null; - } - if ( - typeof runtime.loopIteration === 'number' && - Number.isSafeInteger(runtime.loopIteration) && - stream.requestSlot !== - `final-reply-loop-${runtime.loopIteration}-revision-${stream.responseRevision}` - ) { - return null; - } - if ( - (stream.status === 'streaming' || stream.status === 'ready') && - (runtime.status !== 'running' || - !['response', 'finalizing'].includes(runtime.phase)) - ) { - return null; - } - if (stream.status === 'ready' && !stream.accumulatedText.trim()) { - return null; - } - return stream; -} - -export function sameProjectSupervisorResponseStream( - left: AgentRuntimeResponseStream, - right: AgentRuntimeResponseStream, -) { - return ( - left.agentId === right.agentId && - left.taskId === right.taskId && - left.sessionId === right.sessionId && - left.runId === right.runId && - left.requestKind === right.requestKind && - left.requestSlot === right.requestSlot && - left.appliedSteerCursor === right.appliedSteerCursor && - left.responseRevision === right.responseRevision - ); -} - /** * Durable identity for one final-reply response. The response text is * intentionally excluded so two turns with identical wording remain * distinct messages in the conversation. */ -export function projectSupervisorResponseStreamIdentity( - stream: Pick< - AgentRuntimeResponseStream, - 'runId' | 'requestSlot' | 'responseRevision' - >, -) { - return [stream.runId, stream.requestSlot, stream.responseRevision].join( - '\u001f', - ); -} - -export function mergeProjectSupervisorResponseStream( - current: AgentRuntimeResponseStream | null, - incoming: AgentRuntimeResponseStream | null | undefined, - runtime: AgentRuntimeState, -) { - const currentForRuntime = normalizeProjectSupervisorResponseStream( - current, - runtime, - ); - if (!incoming) { - return null; - } - const next = normalizeProjectSupervisorResponseStream(incoming, runtime); - if (!next) { - return currentForRuntime; - } - if (next.status !== 'streaming' && next.status !== 'ready') { - return null; - } - if ( - !currentForRuntime || - !sameProjectSupervisorResponseStream(currentForRuntime, next) - ) { - return next; - } - if (next.sequence <= currentForRuntime.sequence) { - return currentForRuntime; - } - return next; -} - -export function conversationContainsProjectSupervisorResponseStream( - messages: LocalConversationMessageRecord[], - stream: AgentRuntimeResponseStream | null | undefined, -) { - return Boolean( - stream?.accumulatedText.trim() && - messages.some( - (message) => - message.role === 'assistant' && - message.content === stream.accumulatedText && - message.updatedAt >= stream.startedAt, - ), - ); -} - export function agentRuntimeWaitingOnFromPhase(phase: string) { switch (phase) { case 'planning': @@ -721,36 +435,6 @@ export function agentRuntimeNextStepFromPhase(phase: string) { } } -export function agentRuntimeStartStatus(result: AgentRuntimeResult) { - const pendingTask = (result.recentTasks ?? []).find( - (task) => task.status === 'pending', - ); - return pendingTask - ? `已加入后台队列:${pendingTask.runId}` - : `已启动后台任务:${result.state.runId}`; -} - -export function agentRuntimeStartedRunId( - result: AgentRuntimeResult, - requestedRunId: string, -) { - if (result.acceptedRunId?.trim()) { - return result.acceptedRunId.trim(); - } - if ( - result.recentTasks?.some((task) => task.runId === requestedRunId) || - result.state.runId === requestedRunId - ) { - return requestedRunId; - } - return ( - result.taskQueue?.latestRunId ?? - result.state.taskQueue?.latestRunId ?? - result.state.runId ?? - requestedRunId - ); -} - export function isAgentRuntimeTerminalState(runtime: AgentRuntimeState) { return ( ['completed', 'failed', 'cancelled', 'needs-reconciliation'].includes( @@ -808,225 +492,6 @@ export function agentRuntimeNeedsUserInput( ); } -export const AGENT_RUNTIME_USER_INPUT_REQUEST_TOOL = 'user.input_request'; -export const AGENT_RUNTIME_PLAN_SUBMIT_GDD_TOOL = 'plan.submit_gdd'; - -// 有两个 pending 动作不是「等待批准的动作」,而是某张专用卡的载体:Runtime 把内容停在 -// pending 上,真正的交互面是另一个组件。 -// -// - `user.input_request`:子 Agent 以 needs-user-input 终态退出后,Runtime 在 parent-wake -// 屏障处按信封原文构造这个 pending(`ensure_static_delegate_user_input_wait_at_locked`), -// 同一份问题再投影成 `userInputRequest`,交互面是问答卡。 -// - `plan.submit_gdd`:策划子 Agent 提交 Fast GDD 后,`planning/pending.json` 的 -// `submission.pendingActionId` 指向的正是这个 pending,交互面是 `GddApprovalCard` -// 的批准 / 修改 / 退回。 -// -// 两者都没有「确认 / 拒绝」语义:`user.input_request` 从来不在 -// `GAME_CREATION_APP_COMMANDS` 的 confirm 集合里;`plan.submit_gdd` 更是被运行时策略明确 -// 拒绝转成通用确认 pending(「Runtime-owned create-only 提交」),点确认必然失败。 -// -// 通用待确认卡按 `tool` + `inputSummary` 渲染,套到它们身上就是把同一个请求画两遍——标题 -// 是原始工具名,副标题是 `questionCount=… · questionsSha256=…` 这种给日志看的取证摘要, -// 还会把真正该看的那张卡压在下面。所以这里统一把通用确认面判掉。 -const AGENT_RUNTIME_DEDICATED_CARD_TOOLS = new Set([ - AGENT_RUNTIME_USER_INPUT_REQUEST_TOOL, - AGENT_RUNTIME_PLAN_SUBMIT_GDD_TOOL, -]); - -export function agentRuntimePendingActionHasDedicatedCard( - action: AgentRuntimePendingToolActionSummary | null | undefined, -) { - return Boolean(action && AGENT_RUNTIME_DEDICATED_CARD_TOOLS.has(action.tool)); -} - -export function matchingAgentRuntimeForSteer( - runtimes: Array, - agentId: string, - sessionId: string | null, - requestedRunProfile: NonNullable< - AgentRuntimeState['runProfile'] - > = 'standard', - requestedSource?: string, -) { - if (!sessionId) { - return null; - } - return ( - runtimes.find( - (runtime) => - runtime?.agentId === agentId && - runtime.sessionId === sessionId && - (runtime.runProfile ?? 'standard') === requestedRunProfile && - (!requestedSource || runtime.source === requestedSource) && - isAgentRuntimeSteerableState(runtime), - ) ?? null - ); -} - -export function projectSupervisorActiveSessionIdFromList( - sessionList: AgentConversationSessionListResult, -) { - if ( - !sessionList || - sessionList.agentId !== PROJECT_SUPERVISOR_AGENT_ID || - !Array.isArray(sessionList.sessions) - ) { - throw new Error('项目总控 Agent 会话列表返回格式无效'); - } - const activeSession = sessionList.activeSessionId - ? sessionList.sessions.find( - (session) => - session.sessionId === sessionList.activeSessionId && - session.archivedAt === null, - ) - : null; - return ( - activeSession?.sessionId || - sessionList.sessions.find((session) => session.archivedAt === null) - ?.sessionId || - null - ); -} - -export async function readProjectSupervisorActiveSessionId( - invoke: TauriInvoke, - projectPath: string, -) { - try { - const sessionList = await invoke( - 'list_game_creator_agent_sessions', - { - projectPath, - agentId: PROJECT_SUPERVISOR_AGENT_ID, - }, - ); - return projectSupervisorActiveSessionIdFromList(sessionList); - } catch (error) { - if (isMissingAgentSessionCommandError(error)) { - return null; - } - throw error; - } -} - -export async function ensureProjectSupervisorActiveSessionId( - invoke: TauriInvoke, - projectPath: string, -) { - const existingSessionId = await readProjectSupervisorActiveSessionId( - invoke, - projectPath, - ); - if (existingSessionId) { - return existingSessionId; - } - const sessionList = await invoke( - 'create_game_creator_agent_session', - { - projectPath, - agentId: PROJECT_SUPERVISOR_AGENT_ID, - title: '项目总控', - }, - ); - const sessionId = projectSupervisorActiveSessionIdFromList(sessionList); - if (!sessionId) { - throw new Error('项目总控 Agent active Session 创建失败'); - } - return sessionId; -} - -export async function submitProjectSupervisorRuntimeTask({ - invoke, - projectPath, - sessionId, - prompt, - runtime, - runProfile, - source, -}: { - invoke: TauriInvoke; - projectPath: string; - sessionId: string; - prompt: string; - runtime: AgentRuntimeState | null; - runProfile: 'standard' | 'autonomous-game-build'; - source: ProjectSupervisorRuntimeSubmission['source']; -}) { - const steerRuntime = matchingAgentRuntimeForSteer( - [runtime], - PROJECT_SUPERVISOR_AGENT_ID, - sessionId, - runProfile, - source, - ); - if (steerRuntime) { - const steer = await invoke( - 'steer_game_creator_agent_runtime_task', - { - projectPath, - agentId: PROJECT_SUPERVISOR_AGENT_ID, - sessionId, - runId: steerRuntime.runId, - steerId: createAgentChatRunId('project-supervisor-steer'), - instruction: prompt, - runProfile, - source, - }, - ); - return { - mode: 'steer' as const, - runtimeResult: steer.runtime, - acceptedRunId: steerRuntime.runId, - }; - } - const requestedRunId = createAgentChatRunId('project-supervisor-task'); - const runtimeResult = await invoke( - 'start_game_creator_supervisor_runtime_task', - { - projectPath, - sessionId, - task: prompt, - runId: requestedRunId, - runProfile, - source, - }, - ); - return { - mode: 'start' as const, - runtimeResult, - acceptedRunId: agentRuntimeStartedRunId(runtimeResult, requestedRunId), - }; -} - -export function agentRuntimeSteerStatus(result: AgentRuntimeSteerResult) { - const runId = result.runtime.state.runId; - if (result.status === 'restarted') { - return `目标已变化,正在新 Run 重新理解并执行:${runId}`; - } - if (result.providerInterrupted) { - return `智能服务已根据追加指令调整,旧请求已安全中断:${runId}`; - } - if (result.interruptDecision === false) { - return `智能服务已回复且判定无需中断,当前 Run 继续:${runId}`; - } - if (result.interruptDecision === true) { - return `智能服务已判定需要改向;旧请求已结束或新规划已开始:${runId}`; - } - if (result.status === 'applied') { - return `追加指令已应用,当前 Run 正在继续:${runId}`; - } - return `追加指令已排队,等待当前 Run 应用:${runId}`; -} - -export function agentRuntimeCancelStatus( - runtime: AgentRuntimeState, - runId: string, -) { - return runtime.status === 'cancelling' || runtime.phase === 'cancelling' - ? `正在取消后台任务:${runId}` - : `已取消后台任务:${runId}`; -} - function agentRuntimeProviderRetryStatus(runtime: AgentRuntimeState) { if (runtime.phase !== 'waiting-for-provider-retry') { return null; @@ -1108,32 +573,6 @@ export function agentRuntimeConversationStatus(runtime: AgentRuntimeState) { return waitingOn ? `Agent 正在运行,等待${waitingOn}` : 'Agent 正在运行'; } -export function projectSupervisorChatRuntimeStatus(runtime: AgentRuntimeState) { - if (runtime.status === 'idle' || runtime.phase === 'idle') { - return '等待输入'; - } - if ( - runtime.status === 'needs-reconciliation' || - runtime.phase === 'needs-reconciliation' - ) { - return runtime.error - ? projectRuntimeVisibleError(runtime.error, '项目总控 Agent', true) - : '项目总控 Agent 运行状态需要核对,请打开运行详情后重试'; - } - if (isAgentRuntimeTerminalState(runtime)) { - if (runtime.status === 'failed' || runtime.phase === 'failed') { - return runtime.error - ? projectRuntimeVisibleError(runtime.error, '项目总控 Agent', true) - : 'Agent 运行失败'; - } - if (runtime.status === 'cancelled' || runtime.phase === 'cancelled') { - return '本轮已取消'; - } - return '本轮已完成'; - } - return agentRuntimeConversationStatus(runtime); -} - export function formatAgentRuntimeEvent(event: AgentRuntimeEventRecord) { const isFailureEvent = [ 'error', @@ -1182,21 +621,6 @@ export function formatAgentRuntimeTaskQueue( return `任务队列:${parts.join(' · ')}`; } -export function formatAgentRuntimeLoopProgress( - runtime: Pick< - AgentRuntimeState, - 'loopIteration' | 'maxLoopIterations' | 'toolActionBudget' - >, -) { - const loopIteration = runtime.loopIteration ?? 0; - if (loopIteration <= 0) { - return null; - } - return `Loop:${loopIteration}/${runtime.maxLoopIterations ?? 3} · 工具预算 ${ - runtime.toolActionBudget ?? 3 - }`; -} - export function formatAgentRuntimePlanStep( step: AgentRuntimePlanStep, fallbackIndex = 0, @@ -1229,31 +653,6 @@ export function agentRuntimeActivePlanStep( ); } -export function agentRuntimeCanCancel(status: string) { - return [ - 'pending', - 'running', - 'waiting-for-confirmation', - 'waiting-for-user-input', - ].includes(status); -} - -export function agentRuntimeCanRetry(status: string) { - return ['cancelled', 'failed', 'completed', 'idle'].includes(status); -} - -export function agentGoalStatusIsPaused(status: string | null | undefined) { - return ['pause-requested', 'pausing', 'paused'].includes(status ?? ''); -} - -export function agentGoalStatusIsTerminal(status: string | null | undefined) { - return ['completed', 'cleared'].includes(status ?? ''); -} - -export function agentRuntimeCanConfirm(status: string) { - return status === 'waiting-for-confirmation'; -} - export function formatAgentRuntimeDelegationSource(runtime: { source: string; parentAgentId?: string | null; @@ -1284,20 +683,6 @@ export function createAgentRuntimeUserInputResponseId() { return `app-user-input-${Date.now().toString(36)}-${entropy}`.slice(0, 160); } -export function isMissingAgentSessionCommandError(error: unknown) { - const message = error instanceof Error ? error.message : String(error); - const normalized = message.toLowerCase(); - return ( - normalized.includes('list_game_creator_agent_sessions') && - (normalized.includes('not found') || - normalized.includes('unknown command') || - normalized.includes('unexpected invoke') || - normalized.includes('unexpected command') || - normalized.includes('不存在') || - normalized.includes('未找到')) - ); -} - export function isMissingAgentRuntimeResumeCommandError(error: unknown) { const message = error instanceof Error ? error.message : String(error); const normalized = message.toLowerCase(); @@ -1312,20 +697,6 @@ export function isMissingAgentRuntimeResumeCommandError(error: unknown) { ); } -export function isMissingAgentGoalCommandError(error: unknown) { - const message = error instanceof Error ? error.message : String(error); - const normalized = message.toLowerCase(); - return ( - normalized.includes('game_creator_agent_goal') && - (normalized.includes('not found') || - normalized.includes('unknown command') || - normalized.includes('unexpected invoke') || - normalized.includes('unexpected command') || - normalized.includes('不存在') || - normalized.includes('未找到')) - ); -} - export function createDefaultChatMessages(): ChatMessage[] { // 默认问候「想做什么游戏?」已移除:它在对话记录里没有信息量,而且会出现在用户消息之后。 // 空对话由空状态提示(panels.tsx 的引导文案)承担,不再往消息列表里塞占位消息。 @@ -1351,210 +722,6 @@ export function projectNameFromPath(projectPath: string) { ); } -export function projectWorkspaceStatusForDisplay(workspaceStatus: string) { - const openedPrefix = '已打开:'; - if (!workspaceStatus.startsWith(openedPrefix)) { - return workspaceStatus; - } - const projectPath = workspaceStatus.slice(openedPrefix.length).trim(); - if (!/[\\/]/u.test(projectPath)) { - return workspaceStatus; - } - return `${openedPrefix}${projectNameFromPath(projectPath)}`; -} - -export function mergeProjectSupervisorConversation( - projectRecords: LocalConversationMessageRecord[], - supervisorRecords: LocalConversationMessageRecord[], -): ChatMessage[] { - const supervisorRecordIndexByCorrelation = new Map(); - supervisorRecords.forEach((record, index) => { - if (record.role !== 'user') { - return; - } - const correlationId = agentRuntimeMessageCorrelationId(record.messageId); - if ( - correlationId && - !supervisorRecordIndexByCorrelation.has(correlationId) - ) { - supervisorRecordIndexByCorrelation.set(correlationId, index); - } - }); - const records = [ - ...projectRecords.map((record, index) => { - const runtimeOwned = Boolean( - record.messageId - ?.trim() - .startsWith(AGENT_RUNTIME_PUBLIC_STATUS_MESSAGE_ID_PREFIX), - ); - const accepted = - runtimeOwned && - record.content === '任务已接收,项目总控 Agent 正在启动处理。'; - const correlationId = agentRuntimeMessageCorrelationId(record.messageId); - const supervisorRecordIndex = correlationId - ? supervisorRecordIndexByCorrelation.get(correlationId) - : undefined; - const runtimeRecordIndex = - supervisorRecordIndex ?? supervisorRecords.length + index; - return { - record, - runtimeOwned, - sameSecondLane: runtimeOwned ? 1 : 0, - sameSecondOrder: runtimeOwned - ? runtimeRecordIndex * 4 + (accepted ? 1 : 2) - : index, - stableIndex: index, - }; - }), - ...supervisorRecords.map((record, index) => { - const userMessage = record.role === 'user'; - return { - record, - runtimeOwned: true, - sameSecondLane: 1, - // Conversation timestamps have second precision. Runtime task and - // public status IDs carry the same opaque run correlation digest, so - // status ordering follows its actual task record instead of guessing - // from independent user/accepted/terminal category counters. - sameSecondOrder: index * 4 + (userMessage ? 0 : 3), - stableIndex: projectRecords.length + index, - }; - }), - ] - .filter( - ({ record }) => record.role === 'user' || record.role === 'assistant', - ) - .sort( - (left, right) => - left.record.updatedAt - right.record.updatedAt || - left.sameSecondLane - right.sameSecondLane || - left.sameSecondOrder - right.sameSecondOrder || - left.stableIndex - right.stableIndex, - ); - const seen = new Set(); - const messages: ChatMessage[] = []; - for (const { record, runtimeOwned } of records) { - const messageId = record.messageId?.trim(); - const identity = messageId - ? `message:${messageId}` - : `identity:${JSON.stringify([ - record.role, - record.agentId, - record.updatedAt, - record.content, - ])}`; - if (seen.has(identity)) { - continue; - } - seen.add(identity); - messages.push({ - role: record.role as ChatMessage['role'], - text: record.content, - messageId: messageId ?? null, - agentId: record.agentId, - updatedAt: record.updatedAt, - runtimeOwned, - }); - } - return messages.length > 0 ? messages : createDefaultChatMessages(); -} - -export function projectSupervisorRuntimeStatusLabel( - runtime: AgentRuntimeState | null, - runtimeError: string, -) { - if ( - runtime?.status === 'needs-reconciliation' || - runtime?.phase === 'needs-reconciliation' - ) { - return '待核对'; - } - if ( - runtimeError || - runtime?.status === 'failed' || - runtime?.phase === 'failed' - ) { - return '失败'; - } - if (!runtime) { - return null; - } - if ( - runtime.userInputRequest || - runtime.status === 'waiting-for-user-input' || - runtime.phase === 'waiting-for-user-input' - ) { - return '需要回答'; - } - if ( - runtime.pendingToolAction || - runtime.status === 'waiting-for-confirmation' || - runtime.phase === 'waiting-for-confirmation' - ) { - return '等待确认'; - } - if ( - runtime.phase === 'waiting-for-delegate-receipts' || - runtime.phase === 'waiting-for-isolated-join' - ) { - return '等待专业 Agent'; - } - if (runtime.phase === 'waiting-for-manifest-tasks') { - return '等待项目任务'; - } - if ( - ['action', 'observation', 'executing'].includes(runtime.phase) || - runtime.status === 'executing' - ) { - return '执行'; - } - if (runtime.phase === 'response' || runtime.phase === 'finalizing') { - return '回复中'; - } - if (runtime.status === 'cancelled' || runtime.phase === 'cancelled') { - return '已取消'; - } - if (runtime.status === 'completed' || runtime.phase === 'completed') { - return '已完成'; - } - if (runtime.status === 'idle') { - return null; - } - return '分析'; -} - -export function projectSupervisorCollaboratingAgentRuntimes( - supervisorRuntime: AgentRuntimeState | null, - runtimeByAgentId: Record, -) { - if (!supervisorRuntime?.runId) { - return []; - } - const runtimesByAgentId = new Map(); - for (const runtime of Object.values(runtimeByAgentId)) { - const isVisibleChildSource = [ - 'agent-delegate', - 'agent-delegate-retry', - ].includes(runtime?.source ?? ''); - if ( - !runtime || - runtime.agentId === PROJECT_SUPERVISOR_AGENT_ID || - !isVisibleChildSource || - runtime.parentAgentId !== PROJECT_SUPERVISOR_AGENT_ID || - runtime.parentRunId !== supervisorRuntime.runId - ) { - continue; - } - const previous = runtimesByAgentId.get(runtime.agentId); - if (!previous || runtime.updatedAt >= previous.updatedAt) { - runtimesByAgentId.set(runtime.agentId, runtime); - } - } - return Array.from(runtimesByAgentId.values()).sort((left, right) => - left.agentId.localeCompare(right.agentId), - ); -} - export function projectProfessionalAgentLabel(agentId: string) { if (agentId === 'project-planning') { return '立项策划 Agent'; @@ -1599,65 +766,6 @@ export function isAgentFinalizationMessageId( return /^agent-finalization-[0-9a-f]{32}$/u.test(messageId ?? ''); } -export function projectRuntimeStatusPresentation(runtime: AgentRuntimeState) { - if (runtime.phase === 'needs-reconciliation') { - return { label: '待核对', tone: 'failed' }; - } - if ( - runtime.userInputRequest || - runtime.status === 'waiting-for-user-input' || - runtime.phase === 'waiting-for-user-input' - ) { - return { label: '待回答', tone: 'waiting' }; - } - if ( - runtime.pendingToolAction || - runtime.status === 'waiting-for-confirmation' || - runtime.phase === 'waiting-for-confirmation' - ) { - return { label: '待确认', tone: 'waiting' }; - } - if (runtime.status === 'failed' || runtime.phase === 'failed') { - return { label: '失败', tone: 'failed' }; - } - if (runtime.status === 'completed' || runtime.phase === 'completed') { - return { label: '已完成', tone: 'completed' }; - } - if (runtime.status === 'cancelled' || runtime.phase === 'cancelled') { - return { label: '已取消', tone: 'idle' }; - } - if (runtime.status === 'idle' || runtime.phase === 'idle') { - return { label: '等待中', tone: 'idle' }; - } - if (runtime.phase === 'planning') { - return { label: '分析中', tone: 'running' }; - } - if ( - runtime.phase === 'waiting-for-delegate-receipts' || - runtime.phase === 'waiting-for-isolated-join' - ) { - return { label: '协作中', tone: 'running' }; - } - if (runtime.phase === 'waiting-for-manifest-tasks') { - return { - label: '项目任务中', - tone: 'running', - }; - } - return { label: '执行中', tone: 'running' }; -} - -export function projectRuntimePlanProgress(runtime: AgentRuntimeState) { - const steps = (runtime.planSteps ?? []).filter( - (step) => agentRuntimePlanStepText(step).length > 0, - ); - return { - completed: steps.filter((step) => step.status === 'completed').length, - total: steps.length, - active: agentRuntimeActivePlanStep(runtime), - }; -} - export function projectRuntimeVisibleCurrentWork(runtime: AgentRuntimeState) { const providerRetryStatus = agentRuntimeProviderRetryStatus(runtime); if (providerRetryStatus) { @@ -2128,125 +1236,6 @@ export function projectRuntimeVisibleError( return `${subject} 执行失败,请稍后重试`; } -export function projectSupervisorVisibleConversationText( - message: string, - role: ChatMessage['role'] | 'tool' = 'assistant', - subject = '项目总控 Agent', -) { - const failurePrefix = '后台任务失败:'; - if (role !== 'assistant' || !message.startsWith(failurePrefix)) { - return message; - } - return projectRuntimeVisibleError( - message.slice(failurePrefix.length), - subject, - true, - ); -} - -export function projectSupervisorChatMessageText( - message: Pick, -) { - return message.role === 'assistant' - ? projectSupervisorVisibleConversationText(message.text, message.role) - : message.text; -} - -export function projectRuntimeVisibleToolSummary(summary: string) { - return summary - .split('·') - .map((part) => part.trim()) - .filter( - (part) => - part.length > 0 && - !/^(contentChars|contentBytes|sha256|fingerprint)=/i.test(part), - ) - .join(' · '); -} - -export function projectSupervisorPendingActionPresentation( - action: AgentRuntimePendingToolActionSummary, -) { - if (action.tool === 'agent.delegate') { - const agentId = - action.inputSummary?.match(/(?:^|·)\s*agentId=([^·]+)/)?.[1]?.trim() ?? - ''; - const agentLabel = agentId - ? projectProfessionalAgentLabel(agentId) - : '专业 Agent'; - const isRepair = - action.inputSummary?.includes('repairOf=') && - !action.inputSummary.includes('repairOf=null'); - return { - title: isRepair ? `安排${agentLabel}返工` : `安排${agentLabel}执行任务`, - detail: isRepair - ? '确认后将在当前项目创建一轮带原验收合同的返工任务' - : '确认后将在当前项目启动新的专业任务', - }; - } - return { - title: action.tool, - detail: action.inputSummary - ? projectRuntimeVisibleToolSummary(action.inputSummary) - : '确认后继续当前项目任务', - }; -} - -export function projectSupervisorPendingRepairMatchesProfessional( - action: AgentRuntimePendingToolActionSummary | null | undefined, - runtime: AgentRuntimeState, -) { - const summary = action?.inputSummary ?? ''; - return Boolean( - action?.tool === 'agent.delegate' && - runtime.delegationId && - summary.includes(`agentId=${runtime.agentId}`) && - summary.includes(`repairOf=${runtime.delegationId}`), - ); -} - -export function formatProjectRuntimeUpdatedAt(updatedAt: number) { - if (!Number.isFinite(updatedAt) || updatedAt <= 0) { - return '更新时间未知'; - } - const milliseconds = - updatedAt < 1_000_000_000_000 ? updatedAt * 1000 : updatedAt; - return `${new Date(milliseconds).toLocaleTimeString('zh-CN', { - hour: '2-digit', - minute: '2-digit', - second: '2-digit', - })} 更新`; -} - -export function formatProjectSupervisorCompactProgress( - runtime: AgentRuntimeState, - collaboratingAgentCount: number, -) { - const planSteps = (runtime.planSteps ?? []).filter( - (step) => agentRuntimePlanStepText(step).length > 0, - ); - const completedPlanStepCount = planSteps.filter( - (step) => step.status === 'completed', - ).length; - const activePlanStep = agentRuntimeActivePlanStep(runtime); - const currentPlanStep = activePlanStep - ? agentRuntimePlanStepText(activePlanStep) - : planSteps.length > 0 && completedPlanStepCount === planSteps.length - ? '已完成' - : '暂无'; - const waitingOn = - runtime.waitingOn?.trim() || agentRuntimeWaitingOnFromPhase(runtime.phase); - const nextStep = - runtime.nextStep?.trim() || agentRuntimeNextStepFromPhase(runtime.phase); - return [ - `计划完成:${completedPlanStepCount}/${planSteps.length}`, - `当前步骤:${currentPlanStep}`, - `等待:${waitingOn}`, - `下一步:${nextStep}`, - `专业 Agent 协作:${collaboratingAgentCount}`, - ].join(' · '); -} - export function taskRowsFromManifest( manifest: GameCreationAppManifest, ): GameCreationAppTaskState[] { diff --git a/apps/ai-game-creator-shell/src/features/agent-runtime/panels.tsx b/apps/ai-game-creator-shell/src/features/agent-runtime/panels.tsx deleted file mode 100644 index 157f1392b..000000000 --- a/apps/ai-game-creator-shell/src/features/agent-runtime/panels.tsx +++ /dev/null @@ -1,1328 +0,0 @@ -import { - ChevronDown, - ChevronUp, - Pause, - Pencil, - Play, - Target, - Trash2, -} from 'lucide-react'; -import { useEffect, useLayoutEffect, useRef, useState } from 'react'; - -import { closeDialogOnEscape } from '../../app/dialogs'; -import type { - AgentGoalRecord, - AgentRuntimeState, - AgentRuntimeUserInputRequest, -} from '../../app/types'; -import type { ProjectAgentResultSummary } from '../../view/project-development'; -import { - agentGoalStatusIsPaused, - agentGoalStatusIsTerminal, - agentRuntimeCanCancel, - agentRuntimeCanConfirm, - agentRuntimeCanRetry, - agentRuntimeNeedsUserInput, - agentRuntimeNextStepFromPhase, - agentRuntimePlanStepText, - agentRuntimeWaitingOnFromPhase, - createAgentRuntimeUserInputResponseId, - formatAgentRecentRuntimeTask, - formatAgentRuntimeDelegationSource, - formatAgentRuntimeEvent, - formatAgentRuntimeLoopProgress, - formatAgentRuntimePlanStep, - formatAgentRuntimeTaskQueue, - formatProjectRuntimeUpdatedAt, - formatProjectSupervisorCompactProgress, - isAgentRuntimeTerminalState, - projectProfessionalAgentLabel, - projectRuntimePlanProgress, - projectRuntimeStatusPresentation, - projectRuntimeVisibleCurrentWork, - projectRuntimeVisibleError, - projectRuntimeVisibleToolSummary, - projectSupervisorCollaboratingAgentRuntimes, - projectSupervisorPendingActionPresentation, - projectSupervisorRuntimeStatusLabel, -} from './model'; - -export function AgentGoalStatusPanel({ - goal, - runtime, - error, - controlBusy = false, - readOnly = false, - onStart, - onEdit, - onPause, - onResume, - onClear, -}: { - goal: AgentGoalRecord | null; - runtime: AgentRuntimeState | null; - error?: string | null; - controlBusy?: boolean; - readOnly?: boolean; - onStart?: () => void; - onEdit?: () => void; - onPause?: () => void; - onResume?: () => void; - onClear?: () => void; -}) { - const runtimeMatchesGoal = Boolean( - runtime?.goalId && (!goal || runtime.goalId === goal.goalId), - ); - const goalId = runtimeMatchesGoal ? runtime?.goalId : goal?.goalId; - const status = - (runtimeMatchesGoal ? runtime?.goalStatus : null) ?? goal?.status ?? null; - const revision = - (runtimeMatchesGoal ? runtime?.goalRevision : undefined) ?? - goal?.revision ?? - 0; - const outcome = - (runtimeMatchesGoal ? runtime?.goalOutcome : null) ?? goal?.outcome ?? ''; - const constraints = - (runtimeMatchesGoal ? runtime?.goalConstraints : undefined) ?? - goal?.constraints ?? - []; - const verification = - (runtimeMatchesGoal ? runtime?.goalVerification : undefined) ?? - goal?.verification ?? - []; - const hasGoal = Boolean(goalId); - const canStart = - Boolean(onStart) && (!hasGoal || agentGoalStatusIsTerminal(status)); - const canEdit = - Boolean(goal && onEdit) && - ['active', 'pause-requested', 'paused'].includes(status ?? ''); - const canPause = Boolean(goal && onPause) && status === 'active'; - const canResume = Boolean(goal && onResume) && status === 'paused'; - const canClear = - Boolean(goal && onClear) && - !['clearing', 'cleared', 'needs-reconciliation'].includes(status ?? ''); - return ( -
-
-
- 持久目标 - - {hasGoal - ? `Status:${status ?? '-'} · Revision:${revision}` - : '尚未开始'} - -
-
- {canStart ? ( - - ) : null} - {canEdit ? ( - - ) : null} - {canPause ? ( - - ) : null} - {canResume ? ( - - ) : null} - {canClear ? ( - - ) : null} -
-
- {hasGoal ? ( -
-

{`Outcome:${outcome || '-'}`}

- {`Verification:${verification.join(';') || '-'}`} - {constraints.length > 0 ? ( - {`Constraints:${constraints.join(';')}`} - ) : null} -
- ) : null} - {goal?.error ? {goal.error} : null} - {error ? {error} : null} -
- ); -} - -export function AgentRuntimeUserInputCard({ - request, - controlBusy, - onSubmit, -}: { - request: AgentRuntimeUserInputRequest; - controlBusy: boolean; - onSubmit?: ( - request: AgentRuntimeUserInputRequest, - responseId: string, - answers: Record, - ) => void | Promise; -}) { - const [answers, setAnswers] = useState>({}); - const [submitting, setSubmitting] = useState(false); - const [responseId] = useState( - () => request.responseId ?? createAgentRuntimeUserInputResponseId(), - ); - const answerPrepared = request.status === 'answer-prepared'; - const allAnswered = request.questions.every((question) => - Boolean(answers[question.id]?.trim()), - ); - const disabled = controlBusy || submitting || answerPrepared; - - return ( -
-
- 需要你的回答 - - {answerPrepared ? '已提交,正在继续' : '回答后继续当前任务'} - -
- {request.questions.map((question, index) => { - const answer = answers[question.id] ?? ''; - return ( -
- {`${index + 1}. ${question.header}`} -

{question.question}

-
- {question.options.map((option) => ( - - ))} -
-