From 340d8241a53c31e90f8792fea807971d31069846 Mon Sep 17 00:00:00 2001 From: Linghong Date: Sun, 13 Sep 2026 19:32:12 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E7=AD=96=E5=88=92agent=20(#3?= =?UTF-8?q?40)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reviewed-on: http://genarrative-station/git/GenarrativeAI/Genarrative/pulls/340 Co-authored-by: Linghong Co-committed-by: Linghong --- apps/ai-game-creator-shell/package.json | 2 +- .../src-tauri/Cargo.lock | 2 +- .../src-tauri/Cargo.toml | 2 +- .../src-tauri/src/agent/design_runtime.rs | 35 ++++++-- .../src-tauri/src/main.rs | 2 +- .../src-tauri/tauri.conf.json | 2 +- apps/ai-game-creator-shell/src/App.tsx | 58 +++++++++--- .../project-workspace/DesignAgentSurface.tsx | 88 ++++++++++++++----- .../ProjectSupervisorView.tsx | 17 +++- .../runtime-config/RuntimeConfigDialog.tsx | 2 +- apps/ai-game-creator-shell/src/styles.css | 6 ++ .../tests/appSurface/harness.ts | 2 +- .../appSurface/runtime-settings.suite.ts | 4 +- package-lock.json | 2 +- 14 files changed, 170 insertions(+), 54 deletions(-) diff --git a/apps/ai-game-creator-shell/package.json b/apps/ai-game-creator-shell/package.json index e195301c3..da7eddb68 100644 --- a/apps/ai-game-creator-shell/package.json +++ b/apps/ai-game-creator-shell/package.json @@ -1,7 +1,7 @@ { "name": "@genarrative/ai-game-creator-shell", "private": true, - "version": "0.1.12", + "version": "0.1.27", "type": "module", "scripts": { "dev": "node scripts/start-tauri-dev.mjs", diff --git a/apps/ai-game-creator-shell/src-tauri/Cargo.lock b/apps/ai-game-creator-shell/src-tauri/Cargo.lock index d6046baf7..60032820b 100644 --- a/apps/ai-game-creator-shell/src-tauri/Cargo.lock +++ b/apps/ai-game-creator-shell/src-tauri/Cargo.lock @@ -1725,7 +1725,7 @@ dependencies = [ [[package]] name = "genarrative-ai-game-creator-shell" -version = "0.1.12" +version = "0.1.27" dependencies = [ "agent-runtime-core", "axum", diff --git a/apps/ai-game-creator-shell/src-tauri/Cargo.toml b/apps/ai-game-creator-shell/src-tauri/Cargo.toml index b70c07f0d..370777cf3 100644 --- a/apps/ai-game-creator-shell/src-tauri/Cargo.toml +++ b/apps/ai-game-creator-shell/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "genarrative-ai-game-creator-shell" -version = "0.1.12" +version = "0.1.27" edition = "2021" publish = false 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 a8bb580e9..3cd44b4bd 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 @@ -229,15 +229,18 @@ fn prepare_design_decision( } if approved { let phase = approve_design_phase(session, request_id)?; - let suffix = if phase == "consultant" { - "" - } else { - "请开始该阶段工作。" - }; append_design_user( session, id, - format!("用户已批准上一阶段,现在进入 {phase} 阶段。{suffix}"), + if phase == "consultant" { + format!( + "用户已批准上一阶段,现在进入 {phase} 阶段。开始本轮工作前,先调用 get_workflow_status 确认 Runtime 当前阶段;在工具返回前,不要开始顾问工作或断言阶段状态。" + ) + } else { + format!( + "用户已批准上一阶段,现在进入 {phase} 阶段。开始本轮工作前,先调用 get_workflow_status 确认 Runtime 当前阶段;在工具返回前,不要开始新阶段工作、写入文件、提交审批或断言阶段状态。" + ) + }, ); begin_design_turn(session, id); } else { @@ -473,7 +476,7 @@ fn design_debug(root: &Path, kind: &str, data: Value) { type Entry = (PathBuf, Value); static QUEUE: OnceLock> = OnceLock::new(); let sender = QUEUE.get_or_init(|| { - let (sender, receiver) = std::sync::mpsc::sync_channel::(16); + let (sender, receiver) = std::sync::mpsc::sync_channel::(256); let _ = std::thread::Builder::new() .name("design-debug".into()) .spawn(move || { @@ -546,8 +549,26 @@ async fn request_design_provider( None, )); let result = if llm.stream { + let mut stream_sequence = 0_u64; client .stream_run(request.clone(), |delta| { + stream_sequence = stream_sequence.saturating_add(1); + design_debug( + root, + "stream", + json!({ + "turnId": turn_id, + "requestIndex": session.turn.as_ref().map(|turn| turn.request_index), + "attempt": attempt, + "sequence": stream_sequence, + "occurredAtUnixNanos": unix_timestamp_nanos().to_string(), + "model": llm.model, + "deltaChars": delta.delta_text.chars().count(), + "accumulatedChars": delta.accumulated_text.chars().count(), + "deltaText": delta.delta_text, + "finishReason": delta.finish_reason, + }), + ); emit(design_event( root, &turn_id, 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 5ec7552b4..3813a0eb6 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/main.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/main.rs @@ -1501,7 +1501,7 @@ const GAME_CREATOR_APP_CONFIG_SCHEMA_VERSION: &str = "game-creator-config.v2"; const DEFAULT_GAME_CREATOR_LLM_BASE_URL: &str = "https://dev.genarrative.world/gpt/v1"; const DEFAULT_GAME_CREATOR_LLM_MODEL: &str = "gpt-6-astra"; const DEFAULT_GAME_CREATOR_LLM_API_KIND: &str = "openai_responses"; -const DEFAULT_GAME_CREATOR_LLM_REASONING_EFFORT: &str = "max"; +const DEFAULT_GAME_CREATOR_LLM_REASONING_EFFORT: &str = "high"; const DEFAULT_GAME_CREATOR_LLM_CONTEXT_WINDOW_TOKENS: u64 = 128_000; const DEFAULT_GAME_CREATOR_LLM_AUTO_COMPACT_TOKEN_LIMIT: u64 = 64_000; const DEFAULT_GAME_CREATOR_LLM_TOOL_OUTPUT_TOKEN_LIMIT: u64 = 12_000; diff --git a/apps/ai-game-creator-shell/src-tauri/tauri.conf.json b/apps/ai-game-creator-shell/src-tauri/tauri.conf.json index 15e394d98..8cf521851 100644 --- a/apps/ai-game-creator-shell/src-tauri/tauri.conf.json +++ b/apps/ai-game-creator-shell/src-tauri/tauri.conf.json @@ -1,7 +1,7 @@ { "$schema": "https://schema.tauri.app/config/2", "productName": "Genarrative AI Game Creator", - "version": "0.1.12", + "version": "0.1.27", "identifier": "world.genarrative.ai-game-creator", "build": { "beforeDevCommand": "npm --prefix ../.. run agc:serve", diff --git a/apps/ai-game-creator-shell/src/App.tsx b/apps/ai-game-creator-shell/src/App.tsx index 49c0242f2..3aece9802 100644 --- a/apps/ai-game-creator-shell/src/App.tsx +++ b/apps/ai-game-creator-shell/src/App.tsx @@ -817,7 +817,10 @@ export function App({ null, ); planningV2SessionRef.current = planningV2Session; - const [planningV2TransientReply, setPlanningV2TransientReply] = useState(''); + const [planningV2TransientReply, setPlanningV2TransientReplyVisible] = + useState(''); + const planningV2TransientReplyTargetRef = useRef(''); + const planningV2VisibleReplyRef = useRef(''); const [planningV2Reasoning, setPlanningV2Reasoning] = useState(''); const planningV2TurnRef = useRef<{ projectPath: string; @@ -838,6 +841,37 @@ export function App({ >(), ); + function setPlanningV2TransientReplyTarget(next: string) { + planningV2TransientReplyTargetRef.current = next; + if (!next) { + planningV2VisibleReplyRef.current = ''; + setPlanningV2TransientReplyVisible(''); + } + } + + useEffect(() => { + const timer = window.setInterval(() => { + const target = planningV2TransientReplyTargetRef.current; + setPlanningV2TransientReplyVisible((current) => { + if (!target) { + planningV2VisibleReplyRef.current = ''; + return ''; + } + const prefix = target.startsWith(current) ? current : ''; + if (prefix === target) { + planningV2VisibleReplyRef.current = target; + return target; + } + const remaining = target.length - prefix.length; + const step = remaining > 160 ? 4 : remaining > 48 ? 2 : 1; + const next = target.slice(0, prefix.length + step); + planningV2VisibleReplyRef.current = next; + return next; + }); + }, 50); + return () => window.clearInterval(timer); + }, []); + function applyPlanningV2CommandResult( result: PlanningSessionCommandResultV2, clientTurnId?: string, @@ -1006,7 +1040,7 @@ export function App({ }; setChatAgentBusy(true); setProjectSupervisorRuntimeError(''); - setPlanningV2TransientReply(''); + setPlanningV2TransientReplyTarget(''); try { const view = await invoke('continue_design_agent_session', { projectPath: nextProjectPath, @@ -1029,7 +1063,7 @@ export function App({ setPlanGddError(message); } finally { designAgentTurnRef.current = null; - setPlanningV2TransientReply(''); + setPlanningV2TransientReplyTarget(''); setChatAgentBusy(false); } } @@ -1520,7 +1554,7 @@ export function App({ setProjectSupervisorResponseStream(null); setProjectSupervisorRuntimeError(''); setPlanningV2Session(null); - setPlanningV2TransientReply(''); + setPlanningV2TransientReplyTarget(''); setPlanningV2Active(planningStartMode); planningV2ActiveRef.current = planningStartMode; designAgentLaneRef.current = planningStartMode; @@ -1914,7 +1948,7 @@ export function App({ return; } if (payload.status === 'started') { - setPlanningV2TransientReply( + setPlanningV2TransientReplyTarget( payload.accumulatedText || '正在生成策划方案…', ); setProjectSupervisorRuntimeError(''); @@ -1923,7 +1957,7 @@ export function App({ payload.status === 'delta' && (payload.accumulatedText || payload.deltaText) ) { - setPlanningV2TransientReply( + setPlanningV2TransientReplyTarget( payload.accumulatedText || payload.deltaText, ); setProjectSupervisorRuntimeError(''); @@ -1971,13 +2005,13 @@ export function App({ return; } if (payload.kind === 'text' && payload.text != null) { - setPlanningV2TransientReply(payload.text); + setPlanningV2TransientReplyTarget(payload.text); } if (payload.reasoningText != null) { setPlanningV2Reasoning(payload.reasoningText); } if (payload.kind === 'tool' && payload.text) { - setPlanningV2TransientReply(payload.text); + setPlanningV2TransientReplyTarget(payload.text); } if (payload.view) { applyDesignView(payload.view, payload.projectPath); @@ -6037,7 +6071,7 @@ export function App({ }; setChatAgentBusy(true); setProjectSupervisorRuntimeError(''); - setPlanningV2TransientReply(''); + setPlanningV2TransientReplyTarget(''); try { const result = currentSessionId ? await invoke( @@ -6090,7 +6124,7 @@ export function App({ ]); } finally { planningV2TurnRef.current = null; - setPlanningV2TransientReply(''); + setPlanningV2TransientReplyTarget(''); setChatAgentBusy(false); } } @@ -11790,7 +11824,7 @@ export function App({ projectPath: nextProjectPath, clientTurnId, }; - setPlanningV2TransientReply(''); + setPlanningV2TransientReplyTarget(''); setChatAgentBusy(true); setPlanGddDecisionBusy(true); void invoke('decide_design_phase', { @@ -11831,7 +11865,7 @@ export function App({ return; } designAgentTurnRef.current = null; - setPlanningV2TransientReply(''); + setPlanningV2TransientReplyTarget(''); setChatAgentBusy(false); setPlanGddDecisionBusy(false); }); 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 91483c865..077a53437 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 @@ -25,23 +25,20 @@ type DesignAgentSurfaceProps = { onRetry: () => void; }; -export function DesignAgentSurface({ +type DesignAgentControlsProps = Pick< + DesignAgentSurfaceProps, + 'view' | 'busy' | 'error' | 'onApprove' | 'onClarify' | 'onRetry' +>; + +export function DesignAgentPhaseStatus({ view, - busy, error, - onApprove, - onClarify, onRetry, -}: DesignAgentSurfaceProps) { - const [clarifyText, setClarifyText] = useState(''); + busy, +}: DesignAgentControlsProps) { const phase = view?.session.currentPhase ?? 'concept'; - const pending = view?.session.pendingApproval; - const clarification = view?.session.pendingClarification; - useLayoutEffect(() => { - setClarifyText(''); - }, [clarification?.requestId]); return ( -
+
当前阶段 @@ -54,6 +51,42 @@ export function DesignAgentSurface({ ) : null}
{error ?

{error}

: null} + {view?.canRetry ? ( + + ) : null} +
+ ); +} + +export function DesignAgentPendingActions({ + view, + busy, + onApprove, + onClarify, +}: DesignAgentControlsProps) { + const [clarifyText, setClarifyText] = useState(''); + const phase = view?.session.currentPhase ?? 'concept'; + const pending = view?.session.pendingApproval; + const clarification = view?.session.pendingClarification; + useLayoutEffect(() => { + setClarifyText(''); + }, [clarification?.requestId]); + if (!pending || phase === 'consultant') { + if (!clarification) return null; + } + return ( +
{pending && phase !== 'consultant' ? (

当前阶段产物已提交,是否批准进入下一阶段?

@@ -110,17 +143,26 @@ export function DesignAgentSurface({
) : null} - {view?.canRetry ? ( - - ) : null}
); } + +export function DesignAgentSurface({ + view, + busy, + error, + onApprove, + onClarify, + onRetry, +}: DesignAgentSurfaceProps) { + return ( + + ); +} 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 5e15cd53d..ef8a6b119 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 @@ -31,7 +31,10 @@ import { ConversationModelSelect, type ConversationModelSelectHandle, } from './ConversationModelSelect'; -import { DesignAgentSurface } from './DesignAgentSurface'; +import { + DesignAgentPendingActions, + DesignAgentPhaseStatus, +} from './DesignAgentSurface'; import { PlanGddSurface } from './GddApprovalCard'; import { pendingCommandDetail, @@ -212,7 +215,7 @@ export function ProjectSupervisorView({ >
{designView || onDesignApprove ? ( -
) : null} + {designView || onDesignApprove ? ( + undefined)} + onClarify={onDesignClarify ?? (() => undefined)} + onRetry={onDesignRetry ?? (() => undefined)} + /> + ) : null}