接入策划顾问态做成游戏运行时切换
Project CI / Frontend tests (pull_request) Failing after 2m24s
Project CI / Repository checks (pull_request) Failing after 2m42s
Project CI / Native shell tests (pull_request) Failing after 5m45s
Project CI / Backend tests (pull_request) Successful in 6m46s

新增项目 Agent 运行时模式持久化与恢复判断

在顾问态增加做成游戏按钮并切换同项目 DirectProject

切换时不自动发起首轮 Provider 请求
This commit is contained in:
2026-09-11 12:01:38 +00:00
parent f8f1cdd1f4
commit 3047434b33
8 changed files with 127 additions and 6 deletions
@@ -813,6 +813,12 @@ pub(crate) fn hydrate_design_agent_session(
) -> Result<Option<DesignView>, 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<DesignRuntimeMode, String> {
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,
@@ -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<Option<DesignRuntimeMode>, 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<DesignRuntimeMode, String> {
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",
@@ -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,
+11
View File
@@ -526,6 +526,7 @@ type AppProps = {
) => void;
onAgentResultsChange?: (results: ProjectAgentResultSummary[]) => void;
onMakeGameFromApprovedGdd?: (projectPath: string) => Promise<void>;
onSwitchToGameRuntime?: (projectPath: string) => Promise<void>;
};
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) => {
@@ -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<ProjectManifestMergeState | null>(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={
<ProjectSupervisor
key={currentProjectContext.projectPath}
key={`${currentProjectContext.projectPath}:${agentRuntimeMode}`}
initialProjectPath={currentProjectContext.projectPath}
initialProjectManifest={currentProjectContext.manifest}
initialProjectKind={currentProjectContext.projectKind}
initialSupervisorMessage={currentProjectContext.initialPrompt}
initialCreationType={currentProjectContext.creationType}
initialAttachments={currentProjectContext.attachments}
initialSupervisorMessage={
agentRuntimeMode === 'design'
? currentProjectContext.initialPrompt
: ''
}
initialCreationType={
agentRuntimeMode === 'design'
? currentProjectContext.creationType
: null
}
initialAttachments={
agentRuntimeMode === 'design'
? currentProjectContext.attachments
: []
}
orchestrationMode="single-supervisor"
projectSupervisorOnly
planningStartMode={
@@ -358,6 +391,7 @@ export function WorkspaceLauncherShell({
}
onAgentResultsChange={setActiveProjectAgentResults}
onMakeGameFromApprovedGdd={startGameFromApprovedGdd}
onSwitchToGameRuntime={switchToGameRuntime}
/>
}
/>
@@ -56,6 +56,7 @@ export type ProjectSupervisorComponentProps = {
) => void;
onAgentResultsChange?: (results: ProjectAgentResultSummary[]) => void;
onMakeGameFromApprovedGdd?: (projectPath: string) => Promise<void>;
onSwitchToGameRuntime?: (projectPath: string) => Promise<void>;
};
export type WorkspaceLauncherShellProps = WorkspaceLauncherProps & {
@@ -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({
<div className="design-agent-controls__header">
<div>
<span></span>
<strong>{PHASE_LABELS[phase] ?? phase}</strong>
<div className="design-agent-controls__phase-title">
<strong>{PHASE_LABELS[phase] ?? phase}</strong>
{phase === 'consultant' ? (
<button
type="button"
className="design-agent-make-game"
disabled={busy || Boolean(view?.running)}
onClick={onMakeGame}
>
</button>
) : null}
</div>
</div>
{view?.running ? (
<span className="design-agent-controls__status"></span>
@@ -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)}
/>