From 3047434b33b6d79ff153c516e900dfa65f20c763 Mon Sep 17 00:00:00 2001 From: Linghong Date: Fri, 11 Sep 2026 12:01:38 +0000 Subject: [PATCH] =?UTF-8?q?=E6=8E=A5=E5=85=A5=E7=AD=96=E5=88=92=E9=A1=BE?= =?UTF-8?q?=E9=97=AE=E6=80=81=E5=81=9A=E6=88=90=E6=B8=B8=E6=88=8F=E8=BF=90?= =?UTF-8?q?=E8=A1=8C=E6=97=B6=E5=88=87=E6=8D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增项目 Agent 运行时模式持久化与恢复判断 在顾问态增加做成游戏按钮并切换同项目 DirectProject 切换时不自动发起首轮 Provider 请求 --- .../src-tauri/src/agent/design_runtime.rs | 21 +++++++++ .../agent/runtime_protocol/design_session.rs | 36 +++++++++++++++ .../src-tauri/src/main.rs | 1 + apps/ai-game-creator-shell/src/App.tsx | 11 +++++ .../features/app-shell/WorkspaceLauncher.tsx | 44 ++++++++++++++++--- .../src/features/app-shell/model.ts | 1 + .../project-workspace/DesignAgentSurface.tsx | 16 ++++++- .../ProjectSupervisorView.tsx | 3 ++ 8 files changed, 127 insertions(+), 6 deletions(-) diff --git a/apps/ai-game-creator-shell/src-tauri/src/agent/design_runtime.rs b/apps/ai-game-creator-shell/src-tauri/src/agent/design_runtime.rs index 8dba870dd..1907ea4d3 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/agent/design_runtime.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/agent/design_runtime.rs @@ -813,6 +813,12 @@ pub(crate) fn hydrate_design_agent_session( ) -> Result, String> { let root = Path::new(project_path.trim()); enforce_project_permission_policy(root, "conversation.read")?; + if read_design_runtime_mode(root)? + .as_ref() + .is_some_and(|mode| mode.active_runtime == "game") + { + return Ok(None); + } let project_id = design_project_id(root)?; let Some(session) = read_design_session(root)? else { return Ok(None); @@ -824,6 +830,21 @@ pub(crate) fn hydrate_design_agent_session( Ok(Some(design_view(&session, active.is_none()))) } +#[tauri::command] +pub(crate) fn set_design_agent_runtime_mode( + project_path: String, + active_runtime: String, +) -> Result { + let root = Path::new(project_path.trim()); + enforce_project_permission_policy(root, "conversation.write")?; + design_project_id(root)?; + let _lock = acquire_game_creator_agent_runtime_project_write_lock_with_wait( + root, + "design.runtime-mode", + )?; + write_design_runtime_mode(root, active_runtime.trim()) +} + #[tauri::command] pub(crate) async fn continue_design_agent_session( app: tauri::AppHandle, diff --git a/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/design_session.rs b/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/design_session.rs index 0611da773..7d6b8f147 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/design_session.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/design_session.rs @@ -9,8 +9,44 @@ use uuid::Uuid; pub(crate) const DESIGN_SESSION_SCHEMA_VERSION: &str = "design-agent-session.v1"; pub(crate) const DESIGN_SESSION_ENGINE: &str = "design-agent"; pub(crate) const DESIGN_SESSION_PATH: &str = ".agent/design-agent/session.json"; +pub(crate) const DESIGN_RUNTIME_MODE_PATH: &str = ".agent/runtime-mode.json"; const DESIGN_SESSION_MAX_BYTES: usize = 64 * 1024 * 1024; +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub(crate) struct DesignRuntimeMode { + pub(crate) active_runtime: String, +} + +pub(crate) fn read_design_runtime_mode(root: &Path) -> Result, String> { + read_agent_runtime_json_sidecar_with_max_bytes( + root, + DESIGN_RUNTIME_MODE_PATH, + "项目 Agent 运行时模式", + 4096, + ) +} + +pub(crate) fn write_design_runtime_mode( + root: &Path, + active_runtime: &str, +) -> Result { + if !matches!(active_runtime, "design" | "game") { + return Err("未知项目 Agent 运行时模式".to_string()); + } + let mode = DesignRuntimeMode { + active_runtime: active_runtime.to_string(), + }; + write_agent_runtime_json_sidecar_with_max_bytes( + root, + DESIGN_RUNTIME_MODE_PATH, + "项目 Agent 运行时模式", + &mode, + 4096, + )?; + Ok(mode) +} + pub(crate) const DESIGN_PHASES: [&str; 6] = [ "concept", "top_design", 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 8eadcc36b..ef9529238 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/main.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/main.rs @@ -2583,6 +2583,7 @@ fn main() { decide_planning_artifact_v2, hydrate_planning_session_v2, hydrate_design_agent_session, + set_design_agent_runtime_mode, continue_design_agent_session, decide_design_phase, list_design_workspace, diff --git a/apps/ai-game-creator-shell/src/App.tsx b/apps/ai-game-creator-shell/src/App.tsx index 0086ab819..f404ddd84 100644 --- a/apps/ai-game-creator-shell/src/App.tsx +++ b/apps/ai-game-creator-shell/src/App.tsx @@ -526,6 +526,7 @@ type AppProps = { ) => void; onAgentResultsChange?: (results: ProjectAgentResultSummary[]) => void; onMakeGameFromApprovedGdd?: (projectPath: string) => Promise; + onSwitchToGameRuntime?: (projectPath: string) => Promise; }; type ExecuteChatAgentReplyInput = { @@ -554,6 +555,7 @@ export function App({ onAgentRuntimeSummariesChange, onAgentResultsChange, onMakeGameFromApprovedGdd, + onSwitchToGameRuntime, }: AppProps = {}) { const { setTitle: setWindowTitle } = useWindowChrome(); const [planningV2Active, setPlanningV2Active] = useState(planningStartMode); @@ -11721,6 +11723,15 @@ export function App({ } : undefined } + onDesignMakeGame={ + useDesignAgentSurface && onSwitchToGameRuntime + ? () => { + const nextProjectPath = resolveChatProjectPath(localProject); + if (nextProjectPath) + void onSwitchToGameRuntime(nextProjectPath); + } + : undefined + } onDesignOpenFile={ useDesignAgentSurface ? (path) => { diff --git a/apps/ai-game-creator-shell/src/features/app-shell/WorkspaceLauncher.tsx b/apps/ai-game-creator-shell/src/features/app-shell/WorkspaceLauncher.tsx index c75b73aef..d3f8f2667 100644 --- a/apps/ai-game-creator-shell/src/features/app-shell/WorkspaceLauncher.tsx +++ b/apps/ai-game-creator-shell/src/features/app-shell/WorkspaceLauncher.tsx @@ -58,6 +58,9 @@ export function WorkspaceLauncherShell({ title: string; message: string; } | null>(null); + const [agentRuntimeMode, setAgentRuntimeMode] = useState<'design' | 'game'>( + 'game', + ); const developerAgent = useDeveloperAgentPanel(launcherView); const homeProject = useHomeProjectCreation({ setStatus, @@ -81,6 +84,11 @@ export function WorkspaceLauncherShell({ createHomeDraftAutomatically, openProject, } = homeProject; + useEffect(() => { + setAgentRuntimeMode( + currentProjectContext?.startMode === 'planning' ? 'design' : 'game', + ); + }, [currentProjectContext?.projectPath, currentProjectContext?.startMode]); const activeProjectContextRef = useRef(currentProjectContext); const manifestMergeRef = useRef(null); activeProjectContextRef.current = currentProjectContext; @@ -238,6 +246,16 @@ export function WorkspaceLauncherShell({ }); } + async function switchToGameRuntime(nextProjectPath: string) { + const invoke = resolveTauriInvoke(); + if (!invoke) throw new Error('需要在陶泥儿客户端内运行'); + await invoke('set_design_agent_runtime_mode', { + projectPath: nextProjectPath, + activeRuntime: 'game', + }); + setAgentRuntimeMode('game'); + } + const currentHelpTitle = launcherView === 'guide' ? '使用指南' @@ -328,7 +346,10 @@ export function WorkspaceLauncherShell({ preview={activeProjectPreview} agentRuntimeSummaries={activeProjectAgentRuntimeSummaries} agentResults={activeProjectAgentResults} - planningStartMode={currentProjectContext.startMode === 'planning'} + planningStartMode={ + agentRuntimeMode === 'design' && + currentProjectContext.startMode === 'planning' + } onPlay={() => requestCurrentProjectPlay(currentProjectContext.projectPath) } @@ -337,13 +358,25 @@ export function WorkspaceLauncherShell({ onProjectsOpen={() => setLauncherView('projects')} supervisor={ } /> diff --git a/apps/ai-game-creator-shell/src/features/app-shell/model.ts b/apps/ai-game-creator-shell/src/features/app-shell/model.ts index 8d8283eba..89ac9e16b 100644 --- a/apps/ai-game-creator-shell/src/features/app-shell/model.ts +++ b/apps/ai-game-creator-shell/src/features/app-shell/model.ts @@ -56,6 +56,7 @@ export type ProjectSupervisorComponentProps = { ) => void; onAgentResultsChange?: (results: ProjectAgentResultSummary[]) => void; onMakeGameFromApprovedGdd?: (projectPath: string) => Promise; + onSwitchToGameRuntime?: (projectPath: string) => Promise; }; export type WorkspaceLauncherShellProps = WorkspaceLauncherProps & { diff --git a/apps/ai-game-creator-shell/src/features/project-workspace/DesignAgentSurface.tsx b/apps/ai-game-creator-shell/src/features/project-workspace/DesignAgentSurface.tsx index 88d5f9914..64851c1b0 100644 --- a/apps/ai-game-creator-shell/src/features/project-workspace/DesignAgentSurface.tsx +++ b/apps/ai-game-creator-shell/src/features/project-workspace/DesignAgentSurface.tsx @@ -30,6 +30,7 @@ type DesignAgentSurfaceProps = { text: string, ) => void; onRetry: () => void; + onMakeGame: () => void; onOpenFile: (path: string) => void; onClosePreview: () => void; }; @@ -41,6 +42,7 @@ export function DesignAgentSurface({ onApprove, onClarify, onRetry, + onMakeGame, }: DesignAgentSurfaceProps) { const [clarifyText, setClarifyText] = useState(''); const phase = view?.session.currentPhase ?? 'concept'; @@ -54,7 +56,19 @@ export function DesignAgentSurface({
当前阶段 - {PHASE_LABELS[phase] ?? phase} +
+ {PHASE_LABELS[phase] ?? phase} + {phase === 'consultant' ? ( + + ) : null} +
{view?.running ? ( 工作中 diff --git a/apps/ai-game-creator-shell/src/features/project-workspace/ProjectSupervisorView.tsx b/apps/ai-game-creator-shell/src/features/project-workspace/ProjectSupervisorView.tsx index 9e35c088b..17499d5a9 100644 --- a/apps/ai-game-creator-shell/src/features/project-workspace/ProjectSupervisorView.tsx +++ b/apps/ai-game-creator-shell/src/features/project-workspace/ProjectSupervisorView.tsx @@ -115,6 +115,7 @@ type ProjectSupervisorViewProps = RuntimePanelProps & { text: string, ) => void; onDesignRetry?: () => void; + onDesignMakeGame?: () => void; onDesignOpenFile?: (path: string) => void; onDesignClosePreview?: () => void; }; @@ -159,6 +160,7 @@ export function ProjectSupervisorView({ onDesignApprove, onDesignClarify, onDesignRetry, + onDesignMakeGame, onDesignOpenFile, onDesignClosePreview, ...runtimePanelProps @@ -200,6 +202,7 @@ export function ProjectSupervisorView({ onApprove={onDesignApprove ?? (() => undefined)} onClarify={onDesignClarify ?? (() => undefined)} onRetry={onDesignRetry ?? (() => undefined)} + onMakeGame={onDesignMakeGame ?? (() => undefined)} onOpenFile={onDesignOpenFile ?? (() => undefined)} onClosePreview={onDesignClosePreview ?? (() => undefined)} />