From 1a4af3506cad32ae8219cb5c64da94efa6c07022 Mon Sep 17 00:00:00 2001 From: Linghong Date: Wed, 23 Sep 2026 13:29:28 +0000 Subject: [PATCH] =?UTF-8?q?=E6=B8=85=E7=90=86=E6=B5=8B=E8=AF=95=E7=94=A8?= =?UTF-8?q?=E9=80=94=E4=BB=A3=E7=A0=81=E5=B9=B6=E5=87=8F=E5=B0=91=E4=B8=83?= =?UTF-8?q?=E5=8D=81=E6=9D=A1=E7=BC=96=E8=AF=91=E8=AD=A6=E5=91=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 收紧测试辅助代码的编译范围并删除退役实现和冗余包装 逐项核对原测试断言,仅保留现役契约覆盖并调整必要调用 移除未消费的Pending错误载荷,保留持久错误记录和恢复验证 保留四条待澄清契约项,更新警告清单与实施文档 验证一百七十八个定向Rust测试,默认构建警告从一百二十条降至五十条 --- .../build_support/runtime_prompt_bundle.rs | 4 +- .../src/agent/codex_app_server/mod.rs | 3 + .../src/agent/codex_provider_proxy.rs | 1 + .../src-tauri/src/agent/design_runtime.rs | 2 + .../src-tauri/src/agent/direct_runtime/mod.rs | 14 - .../src-tauri/src/agent/direct_thread_wire.rs | 2 + .../src-tauri/src/agent/direct_tool_bridge.rs | 1 + .../src/agent/generation/canvas_generation.rs | 1 + .../src-tauri/src/agent/prompt.rs | 8 - .../src-tauri/src/agent/runtime_actions.rs | 13 +- .../src/agent/runtime_actions/action_audit.rs | 1 + .../pending_confirmation_ledger.rs | 14 - .../runtime_actions/response_stream_tests.rs | 24 +- .../runtime_actions/tool_plan_protocol.rs | 9 +- .../src-tauri/src/agent/runtime_driver.rs | 6 +- .../src/agent/runtime_driver/entrypoints.rs | 24 +- .../src/agent/runtime_driver/main_loop.rs | 2 +- .../src-tauri/src/agent/runtime_protocol.rs | 23 +- .../agent/runtime_protocol/context_bundle.rs | 1 + .../agent/runtime_protocol/goal_contract.rs | 1 + .../src/agent/runtime_protocol/models.rs | 4 +- .../runtime_protocol/run_configuration.rs | 2 + .../agent/runtime_protocol/verification.rs | 1 + .../src-tauri/src/agent/runtime_state.rs | 44 +-- .../src-tauri/src/agent/runtime_tools.rs | 9 +- .../src/agent/runtime_tools/delegation.rs | 31 -- .../src/agent/runtime_tools/isolated_joins.rs | 11 +- .../src-tauri/src/collaboration.rs | 3 + .../src-tauri/src/command_exec.rs | 2 + .../src-tauri/src/config.rs | 2 +- .../src-tauri/src/context_compaction.rs | 1 + .../src-tauri/src/delegation.rs | 65 ++-- .../src-tauri/src/editor_adapters.rs | 36 ++- .../src/editor_adapters/execution.rs | 18 +- .../src-tauri/src/patchset.rs | 35 +-- .../src-tauri/src/preview.rs | 1 + .../src/process_session/lifecycle.rs | 1 + .../src-tauri/src/project/conversation.rs | 2 + .../src/project/external_editor_bindings.rs | 2 + .../src-tauri/src/project/memory.rs | 22 -- .../src-tauri/src/project/verification.rs | 2 + .../src-tauri/src/runner/client.rs | 90 ------ .../src-tauri/src/runner/dispatch.rs | 59 +--- .../src-tauri/src/runner/tests.rs | 285 ++---------------- .../src/tests/collaboration/claims.rs | 10 +- .../src/tests/collaboration/recovery.rs | 8 +- .../tests/collaboration/static_deliveries.rs | 80 ++++- .../src-tauri/src/tests/goal.rs | 2 +- .../src-tauri/src/tests/mod.rs | 23 ++ .../src-tauri/src/tests/project_tools.rs | 17 +- .../src-tauri/src/tests/provider.rs | 29 +- .../src-tauri/src/tests/response_stream.rs | 81 +++-- .../autonomous_game_build.rs | 24 +- .../src/tests/runtime_actions/policy.rs | 12 - .../src/tests/runtime_actions/support.rs | 32 +- .../tests/runtime_actions/task_lifecycle.rs | 3 +- .../src-tauri/src/tests/runtime_state.rs | 53 +--- .../src-tauri/src/tests/sessions.rs | 15 +- .../src-tauri/src/ui_editor/workflow.rs | 1 + ...解决】主要构建入口编译警告清单-2026-09-23.md | 155 +++++++++- ...¹案】AI游戏创作智能体App实施计划-2026-06-24.md | 4 +- 61 files changed, 549 insertions(+), 882 deletions(-) diff --git a/apps/ai-game-creator-shell/src-tauri/build_support/runtime_prompt_bundle.rs b/apps/ai-game-creator-shell/src-tauri/build_support/runtime_prompt_bundle.rs index 7eae872ab..47161fd96 100644 --- a/apps/ai-game-creator-shell/src-tauri/build_support/runtime_prompt_bundle.rs +++ b/apps/ai-game-creator-shell/src-tauri/build_support/runtime_prompt_bundle.rs @@ -935,11 +935,11 @@ fn render_rust(manifest: &PromptBundleManifest, sections: &BTreeMap Option<&'static str> {\n match id {\n"); diff --git a/apps/ai-game-creator-shell/src-tauri/src/agent/codex_app_server/mod.rs b/apps/ai-game-creator-shell/src-tauri/src/agent/codex_app_server/mod.rs index 25f478c81..cac4568b6 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/agent/codex_app_server/mod.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/agent/codex_app_server/mod.rs @@ -720,6 +720,7 @@ pub(super) fn resolve_direct_codex_project_authority( Ok((project_root.clone(), project_root)) } +#[cfg(test)] fn resolve_direct_codex_game_workspace( project_root: &std::path::Path, ) -> Result { @@ -1933,6 +1934,7 @@ fn direct_tools_mcp_executable_path() -> Result) -> String { DESIGN_PANIC_PUBLIC_ERROR.to_string() } +#[cfg(test)] pub(crate) async fn continue_design_agent_at( root: &Path, resources: &DesignResources, @@ -1311,6 +1312,7 @@ async fn recover_uncertain_design_batch( .await } +#[cfg(test)] pub(crate) async fn decide_design_phase_at( root: &Path, resources: &DesignResources, diff --git a/apps/ai-game-creator-shell/src-tauri/src/agent/direct_runtime/mod.rs b/apps/ai-game-creator-shell/src-tauri/src/agent/direct_runtime/mod.rs index 5cc3499a5..a82a3d925 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/agent/direct_runtime/mod.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/agent/direct_runtime/mod.rs @@ -100,12 +100,6 @@ fn direct_codex_game_outputs(root: &Path) -> Vec<(String, GameCreationAppAssetKi ] } -fn direct_existing_game_sources_exist(root: &Path) -> bool { - direct_codex_game_outputs(root) - .iter() - .all(|(path, _, _)| root.join(path).is_file()) -} - /// Art generation is an external, billable side effect. Existing direct /// projects therefore stay in their same-thread code-edit/preview loop unless /// the user explicitly asks for a new game or a visual regeneration. @@ -6625,14 +6619,6 @@ mod tests { #[test] fn existing_game_edits_do_not_request_a_fresh_art_generation_by_default() { - let root = tempfile::tempdir().expect("temp dir"); - init_local_game_project_at(root.path(), "direct-edit-intent", "继续编辑") - .expect("init project"); - std::fs::write(root.path().join("game/index.html"), "").expect("index"); - std::fs::write(root.path().join("game/style.css"), "body {};").expect("style"); - std::fs::write(root.path().join("game/game.js"), "console.log('edit');").expect("script"); - - assert!(direct_existing_game_sources_exist(root.path())); for prompt in [ "把棋盘上移一点", "修复闪烁", diff --git a/apps/ai-game-creator-shell/src-tauri/src/agent/direct_thread_wire.rs b/apps/ai-game-creator-shell/src-tauri/src/agent/direct_thread_wire.rs index 354dc8466..c71987fb6 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/agent/direct_thread_wire.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/agent/direct_thread_wire.rs @@ -329,6 +329,7 @@ impl DirectThreadEvent { /// 本轮开口用户条目的 canonical itemId:只有生命周期事件有,其余返回 `None`。 /// /// 只读已存入事件的值,不在读取时重算——重放要用的就是原事件的身份。 + #[cfg(test)] pub(crate) fn user_item_id(&self) -> Option<&str> { match self { Self::TurnStarted { user_item_id, .. } | Self::TurnCompleted { user_item_id, .. } => { @@ -361,6 +362,7 @@ impl DirectThreadEvent { /// 事件级阶段时间(毫秒):只有四种生命周期事件有,其余事件返回 `None`。 /// /// 只读已存入事件的值,不在读取时取钟——重放要用的就是原事件的时间。 + #[cfg(test)] pub(crate) fn at(&self) -> Option { match self { Self::TurnStarted { at, .. } diff --git a/apps/ai-game-creator-shell/src-tauri/src/agent/direct_tool_bridge.rs b/apps/ai-game-creator-shell/src-tauri/src/agent/direct_tool_bridge.rs index fdbc2ad61..93b0d5741 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/agent/direct_tool_bridge.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/agent/direct_tool_bridge.rs @@ -395,6 +395,7 @@ fn direct_resource_request_uuid(turn_id: &str, domain: &str, request_fingerprint uuid::Uuid::from_bytes(bytes).hyphenated().to_string() } +#[cfg(test)] fn direct_tool_bridge_state(root: PathBuf) -> Arc { direct_tool_bridge_state_with_search(root, false) } diff --git a/apps/ai-game-creator-shell/src-tauri/src/agent/generation/canvas_generation.rs b/apps/ai-game-creator-shell/src-tauri/src/agent/generation/canvas_generation.rs index da3076fc7..671986b72 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/agent/generation/canvas_generation.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/agent/generation/canvas_generation.rs @@ -1145,6 +1145,7 @@ pub(crate) fn external_generation_submit_rejection_is_definitive( ) } +#[cfg(test)] pub(crate) async fn wait_for_external_generation_result( client: &reqwest::Client, api_base_url: &str, diff --git a/apps/ai-game-creator-shell/src-tauri/src/agent/prompt.rs b/apps/ai-game-creator-shell/src-tauri/src/agent/prompt.rs index a28d7b60f..3f9e9a99d 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/agent/prompt.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/agent/prompt.rs @@ -342,14 +342,6 @@ pub(super) fn unix_timestamp_nanos() -> u128 { .as_nanos() } -pub(crate) fn build_game_creator_role_agent_chat_request( - root: &Path, - agent_id: &str, - prompt: &str, -) -> Result<(GameCreatorLlmConfig, String, LlmRunRequest), String> { - build_game_creator_role_agent_chat_request_for_session(root, agent_id, None, prompt) -} - pub(crate) fn build_game_creator_role_agent_chat_request_for_session( root: &Path, agent_id: &str, diff --git a/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_actions.rs b/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_actions.rs index 5aada9a28..a8140eb40 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_actions.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_actions.rs @@ -48,10 +48,13 @@ pub(in crate::agent) use tool_plan_protocol::*; pub(crate) use action_audit::agent_runtime_action_receipt_public_safe_detail_for_test; #[cfg(test)] pub(crate) use action_audit::agent_runtime_action_receipt_safe_detail_for_owner_for_test; +#[cfg(test)] pub(crate) use action_audit::{ - agent_runtime_git_commit_safe_detail_value, agent_runtime_tool_action_fingerprint, - agent_runtime_tool_action_id, agent_runtime_tool_action_input_summary, - append_agent_runtime_action_receipt, + agent_runtime_git_commit_safe_detail_value, append_agent_runtime_action_receipt, +}; +pub(crate) use action_audit::{ + agent_runtime_tool_action_fingerprint, agent_runtime_tool_action_id, + agent_runtime_tool_action_input_summary, append_agent_runtime_action_receipt_with_project_revision_before, append_agent_runtime_tool_call_record, AgentRuntimeToolPolicyBlock, }; @@ -85,7 +88,6 @@ pub(crate) use parallel_read::{ rewind_game_creator_agent_runtime_parallel_read_batch_for_test_at, }; pub(crate) use pending_confirmation_ledger::{ - agent_runtime_contains_secret_key_prefix, game_creator_agent_runtime_pending_tool_action_exists, read_game_creator_agent_runtime_pending_tool_action, write_game_creator_agent_runtime_pending_tool_action, @@ -140,7 +142,8 @@ pub(crate) use structured_plan::{ // 复核,只对测试开放;生产代码必须用 `_for_agent`。 #[cfg(test)] pub(crate) use tool_plan_protocol::parse_game_creator_agent_tool_plan_llm_response; -pub(crate) use tool_plan_protocol::parse_game_creator_agent_tool_plan_response; +#[cfg(test)] +pub(crate) use tool_plan_protocol::parse_game_creator_agent_tool_plan_response_classified; pub(crate) use tool_policy_snapshot::{ agent_runtime_acceptance_evidence_tools, agent_runtime_autonomous_design_foundation_command_is_allowed, agent_runtime_executable_tools, diff --git a/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_actions/action_audit.rs b/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_actions/action_audit.rs index 09f5db4bb..9c397e912 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_actions/action_audit.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_actions/action_audit.rs @@ -77,6 +77,7 @@ pub(crate) fn append_agent_runtime_tool_call_record( } } +#[cfg(test)] pub(crate) fn append_agent_runtime_action_receipt( root: &Path, runtime: &AgentRuntimeState, diff --git a/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_actions/pending_confirmation_ledger.rs b/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_actions/pending_confirmation_ledger.rs index 430b67f6b..545371cda 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_actions/pending_confirmation_ledger.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_actions/pending_confirmation_ledger.rs @@ -207,20 +207,6 @@ fn agent_runtime_serialized_string_value(value: &str) -> &str { value } -pub(crate) fn agent_runtime_contains_secret_key_prefix(content: &str, prefix: &str) -> bool { - content - .match_indices(prefix) - .any(|(index, _)| agent_runtime_secret_token_end(content, index, prefix).is_some()) -} - -pub(in crate::agent) fn agent_runtime_secret_token_end( - content: &str, - index: usize, - prefix: &str, -) -> Option { - agent_runtime_secret_token_end_with_minimum(content, index, prefix, 8) -} - pub(in crate::agent) fn agent_runtime_secret_token_end_with_minimum( content: &str, index: usize, diff --git a/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_actions/response_stream_tests.rs b/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_actions/response_stream_tests.rs index 624479e44..16777f4fa 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_actions/response_stream_tests.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_actions/response_stream_tests.rs @@ -469,8 +469,8 @@ fn response_stream_finalization_commits_exactly_one_canonical_assistant() { .expect("finalize response stream assistant") { AgentBackgroundFinalizationOutcome::Completed(completed) => completed, - AgentBackgroundFinalizationOutcome::Pending(error) => { - panic!("response stream finalization remained pending: {error}") + AgentBackgroundFinalizationOutcome::Pending => { + panic!("response stream finalization remained pending") } AgentBackgroundFinalizationOutcome::Stale(blocker) => { panic!( @@ -706,7 +706,7 @@ fn response_stream_finalization_recovers_missing_stream_after_project_revision_d .expect("inject finalization interruption before stream commit"); assert!(matches!( outcome, - AgentBackgroundFinalizationOutcome::Pending(_) + AgentBackgroundFinalizationOutcome::Pending )); assert!(read_game_creator_agent_runtime_response_stream_at( root, @@ -767,9 +767,19 @@ fn response_stream_finalization_repairs_streaming_after_commit_write_failure() { .expect("finalization commit failure remains recoverable"); assert!(matches!( outcome, - AgentBackgroundFinalizationOutcome::Pending(ref error) - if error.contains("injected-response-stream-commit-failure") + AgentBackgroundFinalizationOutcome::Pending )); + let audit = std::fs::read_to_string(root.join(".agent/agent.db")) + .expect("read finalization pending audit"); + assert!(audit.lines().any(|line| { + let record: serde_json::Value = serde_json::from_str(line).expect("agent db record"); + record.get("recordType").and_then(serde_json::Value::as_str) + == Some("agent.runtime.background_task.finalization_pending") + && record + .get("error") + .and_then(serde_json::Value::as_str) + .is_some_and(|error| error.contains("injected-response-stream-commit-failure")) + })); let ready = read_game_creator_agent_runtime_response_stream_at(root, &state.agent_id, &state.run_id) .expect("read repaired ready stream") @@ -835,7 +845,7 @@ fn response_stream_committed_checkpoint_recovers_by_idempotent_cleanup() { .expect("inject committed checkpoint interruption"); assert!(matches!( outcome, - AgentBackgroundFinalizationOutcome::Pending(_) + AgentBackgroundFinalizationOutcome::Pending )); let committed_before = read_game_creator_agent_runtime_response_stream_at(root, &state.agent_id, &state.run_id) @@ -899,7 +909,7 @@ fn completed_orphan_finalization_cleanup_keeps_newer_terminal_run() { .expect("leave completed orphan finalization"); assert!(matches!( outcome, - AgentBackgroundFinalizationOutcome::Pending(_) + AgentBackgroundFinalizationOutcome::Pending )); assert!(read_game_creator_agent_runtime_finalization_journal( root, diff --git a/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_actions/tool_plan_protocol.rs b/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_actions/tool_plan_protocol.rs index 9e845a467..8a79d3feb 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_actions/tool_plan_protocol.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_actions/tool_plan_protocol.rs @@ -1,13 +1,6 @@ use super::*; -pub(crate) fn parse_game_creator_agent_tool_plan_response( - content: &str, -) -> Result { - parse_game_creator_agent_tool_plan_response_classified(content) - .map_err(|error| error.to_string()) -} - -pub(in crate::agent) fn parse_game_creator_agent_tool_plan_response_classified( +pub(crate) fn parse_game_creator_agent_tool_plan_response_classified( content: &str, ) -> Result { let stripped = strip_llm_thinking_blocks(content); diff --git a/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_driver.rs b/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_driver.rs index faec487c1..35ae78a3d 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_driver.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_driver.rs @@ -287,11 +287,9 @@ pub(crate) use entrypoints::acquire_game_creator_manifest_invalidation_event_sin #[allow(unused_imports)] pub(crate) use entrypoints::{ chat_with_game_creator_agent_at, chat_with_game_creator_role_agent_at, - chat_with_game_creator_role_agent_for_session_at, chat_with_game_creator_role_agent_runtime_at, + chat_with_game_creator_role_agent_for_session_at, chat_with_game_creator_role_agent_runtime_for_session_at, - chat_with_game_creator_role_agent_stream_at, - chat_with_game_creator_role_agent_stream_for_session_at, - configure_game_creator_manifest_invalidation_event_sink, emit_direct_game_creator_progress, + chat_with_game_creator_role_agent_stream_for_session_at, emit_direct_game_creator_progress, emit_game_creator_agent_runtime_update, emit_game_creator_manifest_invalidated, game_creator_agent_runtime_update_event, generate_local_game_draft_at, read_game_creator_agent_runtime_at, read_game_creator_agent_runtime_for_session_at, diff --git a/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_driver/entrypoints.rs b/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_driver/entrypoints.rs index 4c371d177..0efa1d1ea 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_driver/entrypoints.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_driver/entrypoints.rs @@ -215,6 +215,7 @@ pub(crate) fn start_game_creator_manifest_invalidation_event_sink( Ok(GameCreatorManifestInvalidationEventSink { port, token }) } +#[cfg(test)] pub(crate) fn configure_game_creator_manifest_invalidation_event_sink( port: u16, token: &str, @@ -578,16 +579,6 @@ pub(crate) async fn chat_with_game_creator_role_agent_for_session_at( Ok(GameCreatorChatAgentReply { reply_text }) } -pub(crate) async fn chat_with_game_creator_role_agent_runtime_at( - root: &Path, - agent_id: &str, - prompt: &str, - run_id: &str, -) -> Result<(GameCreatorChatAgentReply, AgentRuntimeState), String> { - chat_with_game_creator_role_agent_runtime_for_session_at(root, agent_id, None, prompt, run_id) - .await -} - pub(crate) async fn chat_with_game_creator_role_agent_runtime_for_session_at( root: &Path, agent_id: &str, @@ -631,19 +622,6 @@ pub(crate) async fn chat_with_game_creator_role_agent_runtime_for_session_at( } } -pub(crate) async fn chat_with_game_creator_role_agent_stream_at( - root: &Path, - agent_id: &str, - prompt: &str, - on_delta: F, -) -> Result -where - F: FnMut(&platform_llm::LlmStreamDelta), -{ - chat_with_game_creator_role_agent_stream_for_session_at(root, agent_id, None, prompt, on_delta) - .await -} - pub(crate) async fn chat_with_game_creator_role_agent_stream_for_session_at( root: &Path, agent_id: &str, diff --git a/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_driver/main_loop.rs b/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_driver/main_loop.rs index 13f70e747..aef291787 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_driver/main_loop.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_driver/main_loop.rs @@ -3600,7 +3600,7 @@ async fn run_game_creator_agent_background_task_pass_without_deadline( continuation, } } - Ok(AgentBackgroundFinalizationOutcome::Pending(_)) => { + Ok(AgentBackgroundFinalizationOutcome::Pending) => { AgentBackgroundTaskOutcome::FinalizationPending } Err(error) => { diff --git a/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol.rs b/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol.rs index b40f680ec..28542c42d 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol.rs @@ -31,14 +31,18 @@ pub(in crate::agent) use verification::*; pub(crate) use autonomous_completion::autonomous_game_build_root_run_active_at; +#[cfg(test)] pub(crate) use context_bundle::{ build_game_creator_agent_runtime_context_bundle, - continuation_from_game_creator_agent_runtime_context_bundle, - game_creator_agent_runtime_context_bundle_path, game_creator_agent_runtime_context_project_id, - persist_game_creator_agent_runtime_context, read_game_creator_agent_runtime_context_bundle, - read_game_creator_agent_runtime_context_bundle_for_idle_compaction, + game_creator_agent_runtime_context_bundle_path, write_game_creator_agent_runtime_context_bundle, }; +pub(crate) use context_bundle::{ + continuation_from_game_creator_agent_runtime_context_bundle, + game_creator_agent_runtime_context_project_id, persist_game_creator_agent_runtime_context, + read_game_creator_agent_runtime_context_bundle, + read_game_creator_agent_runtime_context_bundle_for_idle_compaction, +}; pub(crate) use context_window::{ agent_runtime_context_window_applies, sanitize_agent_runtime_context_observation, AgentRuntimeContextCheckpoint, @@ -62,7 +66,6 @@ pub(crate) use models::{ AgentRuntimeProviderActionBatchPreparation, AgentRuntimeProviderRequestSnapshot, AgentRuntimeToolAction, AgentRuntimeToolObservation, AgentRuntimeToolPlan, AgentRuntimeVerificationGate, ParsedAgentRuntimeToolPlan, - AGENT_RUNTIME_LOCK_STALE_AFTER_SECONDS, }; #[cfg(test)] pub(crate) use provider_control::mark_game_creator_agent_runtime_provider_success_handoff_needs_reconciliation_for_test; @@ -77,11 +80,14 @@ pub(crate) use response_stream::{ }; pub(crate) use run_configuration::{ agent_runtime_run_profile_identity_at, bind_game_creator_agent_runtime_run_profile_at, - game_creator_agent_runtime_project_revision_path, game_creator_agent_runtime_provider_transient_retry_policy_at, - game_creator_agent_runtime_run_profile_binding_path, read_game_creator_agent_runtime_run_profile_binding, }; +#[cfg(test)] +pub(crate) use run_configuration::{ + game_creator_agent_runtime_project_revision_path, + game_creator_agent_runtime_run_profile_binding_path, +}; pub(crate) use steering::{ acquire_game_creator_agent_runtime_steer_project_write_lock_with_wait, consume_game_creator_agent_runtime_steers, @@ -94,8 +100,9 @@ pub(crate) use steering::{ unregister_game_creator_agent_runtime_provider_request, validate_game_creator_agent_runtime_steer_notification_at, }; +#[cfg(test)] +pub(crate) use verification::game_creator_agent_runtime_verification_gate_path; pub(crate) use verification::{ - game_creator_agent_runtime_verification_gate_path, read_game_creator_agent_runtime_project_revision, read_game_creator_agent_runtime_verification_gate, write_game_creator_agent_runtime_project_revision, diff --git a/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/context_bundle.rs b/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/context_bundle.rs index f10caee1d..d81886ce5 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/context_bundle.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/context_bundle.rs @@ -1,5 +1,6 @@ use super::*; +#[cfg(test)] pub(crate) fn game_creator_agent_runtime_context_bundle_path( root: &Path, agent_id: &str, diff --git a/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/goal_contract.rs b/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/goal_contract.rs index c3a5f4471..96f7b4af6 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/goal_contract.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/goal_contract.rs @@ -79,6 +79,7 @@ fn game_creator_agent_runtime_goal_contract_relative_path( ) } +#[cfg(test)] pub(crate) fn game_creator_agent_runtime_goal_contract_path( root: &Path, root_agent_id: &str, diff --git a/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/models.rs b/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/models.rs index 559470694..dbc0c4ffa 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/models.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/models.rs @@ -1,7 +1,5 @@ use super::*; -pub(crate) const AGENT_RUNTIME_LOCK_STALE_AFTER_SECONDS: u64 = 300; - #[derive(Debug)] pub(crate) enum AgentBackgroundTaskOutcome { Finished, @@ -84,7 +82,7 @@ pub(crate) enum AgentBackgroundFinalizationOutcome { Completed(AgentRuntimeState), Cancelled(AgentRuntimeState), Stale(AgentRuntimeToolObservation), - Pending(String), + Pending, } #[derive(Clone, Copy, Debug, Eq, PartialEq)] diff --git a/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/run_configuration.rs b/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/run_configuration.rs index 7d560ad5f..6619d8d42 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/run_configuration.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/run_configuration.rs @@ -1,5 +1,6 @@ use super::*; +#[cfg(test)] pub(crate) fn game_creator_agent_runtime_project_revision_path(root: &Path) -> PathBuf { root.join(AGENT_RUNTIME_PROJECT_REVISION_RELATIVE_PATH) } @@ -61,6 +62,7 @@ pub(in crate::agent) fn game_creator_agent_runtime_run_profile_binding_relative_ ) } +#[cfg(test)] pub(crate) fn game_creator_agent_runtime_run_profile_binding_path( root: &Path, agent_id: &str, diff --git a/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/verification.rs b/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/verification.rs index f51703901..e1bd6ce89 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/verification.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/verification.rs @@ -11,6 +11,7 @@ pub(in crate::agent) fn game_creator_agent_runtime_verification_gate_relative_pa ) } +#[cfg(test)] pub(crate) fn game_creator_agent_runtime_verification_gate_path( root: &Path, agent_id: &str, diff --git a/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_state.rs b/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_state.rs index 1421d71f0..2966f46e9 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_state.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_state.rs @@ -1311,12 +1311,12 @@ where ) { let error = redact_agent_runtime_error(root, &error, 500); record_game_creator_agent_runtime_finalization_pending(root, &state, &error); - return Ok(AgentBackgroundFinalizationOutcome::Pending(error)); + return Ok(AgentBackgroundFinalizationOutcome::Pending); } if let Err(error) = checkpoint(AgentRuntimeFinalizationCheckpoint::Prepared) { let error = redact_agent_runtime_error(root, &error, 500); record_game_creator_agent_runtime_finalization_pending(root, &state, &error); - return Ok(AgentBackgroundFinalizationOutcome::Pending(error)); + return Ok(AgentBackgroundFinalizationOutcome::Pending); } match advance_game_creator_agent_runtime_finalization_at( root, @@ -1347,7 +1347,7 @@ where Err(error) => { let error = redact_agent_runtime_error(root, &error, 500); record_game_creator_agent_runtime_finalization_pending(root, &state, &error); - Ok(AgentBackgroundFinalizationOutcome::Pending(error)) + Ok(AgentBackgroundFinalizationOutcome::Pending) } } } @@ -2588,6 +2588,7 @@ pub(crate) fn try_open_game_creator_agent_runtime_task_lock_file( )) } +#[cfg(test)] pub(crate) fn game_creator_agent_runtime_task_lock_is_available( root: &Path, agent_id: &str, @@ -2596,43 +2597,6 @@ pub(crate) fn game_creator_agent_runtime_task_lock_is_available( Ok(try_open_game_creator_agent_runtime_task_lock_file(root, &relative_path)?.is_some()) } -#[derive(Debug)] -pub(crate) struct AgentRuntimeTaskLockStatus { - pub(crate) is_stale: bool, - pub(crate) belongs_to_previous_process: bool, -} - -pub(crate) fn read_game_creator_agent_runtime_lock_status( - path: &Path, -) -> AgentRuntimeTaskLockStatus { - let Ok(content) = fs::read_to_string(path) else { - return AgentRuntimeTaskLockStatus { - is_stale: true, - belongs_to_previous_process: true, - }; - }; - let Ok(value) = serde_json::from_str::(&content) else { - return AgentRuntimeTaskLockStatus { - is_stale: true, - belongs_to_previous_process: true, - }; - }; - let pid = value.get("pid").and_then(serde_json::Value::as_u64); - let created_at = value - .get("createdAt") - .and_then(serde_json::Value::as_u64) - .unwrap_or(0); - let is_stale = created_at == 0 - || unix_timestamp().saturating_sub(created_at) > AGENT_RUNTIME_LOCK_STALE_AFTER_SECONDS; - let belongs_to_previous_process = pid - .map(|pid| pid != u64::from(std::process::id())) - .unwrap_or(true); - AgentRuntimeTaskLockStatus { - is_stale, - belongs_to_previous_process, - } -} - pub(crate) fn write_game_creator_agent_runtime_state( root: &Path, state: &AgentRuntimeState, diff --git a/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_tools.rs b/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_tools.rs index 659c9acb2..a68658e4a 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_tools.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_tools.rs @@ -48,6 +48,8 @@ pub(crate) use delivery::{ build_static_delegate_result_for_child_at, wake_waiting_static_delegate_parent_run_for_test_at, }; #[cfg(test)] +pub(crate) use isolated_joins::render_isolated_join_status_batch_with_limit; +#[cfg(test)] pub(crate) use media::validate_agent_runtime_canvas_replacement_authorization_at; pub(crate) use action_history::{ @@ -57,8 +59,7 @@ pub(crate) use command_ops::{ observe_agent_runtime_limited_command, observe_agent_runtime_project_verify, }; pub(crate) use delegation::{ - observe_agent_runtime_agent_delegate, observe_agent_runtime_agent_message, - observe_agent_runtime_agent_spawn_isolated, + observe_agent_runtime_agent_message, observe_agent_runtime_agent_spawn_isolated, }; pub(crate) use delivery::{ agent_runtime_delegation_id, dispatch_isolated_agent_join_at, @@ -66,9 +67,7 @@ pub(crate) use delivery::{ reconcile_game_creator_agent_delegate_receipts_at, }; #[allow(unused_imports)] -pub(crate) use isolated_joins::{ - mark_isolated_join_claim_observed_at, render_isolated_join_status_batch, -}; +pub(crate) use isolated_joins::mark_isolated_join_claim_observed_at; #[cfg(test)] pub(crate) use media::observe_agent_runtime_platform_art_asset_generation_after_dispatch_for_test; #[allow(unused_imports)] diff --git a/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_tools/delegation.rs b/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_tools/delegation.rs index da52e00cc..0008a5602 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_tools/delegation.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_tools/delegation.rs @@ -286,37 +286,6 @@ pub(in crate::agent) fn render_static_delegate_task_contract( Ok(rendered) } -pub(crate) fn observe_agent_runtime_agent_delegate( - root: &Path, - agent_id: &str, - parent_run_id: &str, - action_id: Option<&str>, - input: &serde_json::Value, -) -> AgentRuntimeToolObservation { - let project_write_lock = match acquire_game_creator_agent_runtime_project_write_lock_with_wait( - root, - "runtime.snapshot.agent.delegate.direct", - ) { - Ok(lock) => lock, - Err(error) => { - return AgentRuntimeToolObservation { - tool: "agent.delegate".to_string(), - status: "failed".to_string(), - summary: "无法取得一致项目快照".to_string(), - detail: Some(redact_agent_runtime_project_paths(root, &error, 500)), - }; - } - }; - observe_agent_runtime_agent_delegate_at_locked( - root, - agent_id, - parent_run_id, - action_id, - input, - &project_write_lock, - ) -} - pub(crate) fn observe_agent_runtime_agent_delegate_at_locked( root: &Path, agent_id: &str, diff --git a/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_tools/isolated_joins.rs b/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_tools/isolated_joins.rs index a8b025754..140baa907 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_tools/isolated_joins.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_tools/isolated_joins.rs @@ -102,16 +102,7 @@ pub(in crate::agent) fn ready_isolated_join_status_for_parent_with_budget_at( render_isolated_join_status_batch_with_limit(&joins, max_payload_chars) } -pub(crate) fn render_isolated_join_status_batch( - joins: &[JoinDispatch], -) -> Result, String> { - render_isolated_join_status_batch_with_limit( - joins, - AGENT_RUNTIME_READY_ISOLATED_JOIN_PAYLOAD_MAX_CHARS, - ) -} - -pub(in crate::agent) fn render_isolated_join_status_batch_with_limit( +pub(crate) fn render_isolated_join_status_batch_with_limit( joins: &[JoinDispatch], max_payload_chars: usize, ) -> Result, String> { diff --git a/apps/ai-game-creator-shell/src-tauri/src/collaboration.rs b/apps/ai-game-creator-shell/src-tauri/src/collaboration.rs index 8ba87351f..96b4d8551 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/collaboration.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/collaboration.rs @@ -241,6 +241,7 @@ pub(crate) fn read_supervisor_collaboration_policy_at( normalize_supervisor_collaboration_policy(policy) } +#[cfg(test)] pub(crate) fn write_supervisor_collaboration_policy_at( root: &Path, policy: SupervisorCollaborationPolicy, @@ -308,6 +309,7 @@ fn supervisor_collaboration_policy_snapshot_lock_id( ) } +#[cfg(test)] pub(crate) fn supervisor_collaboration_policy_snapshot_path( root: &Path, parent_agent_id: &str, @@ -319,6 +321,7 @@ pub(crate) fn supervisor_collaboration_policy_snapshot_path( )) } +#[cfg(test)] pub(crate) fn supervisor_collaboration_policy_snapshot_binding_path( root: &Path, parent_agent_id: &str, diff --git a/apps/ai-game-creator-shell/src-tauri/src/command_exec.rs b/apps/ai-game-creator-shell/src-tauri/src/command_exec.rs index c03b02a91..02dd510ed 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/command_exec.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/command_exec.rs @@ -2372,6 +2372,7 @@ fn project_command_id(spec: &ProjectCommandSpec) -> String { format!("command.exec.{}.{}", spec.program, subcommand) } +#[cfg(test)] pub(crate) async fn run_project_command_at( root: &Path, program: &str, @@ -2382,6 +2383,7 @@ pub(crate) async fn run_project_command_at( run_project_command_with_output_at(root, program, arguments, cwd, timeout_seconds, None).await } +#[cfg(test)] pub(crate) async fn run_project_command_with_output_at( root: &Path, program: &str, diff --git a/apps/ai-game-creator-shell/src-tauri/src/config.rs b/apps/ai-game-creator-shell/src-tauri/src/config.rs index d6a3855c3..cfbc650b5 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/config.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/config.rs @@ -2367,7 +2367,7 @@ pub(crate) fn initialize_windows_game_creator_file_owner_for_current_user( /// Initialize ownership only for a directory that was created by the current /// operation. Existing directories must use the strict verifier instead, so a /// foreign-owned path is never silently adopted. -#[cfg(windows)] +#[cfg(all(windows, test))] pub(crate) fn initialize_windows_game_creator_directory_owner_for_current_user( path: &Path, ) -> Result<(), String> { diff --git a/apps/ai-game-creator-shell/src-tauri/src/context_compaction.rs b/apps/ai-game-creator-shell/src-tauri/src/context_compaction.rs index 0b537f9aa..5016ebab8 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/context_compaction.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/context_compaction.rs @@ -88,6 +88,7 @@ pub(crate) fn game_creator_agent_runtime_context_compaction_relative_path( ) } +#[cfg(test)] pub(crate) fn game_creator_agent_runtime_context_compaction_path( root: &Path, agent_id: &str, diff --git a/apps/ai-game-creator-shell/src-tauri/src/delegation.rs b/apps/ai-game-creator-shell/src-tauri/src/delegation.rs index 2f75bc184..e460e8f05 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/delegation.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/delegation.rs @@ -309,6 +309,7 @@ impl StaticDelegateCompletionBarrier { /// ready 后自动唤醒当前父 run」,8 分钟零事件——它在等一条只有它自己能造出来的回执。 /// main_loop 里本来就有一条专为 user_revision 写的分支(`phase=planning`、 /// `next_step=调用 agent.delegate…`),但被上游这道 park 门截胡了。 + #[cfg(test)] pub(crate) fn has_external_wait(self) -> bool { self.waiting_count > 0 || self.unknown_contract_status_count > 0 } @@ -327,6 +328,7 @@ impl StaticDelegateCompletionBarrier { } } +#[cfg(test)] pub(crate) fn new_static_delegate_delivery( parent_agent_id: &str, parent_session_id: &str, @@ -433,6 +435,7 @@ pub(crate) fn reopen_suppressed_static_delegate_repair_at( Ok(delivery) } +#[cfg(test)] pub(crate) fn mark_static_delegate_delivery_ready_at( root: &Path, child_agent_id: &str, @@ -767,6 +770,7 @@ pub(crate) fn static_delegate_run_status_may_include_receipts_at( })) } +#[cfg(test)] pub(crate) fn claim_ready_static_delegate_receipts_at( root: &Path, parent_agent_id: &str, @@ -962,43 +966,6 @@ fn select_static_delegate_receipt_batch( Ok(selected) } -pub(crate) fn mark_static_delegate_claim_observed_at( - root: &Path, - parent_agent_id: &str, - parent_run_id: &str, - action_id: &str, -) -> Result { - let claim_lock = - acquire_static_delegate_claim_lock_at(root, parent_agent_id, parent_run_id, action_id)?; - let Some(mut claim) = - read_static_delegate_claim_at(root, parent_agent_id, parent_run_id, action_id)? - else { - return Ok(false); - }; - if claim.status == StaticDelegateClaimStatus::Prepared { - let delivery_locks = acquire_static_delegate_delivery_locks_at( - root, - claim - .receipts - .iter() - .map(|receipt| receipt.delegation_id.clone()) - .collect(), - )?; - commit_static_delegate_claim_with_locks_at(root, claim, &claim_lock, delivery_locks)?; - claim = read_static_delegate_claim_at(root, parent_agent_id, parent_run_id, action_id)? - .ok_or_else(|| "静态委派 claim 在标记 observation 前消失".to_string())?; - } - if claim.status != StaticDelegateClaimStatus::Observed { - if claim.status != StaticDelegateClaimStatus::Committed { - return Err("静态委派 claim 尚未完成,不能标记 observation".to_string()); - } - claim.status = StaticDelegateClaimStatus::Observed; - claim.updated_at = unix_timestamp(); - write_static_delegate_claim_at(root, &claim)?; - } - Ok(true) -} - pub(crate) fn mark_static_delegate_claim_observed_for_receipts_at( root: &Path, parent_agent_id: &str, @@ -1049,6 +1016,22 @@ pub(crate) fn mark_static_delegate_claim_observed_for_receipts_at( Ok(true) } +#[cfg(test)] +pub(crate) fn static_delegate_claim_receipt_ids_for_test_at( + root: &Path, + parent_agent_id: &str, + parent_run_id: &str, + action_id: &str, +) -> std::collections::BTreeSet { + read_static_delegate_claim_at(root, parent_agent_id, parent_run_id, action_id) + .expect("read claim receipt ids") + .expect("claim exists") + .receipts + .into_iter() + .map(|receipt| receipt.delegation_id) + .collect() +} + fn commit_static_delegate_claim_at( root: &Path, claim: StaticDelegateClaimRecord, @@ -2938,11 +2921,17 @@ mod tests { .expect("committed claim exists"); stale.status = StaticDelegateClaimStatus::Prepared; - mark_static_delegate_claim_observed_at( + mark_static_delegate_claim_observed_for_receipts_at( &root, GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID, parent_run_id, claim_action_id, + &static_delegate_claim_receipt_ids_for_test_at( + &root, + GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID, + parent_run_id, + claim_action_id, + ), ) .expect("mark claim observed"); commit_static_delegate_claim_at(&root, stale).expect("replay stale prepared claim"); diff --git a/apps/ai-game-creator-shell/src-tauri/src/editor_adapters.rs b/apps/ai-game-creator-shell/src-tauri/src/editor_adapters.rs index 33f6b0098..d98bdfb7b 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/editor_adapters.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/editor_adapters.rs @@ -186,8 +186,13 @@ mod unity_receipt_tests { let params = json!({"projectPath":project.path().to_string_lossy(),"code":"return 2;"}); { let _busy = unity_pending_delivery().lock().unwrap(); - let result = - unity_editor_rpc_owned("execute", params.clone(), Some("busy-request")).unwrap(); + let result = managed_editor_rpc_owned( + ManagedEditor::Unity, + "execute", + params.clone(), + Some("busy-request"), + ) + .unwrap(); assert_eq!(result["status"], "failed"); assert_eq!(result["dispatched"], false); assert!(!unity_delivery_requires_ack("busy-request")); @@ -197,9 +202,13 @@ mod unity_receipt_tests { false, ) .unwrap(); - assert!( - unity_editor_rpc_owned("execute", params.clone(), Some("disabled-request")).is_err() - ); + assert!(managed_editor_rpc_owned( + ManagedEditor::Unity, + "execute", + params.clone(), + Some("disabled-request") + ) + .is_err()); assert!(!unity_delivery_requires_ack("disabled-request")); crate::builtin_plugins::set_enabled( crate::builtin_plugins::AGC_UNITY_EDITOR_PLUGIN_ID, @@ -207,7 +216,8 @@ mod unity_receipt_tests { ) .unwrap(); // 空代码在 native 发送前失败,但本次 Runner delivery 已建 fence,仍须确认回执。 - let result = unity_editor_rpc_owned( + let result = managed_editor_rpc_owned( + ManagedEditor::Unity, "execute", json!({"projectPath":project.path().to_string_lossy(),"code":""}), Some("known-failure"), @@ -217,10 +227,10 @@ mod unity_receipt_tests { assert_eq!(result["dispatched"], false); assert_eq!(result["ackRequired"], true); assert!(unity_delivery_requires_ack("known-failure")); - assert!(acknowledge_unity_editor_delivery("wrong-id").is_err()); - assert!(unity_execution_fence_path(config.path()).exists()); - acknowledge_unity_editor_delivery("known-failure").unwrap(); - assert!(!unity_execution_fence_path(config.path()).exists()); + assert!(acknowledge_editor_delivery(ManagedEditor::Unity, "wrong-id").is_err()); + assert!(editor_execution_fence_path(ManagedEditor::Unity, config.path()).exists()); + acknowledge_editor_delivery(ManagedEditor::Unity, "known-failure").unwrap(); + assert!(!editor_execution_fence_path(ManagedEditor::Unity, config.path()).exists()); *crate::game_creator_runtime_config_dir_lock() .lock() .unwrap() = previous_config; @@ -228,10 +238,10 @@ mod unity_receipt_tests { #[test] fn unity_ack_requires_complete_consistent_execution_receipt() { - assert!(unity_execute_receipt_is_valid( + assert!(editor_execute_receipt_is_valid( &json!({"status":"completed","ok":true,"dispatched":true,"retryAllowed":false,"result":null}) )); - assert!(unity_execute_receipt_is_valid( + assert!(editor_execute_receipt_is_valid( &json!({"status":"failed","ok":false,"dispatched":false,"retryAllowed":false,"error":{"code":"missing-helper","message":"no helper"}}) )); for value in [ @@ -240,7 +250,7 @@ mod unity_receipt_tests { json!({"status":"failed","ok":false,"dispatched":true,"retryAllowed":false}), json!({"status":"needs-reconciliation","ok":false,"dispatched":false,"retryAllowed":false,"error":"lost"}), ] { - assert!(!unity_execute_receipt_is_valid(&value)); + assert!(!editor_execute_receipt_is_valid(&value)); } } } diff --git a/apps/ai-game-creator-shell/src-tauri/src/editor_adapters/execution.rs b/apps/ai-game-creator-shell/src-tauri/src/editor_adapters/execution.rs index 3f9d801d6..00d7f7dcb 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/editor_adapters/execution.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/editor_adapters/execution.rs @@ -514,23 +514,7 @@ pub(crate) fn disconnect_managed_editor_project( } } -// 现役 Unity 入口共享同一实现,保留其调用方及持久文件名。 -pub(crate) fn unity_execution_fence_path(config: &Path) -> PathBuf { - editor_execution_fence_path(ManagedEditor::Unity, config) -} -pub(crate) fn unity_execute_receipt_is_valid(value: &Value) -> bool { - editor_execute_receipt_is_valid(value) -} -pub(crate) fn unity_editor_rpc_owned( - method: &str, - params: Value, - delivery_id: Option<&str>, -) -> Result { - managed_editor_rpc_owned(ManagedEditor::Unity, method, params, delivery_id) -} -pub(crate) fn acknowledge_unity_editor_delivery(id: &str) -> Result<(), String> { - acknowledge_editor_delivery(ManagedEditor::Unity, id) -} +// 现役 Unity 入口共享同一实现,保留其持久文件名。 pub(crate) fn execute_unity_editor_code(root: &Path, code: &str) -> Result { execute_managed_editor_code(ManagedEditor::Unity, root, code) } diff --git a/apps/ai-game-creator-shell/src-tauri/src/patchset.rs b/apps/ai-game-creator-shell/src-tauri/src/patchset.rs index dbe032e3e..f6dd6b086 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/patchset.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/patchset.rs @@ -30,29 +30,9 @@ pub(crate) struct ProjectPatchsetChangeSummary { } impl ProjectPatchsetChangeSummary { - pub(crate) fn operation(&self) -> &str { - &self.operation - } - pub(crate) fn path(&self) -> &str { &self.path } - - pub(crate) fn before_sha256(&self) -> Option<&str> { - self.before_sha256.as_deref() - } - - pub(crate) fn after_sha256(&self) -> Option<&str> { - self.after_sha256.as_deref() - } - - pub(crate) fn before_bytes(&self) -> u64 { - self.before_bytes - } - - pub(crate) fn after_bytes(&self) -> u64 { - self.after_bytes - } } #[derive(Clone, Debug)] @@ -70,10 +50,6 @@ impl PreparedProjectPatchset { pub(crate) fn len(&self) -> usize { self.changes.len() } - - pub(crate) fn is_empty(&self) -> bool { - self.changes.is_empty() - } } #[derive(Clone, Debug, Eq, PartialEq, Serialize)] @@ -1563,16 +1539,15 @@ mod tests { let prepared = prepare_project_patchset_at(root, &input).expect("prepare patchset"); assert_eq!(prepared.len(), 3); - assert!(!prepared.is_empty()); - assert_eq!(prepared.summaries()[0].operation(), "create"); + assert_eq!(prepared.summaries()[0].operation, "create"); assert_eq!(prepared.summaries()[1].path(), "game/main.rs"); assert_eq!( - prepared.summaries()[1].before_bytes(), + prepared.summaries()[1].before_bytes, main_before.len() as u64 ); - assert!(prepared.summaries()[1].before_sha256().is_some()); - assert!(prepared.summaries()[1].after_sha256().is_some()); - assert_eq!(prepared.summaries()[2].after_bytes(), 0); + assert!(prepared.summaries()[1].before_sha256.is_some()); + assert!(prepared.summaries()[1].after_sha256.is_some()); + assert_eq!(prepared.summaries()[2].after_bytes, 0); let applied = apply_prepared_project_patchset_at(root, &prepared).expect("apply patchset"); assert_eq!(applied.summaries(), prepared.summaries()); diff --git a/apps/ai-game-creator-shell/src-tauri/src/preview.rs b/apps/ai-game-creator-shell/src-tauri/src/preview.rs index 6a6520abe..390961292 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/preview.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/preview.rs @@ -12,6 +12,7 @@ struct PreviewServer { } impl PreviewRegistry { + #[cfg(test)] pub(crate) fn set_running( &self, preview: LocalPreviewResult, diff --git a/apps/ai-game-creator-shell/src-tauri/src/process_session/lifecycle.rs b/apps/ai-game-creator-shell/src-tauri/src/process_session/lifecycle.rs index ec5882bd2..23b7fc096 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/process_session/lifecycle.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/process_session/lifecycle.rs @@ -228,6 +228,7 @@ pub(crate) fn validate_process_session_start_preflight_at( Ok(()) } +#[cfg(test)] pub(crate) fn start_process_session_at( root: &Path, identity: ProcessSessionIdentity, diff --git a/apps/ai-game-creator-shell/src-tauri/src/project/conversation.rs b/apps/ai-game-creator-shell/src-tauri/src/project/conversation.rs index ecc03de66..bee8a9ef9 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/project/conversation.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/project/conversation.rs @@ -1080,6 +1080,7 @@ pub(crate) fn read_local_conversation_for_session_at( )) } +#[cfg(test)] pub(crate) fn read_local_conversation_at( root: &Path, agent_id: Option<&str>, @@ -1409,6 +1410,7 @@ pub(crate) fn append_local_conversation_message_for_session_idempotent_with_fina .map(|(conversation, _)| conversation) } +#[cfg(test)] pub(crate) fn append_local_conversation_message_at( root: &Path, agent_id: Option<&str>, diff --git a/apps/ai-game-creator-shell/src-tauri/src/project/external_editor_bindings.rs b/apps/ai-game-creator-shell/src-tauri/src/project/external_editor_bindings.rs index 36025909e..605774f61 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/project/external_editor_bindings.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/project/external_editor_bindings.rs @@ -44,6 +44,7 @@ pub(crate) struct ExternalEditorBindingAccess<'a> { } impl<'a> ExternalEditorBindingAccess<'a> { + #[cfg(test)] pub(crate) fn for_platform( frozen_platform_session: &'a PlatformSessionSnapshot, ) -> Result { @@ -54,6 +55,7 @@ impl<'a> ExternalEditorBindingAccess<'a> { ) } + #[cfg(test)] pub(crate) fn for_developer( api_base_url: &'a str, developer_api_key: &'a str, diff --git a/apps/ai-game-creator-shell/src-tauri/src/project/memory.rs b/apps/ai-game-creator-shell/src-tauri/src/project/memory.rs index c18ff0b8c..f00c3025f 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/project/memory.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/project/memory.rs @@ -94,28 +94,6 @@ pub(crate) fn write_local_game_memory_at( }) } -pub(crate) fn delete_local_game_memory_at( - root: &Path, - scope: &str, -) -> Result { - let (scope, path) = memory_file_path(root, scope)?; - match fs::remove_file(&path) { - Ok(()) => Ok(LocalGameMemoryResult { - scope: scope.to_string(), - path: path.to_string_lossy().into_owned(), - content: String::new(), - exists: false, - }), - Err(error) if error.kind() == std::io::ErrorKind::NotFound => Ok(LocalGameMemoryResult { - scope: scope.to_string(), - path: path.to_string_lossy().into_owned(), - content: String::new(), - exists: false, - }), - Err(error) => Err(format!("删除记忆失败:{}: {error}", path.display())), - } -} - pub(crate) fn memory_file_path<'a>( root: &Path, scope: &'a str, diff --git a/apps/ai-game-creator-shell/src-tauri/src/project/verification.rs b/apps/ai-game-creator-shell/src-tauri/src/project/verification.rs index 3a358c46e..87578c306 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/project/verification.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/project/verification.rs @@ -287,6 +287,7 @@ fn project_verification_package_manager_at( Ok("npm") } +#[cfg(test)] pub(crate) fn resolve_project_verification_spec_at( root: &Path, script: &str, @@ -671,6 +672,7 @@ where }) } +#[cfg(test)] pub(crate) async fn run_project_verification_at( root: &Path, script: &str, diff --git a/apps/ai-game-creator-shell/src-tauri/src/runner/client.rs b/apps/ai-game-creator-shell/src-tauri/src/runner/client.rs index 6889aad89..f67bf93aa 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/runner/client.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/runner/client.rs @@ -15,7 +15,6 @@ use std::time::{Duration, Instant}; const AGENT_RUNNER_LOG_FILE_NAME: &str = "agent-runner.log"; const AGENT_RUNNER_LOG_INPUT_LINE_MAX_BYTES: usize = 8 * 1024; const AGENT_RUNNER_LOG_OUTPUT_MAX_CHARS: usize = 1_024; -const AGENT_RUNNER_CLIENT_EXIT_TIMEOUT: Duration = Duration::from_secs(15); #[derive(Default)] pub(super) struct ExternalAgentRunnerGuiOwnerAttachmentState { @@ -664,45 +663,6 @@ fn wait_for_external_agent_runner_boot_exit( } } -fn read_external_agent_runner_endpoint_for_shutdown( - config_dir: &Path, -) -> Result, String> { - let endpoint_path = external_agent_runner_endpoint_path(config_dir); - let lock_path = external_agent_runner_lock_path(config_dir); - let deadline = Instant::now() + EXTERNAL_AGENT_RUNNER_START_TIMEOUT; - loop { - match fs::symlink_metadata(&endpoint_path) { - Ok(metadata) if metadata.file_type().is_symlink() => { - return Err("Agent Runner endpoint 不允许符号链接".to_string()); - } - Ok(_) => { - let endpoint = read_external_agent_runner_endpoint(&endpoint_path)?; - return Ok(Some((endpoint_path, endpoint))); - } - Err(error) if error.kind() == io::ErrorKind::NotFound => { - if let Some(lock) = - try_open_external_agent_runner_lock(&lock_path, "Agent Runner 单实例锁")? - { - drop(lock); - return Ok(None); - } - if Instant::now() >= deadline { - return Err( - "Agent Runner 启动锁仍被占用,但 endpoint 未在期限内就绪".to_string() - ); - } - thread::sleep(Duration::from_millis(50)); - } - Err(error) => { - return Err(format!( - "读取 Agent Runner endpoint 元数据失败:{}: {error}", - endpoint_path.display() - )); - } - } - } -} - pub(super) fn shutdown_external_agent_runner_if_idle_at(config_dir: &Path) -> Result { let endpoint_path = external_agent_runner_endpoint_path(config_dir); let lock_path = external_agent_runner_lock_path(config_dir); @@ -1346,56 +1306,6 @@ fn attach_registered_external_agent_runner_gui_owner_if_needed( ) } -pub(super) fn shutdown_external_agent_runner_for_client_exit_at( - config_dir: &Path, -) -> Result { - let Some((endpoint_path, endpoint)) = - read_external_agent_runner_endpoint_for_shutdown(config_dir)? - else { - return Ok(true); - }; - let request_id = random_identifier(b"genarrative-agent-runner-client-exit-request-id")?; - let result = match send_external_agent_runner_request_with_protocol_and_id( - &endpoint, - endpoint.protocol_version, - request_id, - "runner.shutdown_for_client_exit", - ExternalAgentRunnerRequestParams::default(), - ) { - Ok(result) => result, - Err(error) => { - return match read_external_agent_runner_endpoint(&endpoint_path) { - Ok(current) if current.boot_id == endpoint.boot_id => Err(error), - _ => Ok(true), - }; - } - }; - let accepted = result - .get("accepted") - .and_then(Value::as_bool) - .ok_or_else(|| "Agent Runner shutdown_for_client_exit 响应缺少 accepted".to_string())?; - let busy = result - .get("busy") - .and_then(Value::as_bool) - .ok_or_else(|| "Agent Runner shutdown_for_client_exit 响应缺少 busy".to_string())?; - let will_shutdown = result - .get("willShutdown") - .and_then(Value::as_bool) - .ok_or_else(|| "Agent Runner shutdown_for_client_exit 响应缺少 willShutdown".to_string())?; - match (accepted, busy, will_shutdown) { - (false, true, false) => return Ok(false), - (true, false, true) => {} - _ => return Err("Agent Runner shutdown_for_client_exit 响应状态不一致".to_string()), - } - wait_for_external_agent_runner_boot_exit( - &endpoint_path, - &endpoint, - AGENT_RUNNER_CLIENT_EXIT_TIMEOUT, - "Agent Runner 未在客户端退出期限内停止", - )?; - Ok(true) -} - /// 窗口退出的收尾:先释放本窗口参与锁,再决定 Runner 是否需要关闭。 /// /// 返回 `Ok(false)` 表示仍检测到其它窗口持有参与锁,Runner 必须保留给它们; diff --git a/apps/ai-game-creator-shell/src-tauri/src/runner/dispatch.rs b/apps/ai-game-creator-shell/src-tauri/src/runner/dispatch.rs index 831fa14af..1bb9fcce0 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/runner/dispatch.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/runner/dispatch.rs @@ -95,14 +95,7 @@ pub(super) fn external_agent_runner_request_agent( Ok(agent.to_string()) } -pub(super) fn apply_external_agent_runner_gui_owner_platform_session( - state: &ExternalAgentRunnerServerState, - params: &ExternalAgentRunnerRequestParams, -) -> Result<(), String> { - apply_external_agent_runner_gui_owner_attachment(state, params, None) -} - -fn apply_external_agent_runner_gui_owner_attachment( +pub(super) fn apply_external_agent_runner_gui_owner_attachment( state: &ExternalAgentRunnerServerState, params: &ExternalAgentRunnerRequestParams, event_sink: Option, @@ -837,55 +830,6 @@ pub(super) fn dispatch_external_agent_runner_runtime_request_with_owner_claim( }), ) } - "runner.shutdown_for_client_exit" if cfg!(test) => { - if state.shutdown_requested.load(Ordering::Acquire) { - ExternalAgentRunnerResponse::success( - &request.request_id, - json!({ "accepted": true, "busy": false, "willShutdown": true }), - ) - } else if state - .draining - .compare_exchange(false, true, Ordering::AcqRel, Ordering::Acquire) - .is_err() - { - ExternalAgentRunnerResponse::failure( - &request.request_id, - "runner-draining", - "Agent Runner 已在排空", - ) - } else if state.active_connections.load(Ordering::Acquire) > 1 { - state.draining.store(false, Ordering::Release); - ExternalAgentRunnerResponse::success( - &request.request_id, - json!({ "accepted": false, "busy": true, "willShutdown": false }), - ) - } else { - match external_agent_runner_known_roots_are_idle(state) { - Ok(false) => { - state.draining.store(false, Ordering::Release); - ExternalAgentRunnerResponse::success( - &request.request_id, - json!({ "accepted": false, "busy": true, "willShutdown": false }), - ) - } - Ok(true) => { - state.shutdown_requested.store(true, Ordering::Release); - ExternalAgentRunnerResponse::success( - &request.request_id, - json!({ "accepted": true, "busy": false, "willShutdown": true }), - ) - } - Err(error) => { - state.draining.store(false, Ordering::Release); - ExternalAgentRunnerResponse::failure( - &request.request_id, - "runtime-state-unreadable", - redact_runner_secret(&error, &token), - ) - } - } - } - } "runner.shutdown_if_idle" | "shutdown_if_idle" => { if request.params.root.is_some() { match external_agent_runner_request_root(request) { @@ -1146,7 +1090,6 @@ pub(super) fn handle_external_agent_runner_request( | "platform.session.clear" | "runner.shutdown" | "shutdown" - | "runner.shutdown_for_client_exit" | "runner.shutdown_if_idle" | "shutdown_if_idle" => dispatch_external_agent_runner_runtime_request(&request, state), _ => ExternalAgentRunnerResponse::failure( diff --git a/apps/ai-game-creator-shell/src-tauri/src/runner/tests.rs b/apps/ai-game-creator-shell/src-tauri/src/runner/tests.rs index 08c03c365..4f372d1f3 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/runner/tests.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/runner/tests.rs @@ -216,92 +216,6 @@ fn legacy_runner_force_migration_requires_authenticated_exact_ping_identity() { server.join().expect("join mismatched identity fixture"); } -#[test] -fn client_exit_client_returns_busy_without_waiting_and_accepts_idle_shutdown() { - let directory = unique_test_directory(); - let config_dir = private_runner_test_config_dir(&directory); - let endpoint_path = external_agent_runner_endpoint_path(&config_dir); - let token = "client-exit-response-token-client-exit-response-token"; - - let busy_listener = TcpListener::bind(SocketAddrV4::new(Ipv4Addr::LOCALHOST, 0)) - .expect("bind busy client-exit fixture"); - let busy_endpoint = test_endpoint( - token, - "client-exit-busy-response-boot", - busy_listener - .local_addr() - .expect("busy fixture address") - .port(), - ); - write_external_agent_runner_endpoint_atomic(&endpoint_path, &busy_endpoint) - .expect("write busy client-exit endpoint"); - let busy_server = std::thread::spawn(move || { - let (mut stream, _) = busy_listener.accept().expect("accept busy client exit"); - let payload = read_external_agent_runner_frame(&mut stream).expect("read busy client exit"); - let request = serde_json::from_slice::(&payload) - .expect("parse busy client exit"); - assert_eq!(request.method, "runner.shutdown_for_client_exit"); - let response = ExternalAgentRunnerResponse::success( - &request.request_id, - json!({ "accepted": false, "busy": true, "willShutdown": false }), - ); - write_external_agent_runner_frame( - &mut stream, - &serde_json::to_vec(&response).expect("serialize busy client-exit response"), - ) - .expect("write busy client-exit response"); - }); - - let started = Instant::now(); - assert!( - !shutdown_external_agent_runner_for_client_exit_at(&config_dir) - .expect("busy client exit remains a successful refusal") - ); - assert!( - started.elapsed() < Duration::from_secs(2), - "busy client exit must not wait for Runner boot shutdown" - ); - busy_server.join().expect("join busy client-exit fixture"); - - let idle_listener = TcpListener::bind(SocketAddrV4::new(Ipv4Addr::LOCALHOST, 0)) - .expect("bind idle client-exit fixture"); - let idle_endpoint = test_endpoint( - token, - "client-exit-idle-response-boot", - idle_listener - .local_addr() - .expect("idle fixture address") - .port(), - ); - write_external_agent_runner_endpoint_atomic(&endpoint_path, &idle_endpoint) - .expect("write idle client-exit endpoint"); - let idle_endpoint_path = endpoint_path.clone(); - let idle_server = std::thread::spawn(move || { - let (mut stream, _) = idle_listener.accept().expect("accept idle client exit"); - let payload = read_external_agent_runner_frame(&mut stream).expect("read idle client exit"); - let request = serde_json::from_slice::(&payload) - .expect("parse idle client exit"); - assert_eq!(request.method, "runner.shutdown_for_client_exit"); - let response = ExternalAgentRunnerResponse::success( - &request.request_id, - json!({ "accepted": true, "busy": false, "willShutdown": true }), - ); - write_external_agent_runner_frame( - &mut stream, - &serde_json::to_vec(&response).expect("serialize idle client-exit response"), - ) - .expect("write idle client-exit response"); - drop(stream); - fs::remove_file(idle_endpoint_path).expect("remove idle endpoint after shutdown response"); - }); - - assert!( - shutdown_external_agent_runner_for_client_exit_at(&config_dir) - .expect("idle client exit must complete Runner shutdown") - ); - idle_server.join().expect("join idle client-exit fixture"); -} - #[test] fn endpoint_shape_accepts_legacy_missing_fingerprint_but_rejects_malformed_values() { let endpoint = test_endpoint( @@ -919,7 +833,7 @@ fn gui_owner_platform_session_payload_clears_runner_session() { "runner-token-a", "https://dev.genarrative.world", ); - apply_external_agent_runner_gui_owner_platform_session( + apply_external_agent_runner_gui_owner_attachment( &state, &ExternalAgentRunnerRequestParams { gui_owner_epoch: Some(owner.owner_epoch.clone()), @@ -928,6 +842,7 @@ fn gui_owner_platform_session_payload_clears_runner_session() { platform_auth_revision: Some(2), ..ExternalAgentRunnerRequestParams::default() }, + None, ) .expect("explicit logged-out payload clears Runner session"); assert_eq!(crate::current_platform_session(), None); @@ -948,7 +863,7 @@ fn gui_owner_partial_platform_session_payload_fails_without_mutation() { "https://dev.genarrative.world", ); let before = crate::current_platform_session(); - let error = apply_external_agent_runner_gui_owner_platform_session( + let error = apply_external_agent_runner_gui_owner_attachment( &state, &ExternalAgentRunnerRequestParams { gui_owner_epoch: Some(owner.owner_epoch.clone()), @@ -959,6 +874,7 @@ fn gui_owner_partial_platform_session_payload_fails_without_mutation() { platform_auth_revision: Some(2), ..ExternalAgentRunnerRequestParams::default() }, + None, ) .expect_err("partial platform session must fail closed"); assert!(error.contains("参数不完整"), "{error}"); @@ -980,7 +896,7 @@ fn new_gui_owner_epoch_replaces_higher_generation_runner_session_and_rejects_old ); let owner_a = acquire_test_gui_participant(&config_dir, 0); let owner_a_epoch = owner_a.owner_epoch.clone(); - apply_external_agent_runner_gui_owner_platform_session( + apply_external_agent_runner_gui_owner_attachment( &state, &ExternalAgentRunnerRequestParams { gui_owner_epoch: Some(owner_a_epoch.clone()), @@ -992,12 +908,13 @@ fn new_gui_owner_epoch_replaces_higher_generation_runner_session_and_rejects_old platform_auth_revision: Some(10), ..ExternalAgentRunnerRequestParams::default() }, + None, ) .expect("old GUI installs high-generation owner A"); drop(owner_a); let owner_b = acquire_test_gui_participant(&config_dir, 0); - apply_external_agent_runner_gui_owner_platform_session( + apply_external_agent_runner_gui_owner_attachment( &state, &ExternalAgentRunnerRequestParams { gui_owner_epoch: Some(owner_b.owner_epoch.clone()), @@ -1009,6 +926,7 @@ fn new_gui_owner_epoch_replaces_higher_generation_runner_session_and_rejects_old platform_auth_revision: Some(1), ..ExternalAgentRunnerRequestParams::default() }, + None, ) .expect("new GUI epoch replaces higher-generation old owner"); assert_eq!( @@ -1017,7 +935,7 @@ fn new_gui_owner_epoch_replaces_higher_generation_runner_session_and_rejects_old Some(("runner-owner-b".to_string(), 1)) ); - let stale_error = apply_external_agent_runner_gui_owner_platform_session( + let stale_error = apply_external_agent_runner_gui_owner_attachment( &state, &ExternalAgentRunnerRequestParams { gui_owner_epoch: Some(owner_a_epoch), @@ -1029,6 +947,7 @@ fn new_gui_owner_epoch_replaces_higher_generation_runner_session_and_rejects_old platform_auth_revision: Some(11), ..ExternalAgentRunnerRequestParams::default() }, + None, ) .expect_err("old GUI epoch must not overwrite the current owner"); assert!(stale_error.contains("claim 已过期"), "{stale_error}"); @@ -1053,7 +972,7 @@ fn durable_claim_revision_change_clears_runner_session_and_blocks_runtime_until_ "runner-token-seed", "https://dev.genarrative.world", ); - apply_external_agent_runner_gui_owner_platform_session( + apply_external_agent_runner_gui_owner_attachment( &state, &ExternalAgentRunnerRequestParams { gui_owner_epoch: Some(owner.owner_epoch.clone()), @@ -1065,6 +984,7 @@ fn durable_claim_revision_change_clears_runner_session_and_blocks_runtime_until_ platform_auth_revision: Some(8), ..ExternalAgentRunnerRequestParams::default() }, + None, ) .expect("attach owner A claim"); state.gui_owner_attached.store(true, Ordering::Release); @@ -1077,7 +997,7 @@ fn durable_claim_revision_change_clears_runner_session_and_blocks_runtime_until_ "claim mismatch must isolate the platform session without stopping a live GUI owner" ); assert_eq!(crate::current_platform_session(), None); - apply_external_agent_runner_gui_owner_platform_session( + apply_external_agent_runner_gui_owner_attachment( &state, &ExternalAgentRunnerRequestParams { gui_owner_epoch: Some(owner.owner_epoch.clone()), @@ -1089,6 +1009,7 @@ fn durable_claim_revision_change_clears_runner_session_and_blocks_runtime_until_ platform_auth_revision: Some(1), ..ExternalAgentRunnerRequestParams::default() }, + None, ) .expect("current durable claim reattaches owner B"); validate_external_agent_runner_gui_owner_claim_current(&state) @@ -1482,7 +1403,7 @@ fn second_window_attach_with_same_claim_keeps_runner_platform_session() { "https://dev.genarrative.world", ); let owner = acquire_test_gui_participant(&config_dir, 0); - apply_external_agent_runner_gui_owner_platform_session( + apply_external_agent_runner_gui_owner_attachment( &state, &ExternalAgentRunnerRequestParams { gui_owner_epoch: Some(owner.owner_epoch.clone()), @@ -1494,6 +1415,7 @@ fn second_window_attach_with_same_claim_keeps_runner_platform_session() { platform_auth_revision: Some(7), ..ExternalAgentRunnerRequestParams::default() }, + None, ) .expect("first window installs its session"); assert_eq!( @@ -1503,13 +1425,14 @@ fn second_window_attach_with_same_claim_keeps_runner_platform_session() { ); // 第二个窗口启动时本身还没有登录态:同 claim 的 attach 只能是空操作。 - apply_external_agent_runner_gui_owner_platform_session( + apply_external_agent_runner_gui_owner_attachment( &state, &ExternalAgentRunnerRequestParams { gui_owner_epoch: Some(owner.owner_epoch.clone()), gui_owner_session_revision: Some(0), ..ExternalAgentRunnerRequestParams::default() }, + None, ) .expect("second window attaches with the same claim"); assert_eq!( @@ -2367,178 +2290,6 @@ fn forced_shutdown_is_accepted_even_when_runtime_is_busy() { assert!(state.shutdown_requested.load(Ordering::Acquire)); } -#[test] -fn shutdown_for_client_exit_rejects_busy_then_closes_idle_runner_idempotently() { - let directory = unique_test_directory(); - let root = directory.0.join("project"); - let pending = root.join(".agent/runtime/pending-actions/code-prototype/run-client-exit.json"); - fs::create_dir_all(pending.parent().expect("pending parent")) - .expect("create pending directory"); - let durable_bytes = br#"{"durable":true}"#; - fs::write(&pending, durable_bytes).expect("write pending action"); - let token = "client-exit-private-token-client-exit-private-token"; - let state = ExternalAgentRunnerServerState::new( - directory.0.join(EXTERNAL_AGENT_RUNNER_ENDPOINT_FILE_NAME), - test_endpoint(token, "client-exit-boot-id", 32326), - ); - state.remember_root(&root); - - let unauthorized_response = handle_external_agent_runner_request( - ExternalAgentRunnerRequest { - protocol_version: EXTERNAL_AGENT_RUNNER_PROTOCOL_VERSION, - request_id: "shutdown-client-exit-unauthorized".to_string(), - token: "wrong-client-exit-private-token".to_string(), - method: "runner.shutdown_for_client_exit".to_string(), - params: ExternalAgentRunnerRequestParams::default(), - }, - &state, - ); - assert!(!unauthorized_response.ok); - assert_eq!( - unauthorized_response - .error - .as_ref() - .map(|error| error.code.as_str()), - Some("unauthorized") - ); - assert!(!state.shutdown_requested.load(Ordering::Acquire)); - assert!(!state.draining.load(Ordering::Acquire)); - assert_eq!( - fs::read(&pending).expect("read pending action after rejected shutdown"), - durable_bytes - ); - - let idle_response = handle_external_agent_runner_request( - ExternalAgentRunnerRequest { - protocol_version: EXTERNAL_AGENT_RUNNER_PROTOCOL_VERSION, - request_id: "shutdown-client-exit-idle-check".to_string(), - token: token.to_string(), - method: "runner.shutdown_if_idle".to_string(), - params: ExternalAgentRunnerRequestParams::default(), - }, - &state, - ); - assert!(idle_response.ok); - assert_eq!( - idle_response - .result - .as_ref() - .and_then(|value| value["idle"].as_bool()), - Some(false) - ); - assert!(!state.shutdown_requested.load(Ordering::Acquire)); - assert!(!state.draining.load(Ordering::Acquire)); - - let busy_response = handle_external_agent_runner_request( - ExternalAgentRunnerRequest { - protocol_version: EXTERNAL_AGENT_RUNNER_PROTOCOL_VERSION, - request_id: "shutdown-client-exit-busy-1".to_string(), - token: token.to_string(), - method: "runner.shutdown_for_client_exit".to_string(), - params: ExternalAgentRunnerRequestParams::default(), - }, - &state, - ); - assert!(busy_response.ok); - assert_eq!( - busy_response - .result - .as_ref() - .and_then(|value| value["accepted"].as_bool()), - Some(false) - ); - assert_eq!( - busy_response - .result - .as_ref() - .and_then(|value| value["busy"].as_bool()), - Some(true) - ); - assert_eq!( - busy_response - .result - .as_ref() - .and_then(|value| value["willShutdown"].as_bool()), - Some(false) - ); - assert!(!state.shutdown_requested.load(Ordering::Acquire)); - assert!(!state.draining.load(Ordering::Acquire)); - assert_eq!( - fs::read(&pending).expect("read pending action"), - durable_bytes - ); - - fs::remove_file(&pending).expect("clear pending action before idle client exit"); - let shutdown_response = handle_external_agent_runner_request( - ExternalAgentRunnerRequest { - protocol_version: EXTERNAL_AGENT_RUNNER_PROTOCOL_VERSION, - request_id: "shutdown-client-exit-idle-1".to_string(), - token: token.to_string(), - method: "runner.shutdown_for_client_exit".to_string(), - params: ExternalAgentRunnerRequestParams::default(), - }, - &state, - ); - assert!(shutdown_response.ok); - assert_eq!( - shutdown_response - .result - .as_ref() - .and_then(|value| value["accepted"].as_bool()), - Some(true) - ); - assert_eq!( - shutdown_response - .result - .as_ref() - .and_then(|value| value["busy"].as_bool()), - Some(false) - ); - assert_eq!( - shutdown_response - .result - .as_ref() - .and_then(|value| value["willShutdown"].as_bool()), - Some(true) - ); - assert!(state.shutdown_requested.load(Ordering::Acquire)); - assert!(state.draining.load(Ordering::Acquire)); - - let repeated_response = handle_external_agent_runner_request( - ExternalAgentRunnerRequest { - protocol_version: EXTERNAL_AGENT_RUNNER_PROTOCOL_VERSION, - request_id: "shutdown-client-exit-force-2".to_string(), - token: token.to_string(), - method: "runner.shutdown_for_client_exit".to_string(), - params: ExternalAgentRunnerRequestParams::default(), - }, - &state, - ); - assert!(repeated_response.ok); - assert_eq!( - repeated_response - .result - .as_ref() - .and_then(|value| value["accepted"].as_bool()), - Some(true) - ); - assert_eq!( - repeated_response - .result - .as_ref() - .and_then(|value| value["busy"].as_bool()), - Some(false) - ); - assert_eq!( - repeated_response - .result - .as_ref() - .and_then(|value| value["willShutdown"].as_bool()), - Some(true) - ); - assert!(!pending.exists()); -} - #[test] fn durable_tool_plan_handoff_prevents_shutdown_even_when_corrupt() { let directory = unique_test_directory(); diff --git a/apps/ai-game-creator-shell/src-tauri/src/tests/collaboration/claims.rs b/apps/ai-game-creator-shell/src-tauri/src/tests/collaboration/claims.rs index fa3e6a0bb..70989a77a 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/tests/collaboration/claims.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/tests/collaboration/claims.rs @@ -463,7 +463,7 @@ fn project_supervisor_legacy_isolated_claim_is_replayed_before_ready_prefix() { let second_legacy_join = joins.pop().expect("second highest sorted legacy join"); assert_eq!(joins.len(), 16); assert!( - render_isolated_join_status_batch(&joins).is_err(), + render_isolated_join_status_batch_with_limit(&joins, 10_000).is_err(), "lower ready joins must exceed one complete observation payload" ); let first_legacy_action_id = "project-supervisor-legacy-isolated-original-action-a"; @@ -947,11 +947,17 @@ fn project_supervisor_mixed_claim_recovers_isolated_result_after_static_lock_fai first_action_id, ) .expect("mark recovered isolated claim observed")); - assert!(mark_static_delegate_claim_observed_at( + assert!(mark_static_delegate_claim_observed_for_receipts_at( &root, GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID, parent_run_id, recovery_action_id, + &static_delegate_claim_receipt_ids_for_test_at( + &root, + GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID, + parent_run_id, + recovery_action_id + ), ) .expect("mark recovered static claim observed")); assert!(isolated_join_completion_barrier_at( diff --git a/apps/ai-game-creator-shell/src-tauri/src/tests/collaboration/recovery.rs b/apps/ai-game-creator-shell/src-tauri/src/tests/collaboration/recovery.rs index 0a47af5b6..894b303cc 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/tests/collaboration/recovery.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/tests/collaboration/recovery.rs @@ -1223,11 +1223,17 @@ fn claimed_static_delivery_ignores_republished_terminal_projection() { ) .expect("claim ready receipt"); assert_eq!(receipts.len(), 1); - mark_static_delegate_claim_observed_at( + mark_static_delegate_claim_observed_for_receipts_at( &root, GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID, parent_run_id, "project-supervisor-claimed-replay-claim", + &static_delegate_claim_receipt_ids_for_test_at( + &root, + GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID, + parent_run_id, + "project-supervisor-claimed-replay-claim", + ), ) .expect("observe claim"); let claimed_before = read_static_delegate_delivery_at(&root, &delegation_id) 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 d59f7fa1d..f3a42bf2d 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 @@ -844,11 +844,17 @@ fn project_supervisor_static_delivery_transitions_and_claims_idempotently() { .expect("committed claim barrier"); assert!(!committed.is_clear()); assert_eq!(committed.unobserved_claim_count, 1); - assert!(mark_static_delegate_claim_observed_at( + assert!(mark_static_delegate_claim_observed_for_receipts_at( &root, GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID, "project-supervisor-delivery-run", "project-supervisor-claim-action", + &static_delegate_claim_receipt_ids_for_test_at( + &root, + GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID, + "project-supervisor-delivery-run", + "project-supervisor-claim-action" + ), ) .expect("mark claim observation persisted")); let clear = static_delegate_completion_barrier_at( @@ -1130,11 +1136,17 @@ fn project_supervisor_legacy_ready_delivery_stays_byte_stable_until_claimed() { .expect("claim legacy receipt"); assert_eq!(receipts.len(), 1); assert_eq!(receipts[0].structured_result, None); - mark_static_delegate_claim_observed_at( + mark_static_delegate_claim_observed_for_receipts_at( &root, GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID, "project-supervisor-legacy-run", "project-supervisor-legacy-claim", + &static_delegate_claim_receipt_ids_for_test_at( + &root, + GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID, + "project-supervisor-legacy-run", + "project-supervisor-legacy-claim", + ), ) .expect("observe legacy claim"); let barrier = static_delegate_completion_barrier_at( @@ -1215,11 +1227,17 @@ fn project_supervisor_static_delegate_repair_is_single_bounded_wave() { "project-supervisor-weak-claim", ) .expect("claim weak receipt"); - mark_static_delegate_claim_observed_at( + mark_static_delegate_claim_observed_for_receipts_at( &root, GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID, "project-supervisor-repair-run", "project-supervisor-weak-claim", + &static_delegate_claim_receipt_ids_for_test_at( + &root, + GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID, + "project-supervisor-repair-run", + "project-supervisor-weak-claim", + ), ) .expect("observe weak claim"); let weak_barrier = static_delegate_completion_barrier_at( @@ -1369,11 +1387,17 @@ fn project_supervisor_static_delegate_repair_is_single_bounded_wave() { "project-supervisor-repair-claim", ) .expect("claim repair receipt"); - mark_static_delegate_claim_observed_at( + mark_static_delegate_claim_observed_for_receipts_at( &root, GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID, "project-supervisor-repair-run", "project-supervisor-repair-claim", + &static_delegate_claim_receipt_ids_for_test_at( + &root, + GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID, + "project-supervisor-repair-run", + "project-supervisor-repair-claim", + ), ) .expect("observe repair claim"); let nested_error = validate_static_delegate_repair_request_at( @@ -1483,11 +1507,17 @@ fn project_supervisor_failed_static_delivery_cannot_finalize_before_repair_settl "project-supervisor-failed-original-claim", ) .expect("claim failed original"); - mark_static_delegate_claim_observed_at( + mark_static_delegate_claim_observed_for_receipts_at( &root, GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID, parent_run_id, "project-supervisor-failed-original-claim", + &static_delegate_claim_receipt_ids_for_test_at( + &root, + GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID, + parent_run_id, + "project-supervisor-failed-original-claim", + ), ) .expect("observe failed original"); @@ -1576,11 +1606,17 @@ fn project_supervisor_failed_static_delivery_cannot_finalize_before_repair_settl "project-supervisor-failed-repair-claim", ) .expect("claim repair"); - mark_static_delegate_claim_observed_at( + mark_static_delegate_claim_observed_for_receipts_at( &root, GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID, parent_run_id, "project-supervisor-failed-repair-claim", + &static_delegate_claim_receipt_ids_for_test_at( + &root, + GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID, + parent_run_id, + "project-supervisor-failed-repair-claim", + ), ) .expect("observe repair"); assert!(static_delegate_completion_barrier_at( @@ -1669,11 +1705,17 @@ fn project_supervisor_concurrent_repair_dispatch_creates_exactly_one_delivery() "project-supervisor-concurrent-original-claim", ) .expect("claim original"); - mark_static_delegate_claim_observed_at( + mark_static_delegate_claim_observed_for_receipts_at( &root, GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID, parent_run_id, "project-supervisor-concurrent-original-claim", + &static_delegate_claim_receipt_ids_for_test_at( + &root, + GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID, + parent_run_id, + "project-supervisor-concurrent-original-claim", + ), ) .expect("observe original claim"); @@ -2517,11 +2559,17 @@ fn project_supervisor_claimed_contract_catalog_precedes_normal_status_for_two_de ) .expect("claim both catalog deliveries"); assert_eq!(receipts.len(), 2); - assert!(mark_static_delegate_claim_observed_at( + assert!(mark_static_delegate_claim_observed_for_receipts_at( &root, GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID, parent_run_id, claim_action_id, + &static_delegate_claim_receipt_ids_for_test_at( + &root, + GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID, + parent_run_id, + claim_action_id + ), ) .expect("observe catalog claim")); @@ -2657,11 +2705,17 @@ fn project_supervisor_claimed_contract_query_is_exact_scoped_and_drives_repair() .len(), 1 ); - assert!(mark_static_delegate_claim_observed_at( + assert!(mark_static_delegate_claim_observed_for_receipts_at( &root, GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID, parent_run_id, claim_action_id, + &static_delegate_claim_receipt_ids_for_test_at( + &root, + GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID, + parent_run_id, + claim_action_id + ), ) .expect("observe original claim")); let durable = read_static_delegate_delivery_at(&root, original_delegation_id) @@ -3044,11 +3098,17 @@ fn project_supervisor_run_status_uses_durable_claim_when_agent_db_audit_fails() .as_deref() .is_some_and(|detail| detail.contains("durable 设计结论"))); } - assert!(mark_static_delegate_claim_observed_at( + assert!(mark_static_delegate_claim_observed_for_receipts_at( &root, GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID, parent_run_id, claim_action_id, + &static_delegate_claim_receipt_ids_for_test_at( + &root, + GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID, + parent_run_id, + claim_action_id + ), ) .expect("mark durable claim observed")); assert!(static_delegate_completion_barrier_at( diff --git a/apps/ai-game-creator-shell/src-tauri/src/tests/goal.rs b/apps/ai-game-creator-shell/src-tauri/src/tests/goal.rs index 869a5e6fd..9f48b95ba 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/tests/goal.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/tests/goal.rs @@ -859,7 +859,7 @@ fn agent_goal_finalization_v4_treats_new_revision_as_stale_before_assistant_writ .expect("prepare Goal finalization v4"); assert!(matches!( outcome, - AgentBackgroundFinalizationOutcome::Pending(_) + AgentBackgroundFinalizationOutcome::Pending )); let prepared = read_game_creator_agent_runtime_finalization_journal(&root, "design-director", run_id) diff --git a/apps/ai-game-creator-shell/src-tauri/src/tests/mod.rs b/apps/ai-game-creator-shell/src-tauri/src/tests/mod.rs index 621e24906..421d32605 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/tests/mod.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/tests/mod.rs @@ -16,6 +16,29 @@ const MANIFEST_INVALIDATION_RELAY_TEST_ACCEPT_TIMEOUT: Duration = Duration::from const MANIFEST_INVALIDATION_RELAY_TEST_PAYLOAD_TIMEOUT: Duration = Duration::from_millis(500); const MANIFEST_INVALIDATION_RELAY_TEST_MAX_BYTES: usize = 64 * 1024; +// 委派测试显式持有与正式 action 执行链相同的项目写锁,随后调用现役核心。 +fn observe_agent_runtime_agent_delegate( + root: &Path, + agent_id: &str, + parent_run_id: &str, + action_id: Option<&str>, + input: &serde_json::Value, +) -> AgentRuntimeToolObservation { + let project_lock = acquire_game_creator_agent_runtime_project_write_lock_with_wait( + root, + "runtime.snapshot.agent.delegate.test", + ) + .expect("acquire project write lock for delegate test"); + observe_agent_runtime_agent_delegate_at_locked( + root, + agent_id, + parent_run_id, + action_id, + input, + &project_lock, + ) +} + fn read_manifest_invalidation_relay_payload_with_deadline( listener: &TcpListener, ) -> io::Result> { diff --git a/apps/ai-game-creator-shell/src-tauri/src/tests/project_tools.rs b/apps/ai-game-creator-shell/src-tauri/src/tests/project_tools.rs index 8d75193af..9835bbeed 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/tests/project_tools.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/tests/project_tools.rs @@ -169,8 +169,7 @@ fn finalization_resume_recovers_real_assistant_append_checkpoint_once() { .expect("checkpoint injection is a recoverable outcome"); assert!(matches!( outcome, - AgentBackgroundFinalizationOutcome::Pending(ref error) - if error.contains("injected-assistant-checkpoint-crash") + AgentBackgroundFinalizationOutcome::Pending )); let journal = read_game_creator_agent_runtime_finalization_journal(&root, "design-director", run_id) @@ -181,6 +180,10 @@ fn finalization_resume_recovers_real_assistant_append_checkpoint_once() { .expect("read interrupted finalization runtime") .state; assert_ne!(interrupted.phase, "completed"); + assert!(interrupted + .error + .as_deref() + .is_some_and(|error| error.contains("injected-assistant-checkpoint-crash"))); let conversation = read_local_conversation_for_session_at( &root, Some("design-director"), @@ -314,8 +317,7 @@ fn finalization_resume_cleans_completed_checkpoint_without_duplicate_projections .expect("completed checkpoint injection is recoverable"); assert!(matches!( outcome, - AgentBackgroundFinalizationOutcome::Pending(ref error) - if error.contains("injected-runtime-completed-checkpoint-crash") + AgentBackgroundFinalizationOutcome::Pending )); let before = read_game_creator_agent_runtime_at(&root, "design-director") .expect("read completed checkpoint runtime"); @@ -332,6 +334,13 @@ fn finalization_resume_cleans_completed_checkpoint_without_duplicate_projections .filter(|event| event.run_id == run_id && event.event_type == "response") .count(); let before_records = read_agent_db_records_for_test(&root); + assert!(before_records.iter().any(|record| { + record["recordType"] == "agent.runtime.background_task.finalization_pending" + && record["runId"] == run_id + && record["error"] + .as_str() + .is_some_and(|error| error.contains("injected-runtime-completed-checkpoint-crash")) + })); let before_runtime_completed = before_records .iter() .filter(|record| { diff --git a/apps/ai-game-creator-shell/src-tauri/src/tests/provider.rs b/apps/ai-game-creator-shell/src-tauri/src/tests/provider.rs index 4b1bde139..8bbe0cb74 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/tests/provider.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/tests/provider.rs @@ -343,9 +343,13 @@ fn role_agent_chat_request_applies_per_agent_web_search_true_and_false() { }}"# )); - let (llm, config_path, request) = - build_game_creator_role_agent_chat_request(&root, "art-director", "核对角色联网开关") - .expect("build role chat request"); + let (llm, config_path, request) = build_game_creator_role_agent_chat_request_for_session( + &root, + "art-director", + None, + "核对角色联网开关", + ) + .expect("build role chat request"); assert_eq!(config_path, "agentLlm.art-director"); assert_eq!(llm.web_search_enabled, agent_enabled); @@ -406,9 +410,10 @@ async fn chat_with_game_creator_role_agent_stream_emits_deltas() { )); let mut deltas = Vec::new(); - let reply = chat_with_game_creator_role_agent_stream_at( + let reply = chat_with_game_creator_role_agent_stream_for_session_at( &root, "art-director", + None, "我要一个主角设定", |delta| { deltas.push(( @@ -472,9 +477,10 @@ async fn chat_with_game_creator_role_agent_stream_keeps_completed_reply_after_ba )); let mut deltas = Vec::new(); - let reply = chat_with_game_creator_role_agent_stream_at( + let reply = chat_with_game_creator_role_agent_stream_for_session_at( &root, "art-director", + None, "给我一个完整回复", |delta| deltas.push(delta.clone()), ) @@ -528,9 +534,10 @@ async fn chat_with_game_creator_role_agent_stream_falls_back_once_before_first_d )); let mut deltas = Vec::new(); - let result = chat_with_game_creator_role_agent_stream_at( + let result = chat_with_game_creator_role_agent_stream_for_session_at( &root, "art-director", + None, "验证首包协议错误回退", |delta| deltas.push(delta.clone()), ) @@ -587,9 +594,10 @@ async fn chat_with_game_creator_role_agent_web_search_stream_never_falls_back() }}"# )); - let result = chat_with_game_creator_role_agent_stream_at( + let result = chat_with_game_creator_role_agent_stream_for_session_at( &root, "art-director", + None, "验证联网流式协议错误不回退", |_| {}, ) @@ -641,9 +649,10 @@ async fn chat_with_game_creator_role_agent_stream_does_not_fallback_on_upstream_ }}"# )); - let result = chat_with_game_creator_role_agent_stream_at( + let result = chat_with_game_creator_role_agent_stream_for_session_at( &root, "art-director", + None, "验证 403 不回退", |_| {}, ) @@ -7084,7 +7093,7 @@ fn agent_tool_plan_parser_uses_first_complete_json_object_before_explanation() { let content = format!("{plan_json}\n补充解释:后面的示例对象 {{\"ignored\":true}} 不属于工具计划。"); - let plan = parse_game_creator_agent_tool_plan_response(&content) + let plan = parse_game_creator_agent_tool_plan_response_classified(&content) .expect("trailing explanation must not cause a trailing characters error"); assert_eq!(plan.thinking_summary, thinking_summary); @@ -7110,7 +7119,7 @@ fn agent_tool_plan_parser_rejects_protocol_violations() { for content in invalid_plans { assert!( - parse_game_creator_agent_tool_plan_response(&content).is_err(), + parse_game_creator_agent_tool_plan_response_classified(&content).is_err(), "invalid tool plan should be rejected: {content}" ); } diff --git a/apps/ai-game-creator-shell/src-tauri/src/tests/response_stream.rs b/apps/ai-game-creator-shell/src-tauri/src/tests/response_stream.rs index 4ea9eda6f..acbf3f28b 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/tests/response_stream.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/tests/response_stream.rs @@ -64,7 +64,7 @@ fn structured_plan_finalization_without_readable_runtime_state_needs_reconciliat .expect("prepare finalization journal without assistant"); assert!(matches!( outcome, - AgentBackgroundFinalizationOutcome::Pending(_) + AgentBackgroundFinalizationOutcome::Pending )); let before = read_local_conversation_for_session_at( &root, @@ -1034,8 +1034,8 @@ fn background_finalization_rechecks_stale_credential_before_assistant_persistenc AgentBackgroundFinalizationOutcome::Completed(_) => { panic!("stale credential must not complete the run") } - AgentBackgroundFinalizationOutcome::Pending(error) => { - panic!("stale credential must not enter finalization: {error}") + AgentBackgroundFinalizationOutcome::Pending => { + panic!("stale credential must not enter finalization") } AgentBackgroundFinalizationOutcome::Cancelled(_) => { panic!("stale credential recheck must not cancel the run") @@ -1104,8 +1104,8 @@ fn background_finalization_honors_existing_cancel_before_any_completion_write() blocker.summary ) } - AgentBackgroundFinalizationOutcome::Pending(error) => { - panic!("existing cancel must not leave finalization pending: {error}") + AgentBackgroundFinalizationOutcome::Pending => { + panic!("existing cancel must not leave finalization pending") } }; assert_eq!(cancelled.status, "cancelled"); @@ -1334,8 +1334,7 @@ fn finalization_prepared_lifecycle_failure_remains_recoverable() { .expect("prepared lifecycle failure must be recoverable"); assert!(matches!( outcome, - AgentBackgroundFinalizationOutcome::Pending(ref error) - if error.contains("agent.runtime.finalization.lifecycle") + AgentBackgroundFinalizationOutcome::Pending )); let journal = read_game_creator_agent_runtime_finalization_journal(&root, "design-director", run_id) @@ -1347,6 +1346,10 @@ fn finalization_prepared_lifecycle_failure_remains_recoverable() { .state; assert_eq!(pending.status, "running"); assert_eq!(pending.phase, "finalizing"); + assert!(pending + .error + .as_deref() + .is_some_and(|error| error.contains("agent.runtime.finalization.lifecycle"))); let before_resume = read_local_conversation_for_session_at( &root, Some("design-director"), @@ -1450,8 +1453,8 @@ fn finalization_critical_audits_complete_at_the_ordinary_capacity_boundary() { blocker.summary ) } - AgentBackgroundFinalizationOutcome::Pending(error) => { - panic!("capacity boundary must not leave finalization pending: {error}") + AgentBackgroundFinalizationOutcome::Pending => { + panic!("capacity boundary must not leave finalization pending") } AgentBackgroundFinalizationOutcome::Cancelled(_) => { panic!("capacity boundary must not cancel finalization") @@ -1596,7 +1599,7 @@ fn finalization_resume_recovers_persisted_assistant_without_runtime_state() { .expect("assistant checkpoint injection is recoverable"); assert!(matches!( outcome, - AgentBackgroundFinalizationOutcome::Pending(_) + AgentBackgroundFinalizationOutcome::Pending )); fs::remove_file(root.join(".agent/runtime/agents/design-director.json")) .expect("remove runtime state projection"); @@ -1695,7 +1698,7 @@ fn finalization_resume_recovers_interrupted_sidecar_replace_backup() { .expect("assistant checkpoint injection is recoverable"); assert!(matches!( outcome, - AgentBackgroundFinalizationOutcome::Pending(_) + AgentBackgroundFinalizationOutcome::Pending )); let journal_path = game_creator_agent_runtime_finalization_path(&root, "design-director", run_id); @@ -1788,7 +1791,7 @@ fn finalization_journal_accepts_maximum_multibyte_reply() { .expect("maximum legal reply must fit finalization journal"); assert!(matches!( outcome, - AgentBackgroundFinalizationOutcome::Pending(_) + AgentBackgroundFinalizationOutcome::Pending )); let journal = read_game_creator_agent_runtime_finalization_journal(&root, "design-director", run_id) @@ -1833,9 +1836,15 @@ fn finalization_resume_persists_prepared_reply_without_llm_replay() { .expect("prepared checkpoint injection is recoverable"); assert!(matches!( outcome, - AgentBackgroundFinalizationOutcome::Pending(ref error) - if error.contains("injected-prepared-checkpoint-crash") + AgentBackgroundFinalizationOutcome::Pending )); + let pending = read_game_creator_agent_runtime_at(&root, "design-director") + .expect("read prepared checkpoint pending runtime") + .state; + assert!(pending + .error + .as_deref() + .is_some_and(|error| error.contains("injected-prepared-checkpoint-crash"))); let before = read_local_conversation_for_session_at( &root, Some("design-director"), @@ -1922,7 +1931,7 @@ fn finalization_resume_discards_unpersisted_reply_after_revision_drift() { .expect("stale prepared checkpoint injection is recoverable"); assert!(matches!( outcome, - AgentBackgroundFinalizationOutcome::Pending(_) + AgentBackgroundFinalizationOutcome::Pending )); assert_eq!( advance_project_revision_for_test( @@ -2021,7 +2030,7 @@ fn finalization_resume_drops_prepared_reply_after_run_is_cancelled() { .expect("prepared checkpoint injection is recoverable"); assert!(matches!( outcome, - AgentBackgroundFinalizationOutcome::Pending(_) + AgentBackgroundFinalizationOutcome::Pending )); let cancelled = cancel_game_creator_agent_runtime_task_at(&root, "design-director", run_id) .expect("cancel interrupted finalization"); @@ -2093,7 +2102,7 @@ fn finalization_cancel_completes_reply_already_persisted_to_conversation() { .expect("assistant checkpoint injection is recoverable"); assert!(matches!( outcome, - AgentBackgroundFinalizationOutcome::Pending(_) + AgentBackgroundFinalizationOutcome::Pending )); let completed = cancel_game_creator_agent_runtime_task_at(&root, "design-director", run_id) @@ -2163,7 +2172,7 @@ fn finalization_restart_applies_cancel_before_uncommitted_assistant() { .expect("prepared restart cancellation checkpoint"); assert!(matches!( outcome, - AgentBackgroundFinalizationOutcome::Pending(_) + AgentBackgroundFinalizationOutcome::Pending )); write_game_creator_agent_runtime_cancel_request( &root, @@ -2230,7 +2239,7 @@ fn finalization_restart_finishes_committed_assistant_before_late_cancel() { .expect("assistant restart cancellation checkpoint"); assert!(matches!( outcome, - AgentBackgroundFinalizationOutcome::Pending(_) + AgentBackgroundFinalizationOutcome::Pending )); write_game_creator_agent_runtime_cancel_request( &root, @@ -2305,7 +2314,7 @@ fn finalization_resume_blocks_corrupt_journal_without_llm_replay() { .expect("prepared checkpoint injection is recoverable"); assert!(matches!( outcome, - AgentBackgroundFinalizationOutcome::Pending(_) + AgentBackgroundFinalizationOutcome::Pending )); let journal_path = game_creator_agent_runtime_finalization_path(&root, "design-director", run_id); @@ -2378,7 +2387,7 @@ fn finalization_resume_blocks_tampered_journal_identity() { .expect("prepared checkpoint injection is recoverable"); assert!(matches!( outcome, - AgentBackgroundFinalizationOutcome::Pending(_) + AgentBackgroundFinalizationOutcome::Pending )); let journal_path = game_creator_agent_runtime_finalization_path(&root, "design-director", run_id); @@ -2453,7 +2462,7 @@ fn finalization_resume_blocks_internally_consistent_incomplete_plan_snapshot() { .expect("prepared checkpoint injection is recoverable"); assert!(matches!( outcome, - AgentBackgroundFinalizationOutcome::Pending(_) + AgentBackgroundFinalizationOutcome::Pending )); let journal_path = @@ -2555,7 +2564,7 @@ fn finalization_resume_rejects_symlinked_journal() { .expect("prepared checkpoint injection is recoverable"); assert!(matches!( outcome, - AgentBackgroundFinalizationOutcome::Pending(_) + AgentBackgroundFinalizationOutcome::Pending )); let journal_path = game_creator_agent_runtime_finalization_path(&root, "design-director", run_id); @@ -2937,8 +2946,8 @@ async fn stale_finalization_context_survives_restart_before_same_run_replanning( AgentBackgroundFinalizationOutcome::Completed(_) => { panic!("stale final reply must not complete before restart") } - AgentBackgroundFinalizationOutcome::Pending(error) => { - panic!("stale final reply must not enter finalization: {error}") + AgentBackgroundFinalizationOutcome::Pending => { + panic!("stale final reply must not enter finalization") } AgentBackgroundFinalizationOutcome::Cancelled(_) => { panic!("stale final reply must not cancel before restart") @@ -4320,8 +4329,8 @@ fn structured_plan_incomplete_normal_and_resumed_finalization_audits_are_redacte AgentBackgroundFinalizationOutcome::Completed(_) => { panic!("ordinary incomplete plan must not complete finalization") } - AgentBackgroundFinalizationOutcome::Pending(error) => { - panic!("ordinary incomplete plan must block before journal: {error}") + AgentBackgroundFinalizationOutcome::Pending => { + panic!("ordinary incomplete plan must block before journal") } AgentBackgroundFinalizationOutcome::Cancelled(_) => { panic!("ordinary incomplete plan must not cancel finalization") @@ -4413,7 +4422,7 @@ fn structured_plan_incomplete_normal_and_resumed_finalization_audits_are_redacte .expect("prepare resumable finalization journal"); assert!(matches!( prepared, - AgentBackgroundFinalizationOutcome::Pending(_) + AgentBackgroundFinalizationOutcome::Pending )); let before_resume = read_local_conversation_for_session_at( &resumed_root, @@ -4721,11 +4730,17 @@ fn project_supervisor_suppressed_repair_keeps_finalization_blocked() { "project-supervisor-suppressed-original-claim", ) .expect("claim original"); - mark_static_delegate_claim_observed_at( + mark_static_delegate_claim_observed_for_receipts_at( &root, GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID, parent_run_id, "project-supervisor-suppressed-original-claim", + &static_delegate_claim_receipt_ids_for_test_at( + &root, + GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID, + parent_run_id, + "project-supervisor-suppressed-original-claim", + ), ) .expect("observe original claim"); let repair_action_id = "project-supervisor-suppressed-repair-action"; @@ -4882,11 +4897,17 @@ fn project_supervisor_unobserved_claim_blocks_finalization_until_observed() { .as_deref() .is_some_and(|detail| detail.contains("unobservedReceiptClaims=1"))); - assert!(mark_static_delegate_claim_observed_at( + assert!(mark_static_delegate_claim_observed_for_receipts_at( &root, GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID, parent_run_id, claim_action_id, + &static_delegate_claim_receipt_ids_for_test_at( + &root, + GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID, + parent_run_id, + claim_action_id + ), ) .expect("mark claim observation")); assert!(static_delegate_completion_blocker_at( diff --git a/apps/ai-game-creator-shell/src-tauri/src/tests/runtime_actions/planning_strategy/autonomous_game_build.rs b/apps/ai-game-creator-shell/src-tauri/src/tests/runtime_actions/planning_strategy/autonomous_game_build.rs index c4e2351d5..daff27707 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/tests/runtime_actions/planning_strategy/autonomous_game_build.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/tests/runtime_actions/planning_strategy/autonomous_game_build.rs @@ -4,16 +4,16 @@ use crate::{ append_unique_game_creator_agent_runtime_pending_task, autonomous_game_build_root_run_active_at, bind_supervisor_collaboration_policy_snapshot_at, build_static_delegate_structured_result_at, claim_ready_static_delegate_receipts_at, - create_or_read_static_delegate_delivery_at, mark_static_delegate_claim_observed_at, - mark_static_delegate_delivery_ready_at, mark_static_delegate_delivery_ready_with_result_at, - new_game_creation_app_seed_tasks, new_static_delegate_delivery, - new_static_delegate_delivery_with_contract, observe_agent_runtime_run_status, - record_command_run, record_preview_state, + create_or_read_static_delegate_delivery_at, + mark_static_delegate_claim_observed_for_receipts_at, mark_static_delegate_delivery_ready_at, + mark_static_delegate_delivery_ready_with_result_at, new_game_creation_app_seed_tasks, + new_static_delegate_delivery, new_static_delegate_delivery_with_contract, + observe_agent_runtime_run_status, record_command_run, record_preview_state, refresh_agent_runtime_autonomous_convergence_snapshot_after_provider_at, start_game_creator_supervisor_background_task_for_session_at, - static_delegate_completion_barrier_at, validate_agent_runtime_autonomous_plan_liveness, - GameCreationAppCommandRunState, GameCreationAppCommandRunStatus, GameCreationAppPreviewStatus, - StaticDelegateContractStatus, + static_delegate_claim_receipt_ids_for_test_at, static_delegate_completion_barrier_at, + validate_agent_runtime_autonomous_plan_liveness, GameCreationAppCommandRunState, + GameCreationAppCommandRunStatus, GameCreationAppPreviewStatus, StaticDelegateContractStatus, }; use sha2::{Digest as _, Sha256}; @@ -1232,11 +1232,17 @@ async fn assert_autonomous_repair_waits_for_receipt_observation_for_test(unobser .expect("claim needs-repair receipt"); assert_eq!(claimed.len(), 1); if !unobserved_claim { - assert!(mark_static_delegate_claim_observed_at( + assert!(mark_static_delegate_claim_observed_for_receipts_at( &root, GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID, &run_id, &claim_action_id, + &static_delegate_claim_receipt_ids_for_test_at( + &root, + GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID, + &run_id, + &claim_action_id + ), ) .expect("observe needs-repair claim")); let ready = new_static_delegate_delivery( diff --git a/apps/ai-game-creator-shell/src-tauri/src/tests/runtime_actions/policy.rs b/apps/ai-game-creator-shell/src-tauri/src/tests/runtime_actions/policy.rs index 015087ef8..8eadb479e 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/tests/runtime_actions/policy.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/tests/runtime_actions/policy.rs @@ -1894,18 +1894,6 @@ fn local_permission_log_rejects_unknown_event_and_command() { } #[test] fn pending_tool_action_identity_binds_task_context_and_occurrence() { - assert!(!agent_runtime_contains_secret_key_prefix( - "design-task-create-policy-run", - "sk-" - )); - let secret_like = format!( - r#"{{"apiKey":"{}"}}"#, - ["s", "k-test-secret-value"].concat() - ); - assert!(agent_runtime_contains_secret_key_prefix( - &secret_like, - "sk-" - )); let action = AgentRuntimeToolAction { tool: "canvas.asset_generate".to_string(), reason: Some("生成角色规范图".to_string()), diff --git a/apps/ai-game-creator-shell/src-tauri/src/tests/runtime_actions/support.rs b/apps/ai-game-creator-shell/src-tauri/src/tests/runtime_actions/support.rs index 99478e8b1..36e81afb5 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/tests/runtime_actions/support.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/tests/runtime_actions/support.rs @@ -14,12 +14,13 @@ pub(super) use super::super::{ append_auto_tool_action_audit_pair_for_test, assert_auto_tool_action_audit_pair, assert_pending_runtime_decision_revalidates_after_lock, assert_task_status, fake_llm_game_draft, final_tool_plan_response, mock_http_request_json, - native_agent_tool_plan_chat_response, pending_tool_action_for_test, - persist_needs_reconciliation_runtime_for_test, persist_project_verification_for_test, - read_agent_db_records_for_test, register_canvas_visual_asset_fixture, - spawn_barrier_mock_llm_server, spawn_interruptible_mock_llm_server_with_capture, - spawn_mock_llm_raw_responses_with_capture, spawn_mock_llm_server, - spawn_mock_llm_server_responses, spawn_mock_llm_server_responses_with_capture, + native_agent_tool_plan_chat_response, observe_agent_runtime_agent_delegate, + pending_tool_action_for_test, persist_needs_reconciliation_runtime_for_test, + persist_project_verification_for_test, read_agent_db_records_for_test, + register_canvas_visual_asset_fixture, spawn_barrier_mock_llm_server, + spawn_interruptible_mock_llm_server_with_capture, spawn_mock_llm_raw_responses_with_capture, + spawn_mock_llm_server, spawn_mock_llm_server_responses, + spawn_mock_llm_server_responses_with_capture, spawn_releasable_mock_llm_server_responses_with_capture, spawn_releasable_mock_llm_server_responses_with_capture_at, start_agent_runtime_steer_fixture, ui_prototype_assessment_fixture, unique_project_path, use_test_runtime_config_dir, @@ -34,11 +35,10 @@ pub(super) use crate::{ advance_game_creator_agent_runtime_turn_at, agent_runtime_action_receipt_public_safe_detail_for_test, agent_runtime_action_receipt_safe_detail_for_owner_for_test, - agent_runtime_background_worker_threads_for_test, agent_runtime_contains_secret_key_prefix, - agent_runtime_executable_tools, agent_runtime_read_only_delivery_completion_plan_update, - agent_runtime_run_profile_identity_at, agent_runtime_tool_action_fingerprint, - agent_runtime_tool_action_id, agent_runtime_tool_allowed_for_agent, - agent_runtime_tool_policy_snapshot_for_run_at, + agent_runtime_background_worker_threads_for_test, agent_runtime_executable_tools, + agent_runtime_read_only_delivery_completion_plan_update, agent_runtime_run_profile_identity_at, + agent_runtime_tool_action_fingerprint, agent_runtime_tool_action_id, + agent_runtime_tool_allowed_for_agent, agent_runtime_tool_policy_snapshot_for_run_at, agent_runtime_tool_requires_pending_revision_gate, agent_runtime_tool_requires_repository_context_fingerprint_gate, agent_runtime_verified_delivery_completion_plan_update, append_agent_db_record, @@ -67,9 +67,8 @@ pub(super) use crate::{ game_creator_agent_runtime_tool_policy_rule_for_run, init_local_game_project_at, invalidate_agent_runtime_project_verification_after_preview_failure_at, native_runtime_function_name, observe_agent_runtime_action_history, - observe_agent_runtime_agent_delegate, observe_agent_runtime_agent_message, - observe_agent_runtime_agent_spawn_isolated, plan_game_creation_agent_pass, - prepare_agent_runtime_project_mutation_locked, + observe_agent_runtime_agent_message, observe_agent_runtime_agent_spawn_isolated, + plan_game_creation_agent_pass, prepare_agent_runtime_project_mutation_locked, prepare_game_creator_agent_runtime_provider_action_batch, project_verification_completion_blocker_at, read_all_game_creator_agent_runtime_tasks, read_game_creator_agent_runtime_at, read_game_creator_agent_runtime_context_bundle, @@ -107,9 +106,8 @@ pub(super) use crate::{ AGENT_RUNTIME_AUTONOMOUS_PRE_MUTATION_LOOP_LIMIT, AGENT_RUNTIME_AUTONOMOUS_SCAFFOLD_MAX_OUTPUT_TOKENS, AGENT_RUNTIME_AUTONOMOUS_TRUNCATED_SCAFFOLD_MAX_OUTPUT_TOKENS, - AGENT_RUNTIME_BACKGROUND_LOOP_LIMIT, AGENT_RUNTIME_LOCK_STALE_AFTER_SECONDS, - AGENT_RUNTIME_PENDING_ACTION_SCHEMA_VERSION, AGENT_RUNTIME_PENDING_ACTION_STATUS_APPROVED, - AGENT_RUNTIME_PENDING_ACTION_STATUS_EXECUTING, + AGENT_RUNTIME_BACKGROUND_LOOP_LIMIT, AGENT_RUNTIME_PENDING_ACTION_SCHEMA_VERSION, + AGENT_RUNTIME_PENDING_ACTION_STATUS_APPROVED, AGENT_RUNTIME_PENDING_ACTION_STATUS_EXECUTING, AGENT_RUNTIME_PENDING_ACTION_STATUS_OBSERVED_APPROVED, AGENT_RUNTIME_PLAN_STATUS_COMPLETED, AGENT_RUNTIME_RESPOND_FUNCTION_NAME, AGENT_RUNTIME_RUN_PROFILE_AUTONOMOUS_GAME_BUILD, AGENT_RUNTIME_RUN_PROFILE_STANDARD, AGENT_RUNTIME_SCHEMA_VERSION, diff --git a/apps/ai-game-creator-shell/src-tauri/src/tests/runtime_actions/task_lifecycle.rs b/apps/ai-game-creator-shell/src-tauri/src/tests/runtime_actions/task_lifecycle.rs index 6362eb2bd..09cf300b6 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/tests/runtime_actions/task_lifecycle.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/tests/runtime_actions/task_lifecycle.rs @@ -93,8 +93,7 @@ fn agent_runtime_does_not_reclaim_stale_lock_from_live_process() { "agentId": "design-director", "pid": 1, "token": "live-process-token", - "createdAt": unix_timestamp() - .saturating_sub(AGENT_RUNTIME_LOCK_STALE_AFTER_SECONDS + 1), + "createdAt": 1, }) .to_string(), ) diff --git a/apps/ai-game-creator-shell/src-tauri/src/tests/runtime_state.rs b/apps/ai-game-creator-shell/src-tauri/src/tests/runtime_state.rs index ef7d00ac7..6f8862b23 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/tests/runtime_state.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/tests/runtime_state.rs @@ -333,55 +333,6 @@ fn parallel_jsonl_appends_keep_records_line_delimited() { fs::remove_dir_all(root).ok(); } -#[test] -fn agent_runtime_lock_status_keeps_fresh_same_process_locks_busy() { - let root = unique_project_path(); - let lock_path = root.join(".agent/runtime/locks/design-director.lock"); - fs::create_dir_all(lock_path.parent().expect("lock parent")).expect("lock dir"); - fs::write( - &lock_path, - serde_json::json!({ - "agentId": "design-director", - "pid": std::process::id(), - "createdAt": unix_timestamp(), - }) - .to_string(), - ) - .expect("write lock"); - - let status = read_game_creator_agent_runtime_lock_status(&lock_path); - - assert!(!status.is_stale); - assert!(!status.belongs_to_previous_process); - - fs::remove_dir_all(root).ok(); -} - -#[test] -fn agent_runtime_lock_status_marks_old_previous_process_locks_stale() { - let root = unique_project_path(); - let lock_path = root.join(".agent/runtime/locks/design-director.lock"); - fs::create_dir_all(lock_path.parent().expect("lock parent")).expect("lock dir"); - fs::write( - &lock_path, - serde_json::json!({ - "agentId": "design-director", - "pid": u64::from(std::process::id()) + 1000, - "createdAt": unix_timestamp() - .saturating_sub(AGENT_RUNTIME_LOCK_STALE_AFTER_SECONDS + 1), - }) - .to_string(), - ) - .expect("write lock"); - - let status = read_game_creator_agent_runtime_lock_status(&lock_path); - - assert!(status.is_stale); - assert!(status.belongs_to_previous_process); - - fs::remove_dir_all(root).ok(); -} - #[test] fn agent_runtime_system_lock_allows_only_one_owner() { let root = unique_project_path(); @@ -483,7 +434,7 @@ fn missing_runtime_state_stays_idle_without_a_run_profile_binding() { #[test] fn structured_plan_update_validates_and_advances_monotonically() { - let parsed = parse_game_creator_agent_tool_plan_response( + let parsed = parse_game_creator_agent_tool_plan_response_classified( &serde_json::json!({ "thinkingSummary": "先建立可恢复计划", "planUpdate": { @@ -502,7 +453,7 @@ fn structured_plan_update_validates_and_advances_monotonically() { .expect("parse structured plan"); assert!(parsed.plan_update.is_some()); - let legacy = parse_game_creator_agent_tool_plan_response( + let legacy = parse_game_creator_agent_tool_plan_response_classified( &serde_json::json!({ "thinkingSummary": "旧 Provider fallback", "plan": ["旧步骤"], diff --git a/apps/ai-game-creator-shell/src-tauri/src/tests/sessions.rs b/apps/ai-game-creator-shell/src-tauri/src/tests/sessions.rs index 64426bcb8..50d29fc09 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/tests/sessions.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/tests/sessions.rs @@ -18,9 +18,10 @@ async fn role_agent_runtime_turn_persists_session_events_and_index() { }}"# )); - let (reply, runtime) = chat_with_game_creator_role_agent_runtime_at( + let (reply, runtime) = chat_with_game_creator_role_agent_runtime_for_session_at( &root, "art-director", + None, "我要生成主角图", "runtime-test-run", ) @@ -788,7 +789,7 @@ fn generate_local_game_draft_appends_short_and_long_memory() { } #[test] -fn local_game_memory_can_read_write_and_delete_long_memory() { +fn local_game_memory_can_read_and_write_long_memory() { let root = unique_project_path(); let missing = read_local_game_memory_at(&root, "long").expect("read missing memory"); @@ -804,15 +805,11 @@ fn local_game_memory_can_read_write_and_delete_long_memory() { assert_eq!(read.scope, "long"); assert_eq!(read.content, "# 项目长期记忆\n"); - let deleted = delete_local_game_memory_at(&root, "long").expect("delete memory"); - assert!(!deleted.exists); - assert!(!root.join("memory/project.md").exists()); - fs::remove_dir_all(root).ok(); } #[test] -fn local_game_memory_can_read_write_and_delete_blackboard_memory() { +fn local_game_memory_can_read_and_write_blackboard_memory() { let root = unique_project_path(); let written = @@ -824,10 +821,6 @@ fn local_game_memory_can_read_write_and_delete_blackboard_memory() { assert_eq!(read.scope, "blackboard"); assert_eq!(read.content, "# 项目黑板\n"); - let deleted = delete_local_game_memory_at(&root, "blackboard").expect("delete memory"); - assert!(!deleted.exists); - assert!(!root.join(PROJECT_BLACKBOARD_MEMORY_PATH).exists()); - fs::remove_dir_all(root).ok(); } diff --git a/apps/ai-game-creator-shell/src-tauri/src/ui_editor/workflow.rs b/apps/ai-game-creator-shell/src-tauri/src/ui_editor/workflow.rs index 520ae77bb..12c5d58fb 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/ui_editor/workflow.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/ui_editor/workflow.rs @@ -155,6 +155,7 @@ struct ResolvedWorkflowPage { ui_asset: GameCreationAppAssetManifestEntry, } +#[cfg(test)] pub(crate) async fn run_ui_workflow_at( root: &Path, input: UiWorkflowRunInput, diff --git a/docs/project-memory/todos/【待解决】主要构建入口编译警告清单-2026-09-23.md b/docs/project-memory/todos/【待解决】主要构建入口编译警告清单-2026-09-23.md index 518324e8f..6a9c80389 100644 --- a/docs/project-memory/todos/【待解决】主要构建入口编译警告清单-2026-09-23.md +++ b/docs/project-memory/todos/【待解决】主要构建入口编译警告清单-2026-09-23.md @@ -7,7 +7,7 @@ ## 1. 基线与范围 -首次诊断的基线提交为 `016356e509f11a1a638ce45ed51b9e40ef3e36a2`,诊断时工作树干净。环境为 Windows x64、Rust `1.98.1`、Node `v24.15.0`、npm `12.0.2`。第 2~6 节和附录保留首次诊断快照;后续处理状态及契约核查见第 7~9 节,不能将附录的全部条目视为仍未解决。 +首次诊断的基线提交为 `016356e509f11a1a638ce45ed51b9e40ef3e36a2`,诊断时工作树干净。环境为 Windows x64、Rust `1.98.1`、Node `v24.15.0`、npm `12.0.2`。第 2~6 节和附录保留首次诊断快照;后续处理状态及契约核查见第 7~14 节,不能将附录的全部条目视为仍未解决。 AGC Rust 使用默认 features、dev profile;237 是本轮编译器诊断数,不代表 237 个独立根因,也不是所有平台、features 和 test targets 的总数。另有 5 条不带常规源码 span 的 ts-rs 宏提示,不计入 237。 @@ -403,6 +403,159 @@ W192 的外层别名删除后,首次编译暴露出 `codex_app_server/mod.rs` - `check-config.mjs`、定向 rustfmt、编码检查、文档索引检查与 `git diff --check` 通过。单元测试构建自身输出 384 条 warning,属于测试 target 的独立口径,不计入上述默认构建 120 条。 - 未运行跨平台构建、完整 Rust 测试集或 GUI 联调;本批不扩展处理其余测试 / 平台用途项与 3 项待澄清契约。 +## 13. 原 74 条测试用途项复核(2026-09-23) + +### 结论与边界 + +基线为合并最新 master 后的 `e1b900885`。第 8 节原始 74 条 `tests_only` 诊断,均能逐字对应合并后默认 Windows dev 编译日志中的 120 条 warning。本节逐项复核生产调用、测试消费者、条件构建与当前契约;不把第 10 节从 86 条中重新归类的测试 / 平台项混入这 74 条,也不因测试引用自动保留旧实现。 + +| 复核分类 | 条数 | 建议 | +| --- | ---: | --- | +| 测试辅助 | 42 | 保留现役行为测试;包装、观测方法、fixture 和测试重导出加精确 `cfg(test)` 或移入测试模块 | +| 测试处置待逐项核定 | 20 | 先核对实际断言和现役合同:旧行为或重复覆盖直接删除;只有仍保护现役行为且包装仅为调用通道的原测试才改调用入口,不因现役代码缺测试而移植旧测试 | +| 旧业务清理 | 7 | 删除已被替代或已退役的实现及专属测试,保留现役锁、退出、记忆读写等行为 | +| 字段收窄 | 1 | W076 保留生产 Pending 状态,移除未消费的 String 载荷,错误断言转向现役持久状态 / 审计 | +| 契约待厘清 | 4 | W004–W006 与 W113 先收敛明确冲突,不能直接删除或全部加 cfg(test) | +| **合计** | **74** | 这是静态处理建议,不是已消除的 warning 数量 | + +**建议顺序**:先处理 42 条测试编译边界,对其余 20 条复核具体测试后决定删除或调整原调用;再成组处理 7 条旧实现和 W076。用户已明确 4 条契约问题先保留不动。诊断与对象不是一一对应,包装与重导出可能分别计数,实际减少量须以实施后的重新编译为准。下表原“迁移测试入口”标签仅是初步候选,最终测试处置以实施记录为准,不能批量照搬。 + +### 需要保留的边界 + +- W184 的 context bundle build/write 本体有生产调用,只能限制测试 facade 重导出;同理不能因路径 getter 告警删除 sidecar、读写器或持久协议。 +- W074/W084/W085 的旧 JSON `pid/createdAt` 锁判据已经不是正式锁实现,可成组清理;但 Linux“伪造过期 metadata 不能抢走仍持有的 OS 文件锁”测试仍是现役合同,必须保留。 +- W143/W144/W146 的旧 `runner.shutdown_for_client_exit` 仅测试可达;保留正式 GUI 退出、多窗口参与锁和 `shutdown_external_agent_runner_at`,测试迁往这条正式链。 +- W140 的旧 memory 删除 helper 没有正式调用。主方案中的旧斜杠入口描述已被[已接受的退役 ADR](../../adr/【ADR】退役AGC项目对话斜杠命令与终端swarm chat入口-2026-09-22.md)覆盖,实施清理时同步收敛旧说明;现役 memory 读写、blackboard、持久文件和项目权限 id 不随 helper 擅自删除。 +- W076 的 Pending 仍由生产构造;三处返回前已调用 `record_game_creator_agent_runtime_finalization_pending` 写状态及审计。移除载荷时应保留并检查该错误记录,而非删除 Pending 分支或取消故障注入测试。 + +### 四条契约问题 + +- **W004–W006 / AuthBridge**:正式 `config.rs:3706–3713` 与 `codex_app_server/mod.rs:2276–2314` 只支持平台会话或显式自定义 Key,与[模型别名专题](../../technical/【技术方案】AGC后台模型别名与对话选择-2026-09-05.md)一致;但[AGC 主方案](../../technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md)第 129–130 行仍承诺 OAuth 目录与轮换,第 637–640、1467 行也保留认证桥说明。先明确是否退役 OAuth 路线并更新权威文档,再整体处理旧 reader、variant、目录来源与 handoff 专属链;不能只把整链藏进测试配置。 +- **W113 / UserRevisionRequested**:`delegation.rs:570–599` 的写入入口只在测试调用,但 Fast GDD 方案第 2143 行称审批 receipt 会写出该状态,`runtime_driver/main_loop.rs:1616` 仍以它作为修订 blocker。需核对审批回执有无等价写入,再决定补接实现或修正文档;不删持久化状态值、兼容读取或修订门禁来消除告警。 + +### 逐项证据 + +编号沿用附录,位置以本节基线为准;缩写文件位于 `apps/ai-game-creator-shell/src-tauri/src/`,Runtime 深模块位于其 `agent/` 下,`build_support/` 位于 `src-tauri/` 下。每项只建议处理诊断对应对象,具体测试及生产替代路径如下。 + +| 编号 | 分类 | 当前证据与处理边界 | +| --- | --- | --- | +| W001 | 测试辅助 | `build_support/runtime_prompt_bundle.rs:938` 生成 ID,仅 `agent/prompt.rs:693` 的 bundle 断言使用;在生成器输出处添加测试边界,保留 manifest 校验。 | +| W002 | 测试辅助 | 生成器 `runtime_prompt_bundle.rs:942` 生成 VERSION,仅 `agent/prompt.rs:694` 断言;处理同 W001,不编辑 target 产物。 | +| W004 | 契约待厘清 | `codex_app_server/mod.rs:27,141` 的旧 auth-bridge URL 仅测试分支消费;不能把 URL 常量的测试用途等同于整条 OAuth 业务的保留依据,与 W005/W006 一并先收敛冲突文档。 | +| W005 | 契约待厘清 | `codex_app_server/mod.rs:45,1825–1859` 的读取上限只被 cfg(test) auth.json reader 使用;正式凭据构造 :2276–2314 不调用 reader。当前平台路由文档与主规范 OAuth 承诺冲突,先定清范围。 | +| W006 | 契约待厘清 | AuthBridge 只在测试构造;生产 `config.rs:3706–3713` 锁定官方路由,或显式 custom Key。模型别名专题 :5–10,33–45 支持两类来源,但主实施计划 :129–130,637,639–640 仍承诺 OAuth 目录/轮换;旧匹配与 handoff 链不可在本次直接删或全部 cfg(test)。 | +| W007 | 测试辅助 | `codex_app_server/mod.rs:723` 仅委托现役 `resolve_direct_codex_project_authority`(生产 :2606);测试 :6217,6273,6279,6293 验证目录边界,可改测现役函数或将包装放入测试模块。 | +| W008 | 测试辅助 | `codex_app_server/mod.rs:1936` 仅给现役 `configure_game_creator_codex_app_server_command_for_mode` 补默认参数,测试 :7015,7103 消费;限定测试编译或直接显式传入 mode。 | +| W009 | 测试辅助 | `codex_app_server/mod.rs:2516` 只委托现役带 auth_handoff 构造函数;测试替身启动入口 :2506,6914,7672,7849 使用;可 cfg(test),不删除正式进程启动。 | +| W013 | 测试辅助 | `codex_provider_proxy.rs:316` 补默认 parallel=false 后委托现役 :330;本模块 :477,855,916 与 app-server :6778,6827 测试使用,正式启动走 :2671 的 with_parallel。 | +| W014 | 测试辅助 | `design_runtime.rs:1228` 为现役 continue_with_capture 内核提供默认采集上下文;测试 :2429 等消费,正式 Tauri 入口 :1627 显式传入 capture;可限定测试并保留生命周期测试。 | +| W015 | 测试辅助 | `design_runtime.rs:1314` 为现役 decide_with_capture 内核补 capture;测试 :2506,2531,3230 使用,正式入口 :1652 直接调用内核;处理同 W014。 | +| W028 | 迁移测试入口 | `direct_runtime/mod.rs:103` 只在 :6635 断言测试临时目录的固定文件存在,与随后 prompt 判断无数据依赖;删该包装和无关 fixture 准备,保留 :6643 对现役美术意图判断的断言。 | +| W035 | 测试辅助 | `direct_thread_wire.rs:332,366` 是事件身份与时间的只读 getter,仅 app-server :5799,5800,5809 等断言使用;可 cfg(test),事件字段、序列化、构造及其他类型同名方法保持不变。 | +| W039 | 测试辅助 | `direct_tool_bridge.rs:398` 为现役 state_with_search 默认设 false;测试 :3147,3774,4454,4512,4664 使用,限定测试即可;保留生产带搜索策略构造。 | +| W049 | 测试辅助 | generation/canvas_generation.rs:1148-1162 是无 ExternalEditorBindingAccess 的包装(附录旧定位 :1175);canvas_generation.rs:1401,3221,3500 生产调用 with_access;:11218 模块测试调用无 access 包装。给无 access 包装加 cfg(test),保留 inner 与 with_access 的正式鉴权/轮询路径。 本地 HTTP mock 仍测轮询状态;正式绑定认证由 with_access 测试覆盖。 | +| W053 | 迁移测试入口 | agent/prompt.rs:345-350 仅把 None session 转发到 for_session;runtime_driver/entrypoints.rs:562,659 正式路径调用 for_session;tests/provider.rs:347 旧入口测试。删除无 session 包装,测试直接调用 build_game_creator_role_agent_chat_request_for_session(..., None, ...)。 保持角色 webSearchEnabled 断言,不改正式 session 选择/归属。 | +| W055 | 测试辅助 | runtime_actions/action_audit.rs:80-102 从当前 revision 造回执;生产使用 :105 带 project_revision_before 的写入,例如 runtime_actions/parallel_read.rs:861、runtime_driver/pending_execution.rs:685;tests/command_runtime.rs:3937,3995 和 tests/project_tools.rs:4105 使用便捷写入。此造数便捷包装及 runtime_actions.rs:54 重导出加 cfg(test),保留带 before 的正式回执。 现有回执测试继续断言序号/拒绝语义,避免便捷包装进入生产以免抹去动作前 revision。 | +| W056 | 迁移测试入口 | pending_confirmation_ledger.rs:210-214 仅测试包装;generation/prompt_context.rs:1082 生产用 with_minimum;tests/runtime_actions/policy.rs:1897,1905 用旧包装;:1988-2005 已测正式 redact_secret_tokens。与 W179 一并删除旧布尔包装;保留正式 secret token 扫描。 把必要 JSON 边界断言并入 redaction 测试,不保留旧私有 API。 | +| W057 | 迁移测试入口 | pending_confirmation_ledger.rs:216-221 仅 W056 包装调用;pending_confirmation_ledger.rs:224 是仍由 generation/prompt_context.rs:1082 使用的 with_minimum 版本。删除固定最短长度旧包装;保留 with_minimum。 W056 测试改现役 redaction 后,无需独立测旧 token_end。 | +| W062 | 迁移测试入口 | runtime_actions/tool_plan_protocol.rs:3-8 旧 String 错误包装;tool_plan_protocol.rs:49 正式 classified parser 调用;tests/provider.rs:7087,7113;tests/runtime_state.rs:486,505 仅旧包装调用。与 W181 一并删除旧包装;保持 classified parser。 迁移 parser 测试到 classified Result 类型,保留首个完整 JSON 和协议拒绝的行为断言。 | +| W065 | 测试辅助 | runtime_driver/entrypoints.rs:218-224 仅 test guard :272 调用;entrypoints.rs:248-263 是生产多窗口注册表写入;:265 guard 本身已 cfg(test)。给 configure 包装加 cfg(test),保留 validate/register 及多窗口广播注册表。 测试 guard.configure 继续验证端口/token;生产窗口 attach 用正式注册。 | +| W067 | 迁移测试入口 | runtime_driver/entrypoints.rs:581-588 无 session 转发包装;src/commands.rs:1255 调用 for_session_at;tests/sessions.rs:21 仅旧包装调用。删无 session 包装及 runtime_driver.rs:290 重导出;测试调用 for_session_at(..., None, ...)。 保留聊天内容/运行状态断言;正式命令的 session id 传递不改。 | +| W068 | 迁移测试入口 | runtime_driver/entrypoints.rs:634-645 无 session 转发包装;src/commands.rs:1328 调用 for_session_at;tests/provider.rs:409,475,531,590,644 调旧包装。删无 session 包装及 runtime_driver.rs:292 重导出;测试调用 stream_for_session_at(..., None, ...)。 保留 delta/失败回退断言,不改变命令层显式 session。 | +| W070 | 测试辅助 | runtime_protocol/goal_contract.rs:82-91 root.join 便捷路径;goal_contract.rs:746 位于 #[test] goal_contract_read_fails_closed_after_content_or_binding_tampering;goal_contract.rs:463,547 正式读写走 relative_path。仅 path 便捷函数加 cfg(test),保留 goal contract 相对路径和正式读写。 篡改测试继续借便捷路径定位 fixture;不改变 durable goal contract 格式。 | +| W073 | 测试辅助 | runtime_protocol/context_bundle.rs:3-11 便捷路径;context_bundle.rs:329,419 正式写/读走 relative_path;tests/runtime_state.rs:1399,1817 和 tests/goal.rs:1552 使用路径。path 便捷函数及 runtime_protocol.rs:37 的该项重导出加 cfg(test);保留 production relative_path。 文件存在/损坏/恢复测试继续可定位同一持久 sidecar。 | +| W074 | 旧业务清理 | runtime_protocol/models.rs:3 300 秒常量仅旧 read_lock_status :2626 用;runtime_state.rs:2341,2450 当前 Unix/Windows 走系统文件锁;tests/runtime_state.rs:371 和 tests/runtime_actions/task_lifecycle.rs:97 用旧时间戳造过期锁。删除仅旧 read_lock_status 使用的 300 秒常量及 runtime_protocol.rs:65 重导出;保留 Linux 真实文件锁争用回归。 tests/runtime_state.rs:337-382 的旧 JSON 判据测试随 W084/W085 删除;tests/runtime_actions/task_lifecycle.rs:83-112 是重要现役合同(旧 metadata 不可抢活锁),把 createdAt 改为固定极早值如 1,仍验证 OS 锁持有时不可重取。 | +| W076 | 字段收窄 | 原 runtime_protocol/models.rs:87 为 Pending(String);三处生产构造前先调用 record_game_creator_agent_runtime_finalization_pending,主循环不读取载荷。具体错误还被 tests/project_tools.rs、tests/response_stream.rs 和 runtime_actions/response_stream_tests.rs 的多处测试读取,初审的“唯一读取”判断有误。实施时保留生产 Pending 状态和三处错误记录,将 5 处具体错误断言改为检查现役持久状态或同一条 finalization_pending 审计记录,其余匹配改为 Pending;详见第 14 节。 | +| W079 | 测试辅助 | runtime_protocol/run_configuration.rs:3-5 root.join 便捷路径;runtime_protocol/verification.rs:254,270 正式读写直接用 AGENT_RUNTIME_PROJECT_REVISION_RELATIVE_PATH;tests/command_runtime.rs:2131 断言文件存在。便捷函数及 W188 中对应重导出加 cfg(test);保留 revision 常量和正式读写。 保留 revision sidecar 存在性断言。 | +| W080 | 测试辅助 | runtime_protocol/run_configuration.rs:64-70 root.join 便捷路径;run_configuration.rs:134,352 正式读写用 relative_path;tests/provider.rs:781 和 tests/runtime_actions/planning_strategy/autonomous_game_build.rs:1590 以此删除 fixture。便捷函数及 W188 中对应重导出加 cfg(test);保留 relative_path 和正式 binding 读写。 缺失绑定/漂移测试继续删相同文件;不得更改 run profile 持久路径。 | +| W081 | 测试辅助 | runtime_protocol/verification.rs:14-22 root.join 便捷路径;verification.rs:282,300 正式读写用 relative_path;tests/command_runtime.rs:2123,2212,2417 检查/篡改 gate。便捷函数及 W190 重导出加 cfg(test);保留 relative_path 和 verification gate 读写。 保持 gate 不存在/篡改阻断测试。 | +| W083 | 测试辅助 | runtime_state.rs:2591-2597 是对正式 try_open_task_lock_file 的只读探针;runtime_state.rs:2188,2311 正式任务锁取得;tests/mod.rs:476,551 和 tests/response_stream.rs:2660 等用于轮询释放。只给 availability 探针加 cfg(test),保留系统文件锁实现。 异步测试仍可等待锁释放;不要把 probe 的瞬时判断用作正式获取锁的前置门禁。 | +| W084 | 旧业务清理 | runtime_state.rs:2599-2603 旧 is_stale/belongs_to_previous_process 结构;runtime_state.rs:2605-2634 仅旧解析器构造;tests/runtime_state.rs:352,377 读取旧结构;正式锁由 :2188,2311 取得。删旧结构及 W085 解析器,正式文件锁不受影响。 删除 JSON pid/时间戳断言;锁争用和释放已有真实探针测试,必要时补目标行为测试。 | +| W085 | 旧业务清理 | runtime_state.rs:2605-2634 读取 JSON pid/createdAt;无生产调用;tests/runtime_state.rs:337-382 两测试独占旧 API;runtime_state.rs:2341,2450 现役 OS 锁并不以文件内容判定失效。删除旧状态读取器及两条脱离现役行为的测试;与 W074/W084 同组。 如保留恢复合同测试,应通过真实锁句柄和进程退出模拟,而非伪造 JSON 状态。 | +| W086 | 迁移测试入口 | runtime_tools/delegation.rs:289-317 旧直接入口自行取项目写锁;runtime_actions/action_execution.rs:457 正式工具入口已在统一执行链调用 at_locked;tests/response_stream.rs:4781、tests/collaboration/static_deliveries.rs:267,445 等只从测试调用旧入口。删除旧直接业务入口及 runtime_tools.rs:60 重导出;测试搬到 fixture 调 at_locked 并显式持写锁。 静态 delivery、协作恢复与 response-stream 测试保留同样观察语义,不能因 fixture 迁移放宽权限/锁校验。 | +| W088 | 迁移测试入口 | runtime_tools/isolated_joins.rs:105-112 旧默认上限包装;isolated_joins.rs:102,208,229,397 正式路径用 with_limit;tests/collaboration/claims.rs:466 唯一旧包装调用。删除默认上限包装及 runtime_tools.rs:70 重导出;测试直接调 with_limit(joins, AGENT_RUNTIME_READY_ISOLATED_JOIN_PAYLOAD_MAX_CHARS)。 保留超出完整 observation 预算时失败的断言,正式动态 max_payload_chars 不改。 | +| W094 | 测试辅助 | collaboration.rs:244-256 定义写 policy fixture;src/tests/collaboration/policy_snapshot.rs:14、policy_batches.rs:109 等测试调用;生产 resolve_supervisor_collaboration_policy_for_run_at 在 collaboration.rs:681 读取项目 policy/快照,无写 helper 调用。 给 writer 加 cfg(test) 或移测试 support;保留 policy 读取、校验和快照绑定生产路径,测试继续用 fixture 写 policy。 | +| W095 | 测试辅助 | collaboration.rs:311-320 绝对 snapshot path 包装;src/tests/mod.rs:3885 和 policy_snapshot.rs:1198 测试调用;生产快照路径由 collaboration.rs:279 相对路径函数和读写侧使用。 给绝对路径测试包装加 cfg(test);不改相对路径、持久文件名和读写恢复契约。 | +| W096 | 测试辅助 | collaboration.rs:322-333 binding 绝对路径包装;src/tests/mod.rs:3898 测试调用;生产 binding 在 collaboration.rs:290、484、583 等使用相对路径。 给包装加 cfg(test);保留 binding 持久路径与恢复测试。 | +| W099 | 测试辅助 | command_exec.rs:2375-2383 无提交钩子的便捷入口;只在该文件 cfg(test) mod tests(2564-2565)内如 2968、3404 调用;现役 command_ops.rs:487、direct_validation.rs:574 调用 run_prepared_project_command_with_output_at。 将入口收窄到 cfg(test);保留测试对实际 resolve/prepare/stage/execute 核心路径的覆盖。 | +| W100 | 测试辅助 | command_exec.rs:2385-2397 只由 W099 在 2382 调用;生产 bootstrap 在 2407、runtime command_ops.rs:487 使用 prepared 入口。 与 W099 一起 cfg(test);不移除 resolve_project_command_spec_at 与 prepared 执行核心。 | +| W107 | 测试辅助 | config.rs:2371 Windows owner 初始化函数;config.rs:1388-1389 调用明确 cfg(all(windows,test)),assets.rs:2641-2648 与 tests/mod.rs:439 是 Windows 测试;生产 config.rs:1390-1394 调 harden_new_game_creator_private_path。 函数改 cfg(all(windows,test));保持生产 Windows ACL harden 与测试 owner fixture。 | +| W109 | 测试辅助 | context_compaction.rs:91-99 绝对 path 拼接,仅该文件 cfg(test) 模块 1174 和 tests/provider.rs:2627、7691 等读取;生产通过 relative_path 访问 context compaction sidecar。 给绝对 path 包装加 cfg(test);保留相对路径函数、读写与恢复测试。 | +| W110 | 测试辅助 | delegation.rs:312 has_external_wait 只在 runtime_actions/project_gates.rs:1937 的测试与 detail parser 比对;生产 main_loop.rs:1703 使用 static_delegate_barrier_has_external_wait(detail)。 方法可 cfg(test);保留并运行 detail 解析与 barrier 字段等价测试,生产 park 行为不改。 | +| W111 | 测试辅助 | delegation.rs:330-353 默认 contract 构造包装;测试调用如 delegation.rs:2696、tests/collaboration/claims.rs:841;生产 runtime_tools/delegation.rs:755、921 调 new_static_delegate_delivery_with_contract。 包装 cfg(test);保留现役 contract 构造器,测试 fixture 仍验证真实 record 校验。 | +| W112 | 测试辅助 | delegation.rs:436-471 将简单 terminal status 映射结构化结果;只在 delegation.rs:2707 测试及 tests/collaboration/claims.rs:853 等调用;生产 runtime_tools/delegation.rs:814、1087 调 mark_static_delegate_delivery_ready_with_result_at。 包装 cfg(test);保留结构化结果写入与 delivery 状态迁移核心,测试不得把简化 fixture 当作完整生产合同。 | +| W113 | 契约待厘清 | delegation.rs:570-599 标记 UserRevisionRequested,仓库精确调用仅 delegation.rs:3331、3393 的 cfg(test);Fast GDD 技术方案:2143 声称 M1C-1 审批 receipt 写入该状态;main_loop.rs:1616 仍以该状态作为审批卡修订 blocker。 先核对现行审批 receipt 写入位置和 durable status:若另有等价写入,删除/迁移此断链入口测试;若没有,补接审批写入或修订文档承诺。不要直接 cfg(test)/删除而掩盖修订 barrier 缺口。 | +| W114 | 测试辅助 | delegation.rs:770-783 默认 payload budget 包装只在 delegation.rs:2717、tests/collaboration/claims.rs:1232 等测试调用;生产 runtime_tools/run_status.rs:184 使用 claim_ready_static_delegate_receipts_with_budget_at。 包装 cfg(test);默认预算测试保留,现役带预算 claim 路径不变。 | +| W115 | 迁移测试入口 | delegation.rs:965-1000 旧 mark claim observed 不校验 observation receipt 集合;只在 delegation.rs:2941 和 tests/collaboration/static_deliveries.rs:847 等测试调用;生产 runtime_actions/run_status_observation.rs:16 使用 mark_static_delegate_claim_observed_for_receipts_at,delegation.rs:1002-1025 校验完整 ID 集。 把相关测试迁到 for_receipts 入口并传真实 observed_delegation_ids,随后删除旧入口;保留 Prepared→Committed→Observed 及缺 receipt 拒绝回归。 | +| W118 | 迁移测试入口 | editor_adapters/execution.rs:518-520 Unity path 别名只在 editor_adapters.rs:221、223 的 cfg(test) 模块使用;现役通用 editor_execution_fence_path 和 Unity execute 路径共享固定持久文件。 测试改用 editor_execution_fence_path(ManagedEditor::Unity, config),删除别名;保留 Unity fence 路径/恢复断言。 | +| W121 | 迁移测试入口 | 原告警 W121 行 527;当前 execution.rs:521-523 Unity receipt 校验别名,editor_adapters.rs:231-243 测试用;实际 editor_execute_receipt_is_valid 在通用执行路径使用。 测试调用通用 receipt 校验并删除别名;维持 Unity 非法/有效 receipt 断言。 | +| W122 | 迁移测试入口 | 原告警 W122 行 530;当前 execution.rs:524-530 Unity RPC 别名,editor_adapters.rs:190、201、210 测试用;生产 execute_managed_editor_code 在 execution.rs:534-538。 测试调用 managed_editor_rpc_owned(ManagedEditor::Unity,...) 或现役 execute 路径,删除别名;保留 busy/disabled/delivery ID 行为测试。 | +| W123 | 迁移测试入口 | 原告警 W123 行 537;当前 execution.rs:531-533 Unity acknowledge 别名仅 editor_adapters.rs:220、222 测试用;通用 acknowledge_editor_delivery 为现役核心。 测试改用 acknowledge_editor_delivery(ManagedEditor::Unity, id),删除别名;保留 wrong-id 与已知 delivery ack 断言。 | +| W126 | 迁移测试入口 | patchset.rs:33-55 五个 getter 仅 patchset.rs:1567、1570、1573-1575 的 cfg(test) 模块调用;ProjectPatchsetChangeSummary 对应字段在 24-29 已是 pub(crate) 且 Serialize。 测试直接读字段后删五个冗余 getter;保持 summary 序列化字段、hash/bytes 断言。 | +| W127 | 迁移测试入口 | patchset.rs:74-76 is_empty 只在 patchset.rs:1566 测试调用;同类型 len 在 70-72 且测试 1565 已断言 len==3。 删 is_empty 与重复断言或直接检查 changes;保留 patchset 实际 apply/rollback 测试。 | +| W130 | 测试辅助 | preview.rs:15-21 无 analytics lease 的 registry setter;tests/project.rs:4657、4724、6419 使用;生产 preview.rs:797 调 set_running_with_lease 记录 analytics lease。 set_running 可 cfg(test);保留替换旧 preview 的 registry 行为测试与生产 lease 路径。 | +| W131 | 测试辅助 | process_session/lifecycle.rs:231-248 包装 prepare+start,只在 process_session/tests.rs:474、647 等及 tests/mod.rs:5656 调用;生产 runtime_tools/process_ops.rs:309 调 start_prepared_process_session_at。 包装 cfg(test);保留测试对 launch 准备、安全约束和实际 start_prepared 状态迁移的覆盖。 | +| W135 | 测试辅助 | project/conversation.rs:1083-1088 无 session 包装;调用都在 project/conversation/tests.rs:261 与 tests/collaboration/delegation.rs:154 等;生产 commands.rs:5655、prompt_context.rs:308/346 调 read_local_conversation_for_session_at。 包装 cfg(test);保留默认 session 和 legacy conversation 兼容测试,不改当前 session-aware 读取。 | +| W136 | 测试辅助 | project/conversation.rs:1412-1418 无 session append 包装;project/conversation/tests.rs:243 与 tests/sessions.rs:976 等测试调用;生产 commands.rs:5799、runtime_tools/delivery.rs:1458 调 append_local_conversation_message_for_session_at。 包装 cfg(test);保持默认会话和 JSONL append 测试及现役 session-aware 写入。 | +| W139 | 测试辅助 | project/external_editor_bindings.rs:47-62 for_platform/for_developer;所有精确调用位于该文件 1148 之后 cfg(test) 模块、resource_editor.rs:5808 后测试、canvas_generation.rs:8407 后测试等;生产 commands.rs:3671/4359、direct_runtime/mod.rs:3170、canvas_generation.rs:3083 调 ExternalEditorBindingAccess::new。 两个 convenience constructor 加 cfg(test) 或移测试 support;保留 new 对平台冻结 session 与开发者 key 的校验,测试继续覆盖两种凭据来源。 | +| W140 | 旧业务清理 | project/memory.rs:97-117 delete_local_game_memory_at 仅 tests/sessions.rs:807/827 调用;当前 commands.rs:5526/5561 与 main.rs:2738/2741 只注册读写。2026-09-22 已接受 ADR《退役AGC项目对话斜杠命令与终端swarm chat入口》决策段及 decision-log.md:59-64 明确 /remember 等斜杠命令与 memoryCommands 整体退役;技术方案:1176 的 /forget-memory→memory.delete 和 1195 的旧开发删除表述是历史口径。 删未接线的旧 delete_local_game_memory_at 与 tests/sessions.rs:791-830 中仅验证删除的断言;读写/blackboard 现役测试保留。同步修正主技术方案 1176、1195 的过期删除/斜杠描述;GAME_CREATION_APP_COMMANDS 中 memory.delete 若仍作为权限词汇保留,按 ADR 明确保留项目权限 id 的边界单独核对,不因删除 helper 擅删权限 id。 | +| W141 | 测试辅助 | project/verification.rs:290-303 默认 cwd='.' 规格包装,仅 tests/command_runtime.rs:2600-2660 调用;生产 verification.rs:702 使用 resolve_project_verification_spec_with_cwd_at,runtime_tools/command_ops.rs:900 走 with_commit。 包装 cfg(test);保留 script allowlist、expectedCommand 精确匹配测试,正式 cwd/commit 路径不改。 | +| W142 | 测试辅助 | project/verification.rs:674-689 默认 cwd/无 commit 包装,仅 tests/command_runtime.rs:2721-2837 调用;生产 project/export.rs:284、runtime_tools/command_ops.rs:900 调 run_project_verification_with_commit_at。 包装 cfg(test);保留成功/失败/超时与审计行为测试,正式持锁 commit 路径不变。 | +| W143 | 旧业务清理 | runner/client.rs:18 client-exit timeout 仅 1393 的 shutdown_external_agent_runner_for_client_exit_at 使用;正式 GUI exit 在 client.rs:1403-1415 调 shutdown_external_agent_runner_at。 与 W144/W146 同组删除 client-exit 专属 timeout;将 client-exit 测试迁到现役 GUI exit/idle shutdown 语义。 | +| W144 | 旧业务清理 | runner/client.rs:667-696 仅被旧 shutdown_external_agent_runner_for_client_exit_at 在 1353 调;正式 GUI exit 在 1414 用 shutdown_external_agent_runner_at,并有其他窗口参与锁门。 与 W143/W146 同组删除 endpoint-for-client-exit 读取路径;保留现役 endpoint/lock 验证与竞态测试。 | +| W146 | 旧业务清理 | 原告警 W146 行 1355;当前 runner/client.rs:1349-1397 仅 runner/tests.rs:257、299 调;生产 main.rs:2217 使用 shutdown_external_agent_runner_for_gui_exit,client.rs:1408-1415 以参与锁决定并调 shutdown_external_agent_runner_at;dispatch.rs:840 旧 runner.shutdown_for_client_exit 仅 cfg!(test) 分支。 删除整条旧 client-exit 请求链及专属测试,改测正式 GUI exit(多窗口保留、最后窗口 shutdown);同步移除仅测试可达的旧协议分支/方法白名单,确认当前 Runner shutdown 语义。 | +| W152 | 迁移测试入口 | runner/dispatch.rs:98-103 无 event sink 包装仅 runner/tests.rs:922-1506 使用;生产 dispatch.rs:769-806 的 runner.attach_gui_owner 调 apply_external_agent_runner_gui_owner_attachment(..., Some(event_sink))。 测试直接调用现役 attachment 并提供合法 event sink 或走 dispatch 请求,删除旧无 sink 包装;保持 epoch/revision、登录态与接收端绑定测试。 | +| W165 | 测试辅助 | ui_editor/workflow.rs:158-163 无 provider_identity 便捷入口仅该文件 cfg(test) mod tests:1651、1658、1730 使用;生产 runtime_tools/ui_workflow.rs:27 调 run_ui_workflow_at_with_provider 并传 agent/run 身份。 入口 cfg(test);保留 prepare/status 测试与生产 provider 身份、权限和 revision 行为。 | +| W176 | 测试辅助 | runtime_actions.rs:52,54 仅 facade 重导出告警;action_audit.rs:239,869 中 git detail 清洗是生产逻辑;action_audit.rs:80-102 的 receipt 简便包装仅测试调用;tests/project_tools.rs:3311,3400,3409;tests/command_runtime.rs:3937。仅给两个重导出及 receipt 简便包装加 cfg(test);保留 git detail 清洗本体、带 project_revision_before 的生产回执写入。 receipt 测试仍用当前 revision 造数;不可删 action_audit.rs:869 或 :105 的生产路径。 | +| W179 | 迁移测试入口 | runtime_actions.rs:88 重导出;pending_confirmation_ledger.rs:210-221 仅供自身及测试使用;generation/prompt_context.rs:1082 生产使用 agent_runtime_secret_token_end_with_minimum;tests/runtime_actions/policy.rs:1988 已测实际 redact_secret_tokens。删旧 contains 包装及无最短长度包装,移除重导出;保留 with_minimum 生产扫描。 tests/runtime_actions/policy.rs:1897-1907 的两条断言迁入现有 redact_secret_tokens 行为测试或删除重复断言。 | +| W181 | 迁移测试入口 | runtime_actions.rs:143 旧 parser 重导出;tool_plan_protocol.rs:3-8 包装 classified parser 并把 typed error 转 String;tool_plan_protocol.rs:49 生产走 parse_game_creator_agent_tool_plan_response_classified;tests/provider.rs:7087,7113 旧包装调用。删 String 错误旧包装及重导出;生产保留 classified parser。 tests/provider.rs:7087,7113 与 tests/runtime_state.rs:486,505 改测 classified parser 的成功/失败语义。 | +| W184 | 测试辅助 | runtime_protocol.rs:35-40 为 facade 重导出;context_bundle.rs:812,821 生产 persist_game_creator_agent_runtime_context 调用 build/write;context_bundle.rs:329,419 生产按 relative_path 存取;tests/command_runtime.rs:176,196 使用 facade。仅把三项 facade 重导出置于 cfg(test);仅 path 便捷函数本体可 cfg(test)。build/write 本体必须保留生产构建。 测试继续通过 facade 构造/篡改 bundle;生产 persist、相对路径及持久格式均不迁移。 | +| W188 | 测试辅助 | runtime_protocol.rs:80,82 facade 重导出;run_configuration.rs:3,64 两个 root.join 便捷路径;run_configuration.rs:134,352 生产使用 run_profile_binding_relative_path;verification.rs:254,270 生产用 AGENT_RUNTIME_PROJECT_REVISION_RELATIVE_PATH;tests/command_runtime.rs:2131;tests/provider.rs:781。两个便捷路径函数及重导出可 cfg(test);保留 production relative_path/常量和读写器。 项目 revision 存在性及 binding 缺失模拟仍可用 cfg(test) helper;不得改变 .agent/runtime 持久路径。 | +| W190 | 测试辅助 | runtime_protocol.rs:98 facade 重导出(附录 W190 旧定位为 :103);verification.rs:14-22 root.join 便捷路径;verification.rs:282,300 生产读写走 verification_gate_relative_path;tests/command_runtime.rs:2123,2212。便捷路径函数及 facade 重导出加 cfg(test);保留 relative_path 和门禁读写。 测试直接检查/篡改 gate 文件,路径语义仍由 production relative_path 定义。 | +| W191 | 迁移测试入口 | runtime_tools.rs:60 重导出;runtime_tools/delegation.rs:289-317 旧直接包装自行取写锁;runtime_actions/action_execution.rs:457 生产在统一 action 执行写锁下调用 observe_agent_runtime_agent_delegate_at_locked;tests/collaboration/delegation.rs:1798,1862 调旧包装。删除旧直接业务包装和重导出,或仅转成 tests/support 中 fixture;保留 at_locked 正式路径。 大量 collaboration/static_deliveries 等测试应由测试 fixture 显式取锁后调用 at_locked,不改变委派合同。 | + +本轮仅更新本清单,未修改 AGC 实现、未重新运行编译或 Rust 测试;120 条是本轮开始前合并验证的已测结果。文档更新运行编码检查与 diff 检查。后续实施应按组验证默认构建、test target 和相关定向用例,涉及 Windows ACL / Linux 文件锁等条件路径时另补相应目标验证。 + +## 14. 原测试用途项清理实施(2026-09-23) + +本批落实第 13 节中的 70 条;用户要求 W004–W006(AuthBridge)和 W113(UserRevisionRequested)保留不动。本批未修改这些符号的实现或替它们修订契约。 + +### 测试处置复核 + +“现役代码缺少测试”不是迁移旧测试的理由。本批先核对原断言:仅旧实现、无关 fixture 或重复覆盖直接删除;仅当原测试仍保护现役契约、包装只是调用通道时,才调整原测试调用。没有为补覆盖率新增用例,也没有为证明删除新增“不存在”测试。 + +| 原 20 条候选 | 实际处理与依据 | +| --- | --- | +| W028 | 删除只检查测试临时文件存在的 helper、准备代码和断言;它与原美术意图判断无数据依赖,原意图测试保留。 | +| W056/W057/W179 | 删除旧 secret-prefix 包装及身份测试中夹带的两条断言;现有 `redact_secret_tokens` 用例已覆盖 JSON 中的密钥与普通词不误删,不再迁移重复断言。 | +| W053 | 原测试核对逐 Agent 联网配置仍属现役契约,直接调用带 session 的组装器并传 None,保留原断言。 | +| W062/W181 | 原测试验证完整 JSON 提取、无效工具计划拒绝和计划更新,仍适用于现役协议;直接调用 classified parser,未新增旧 String 错误转换的测试。 | +| W067/W068 | 保留原默认 session 持久化、delta、首包错误回退和上游错误不回退测试;直接调用现役 for_session 入口并显式传 None。 | +| W086/W191 | 删除旧直接委派业务入口和导出;现有协作测试保留,在 `tests/mod.rs` 用测试 fixture 显式取得项目写锁后调用现役 at_locked 核心。fixture 不保留旧锁失败 observation 投影。 | +| W088 | 原用例验证恢复旧 claim 时完整 observation 超预算,仍属现役恢复契约;删除默认预算包装,原断言调用 with_limit 并显式使用 10,000 字符预算。 | +| W115 | 删除跳过回执集合检查的旧 observed 入口;现有 claim 状态/恢复测试使用完整 fixture 回执集合调用正式 for_receipts 入口。它们保留原状态断言,不冒充观察响应完整性测试;现有缺 ID 拒绝并保留 blocker 的专门测试继续保留。 | +| W118/W121/W122/W123 | 删除 Unity 专属别名;原 busy、disabled、围栏、错误回执和 acknowledgement 断言仍是现役 Unity 行为,改为通用入口加显式 ManagedEditor::Unity。 | +| W126 | 删除直接返回公开内部字段的五个 getter;原 hash、bytes、operation 等断言直接读取同一字段。 | +| W127 | 删除 is_empty 及与同一用例 len==3 重复的断言。 | +| W152 | 删除无 sink 包装;原平台会话/epoch 测试直接调用同一 attachment 核心并保持 None,不改造成带 sink 的另一场景;正式 dispatch 的 Some(sink) 原已有独立覆盖。 | + +其余 42 条只收紧测试辅助、观测 getter、生成常量或 facade 导出的编译边界。W184 的 context bundle build/write 本体仍参与生产;W107 使用精确的 `cfg(all(windows, test))`;生成 prompt 常量从生成器调整,不手改 target 产物。 + +7 条旧实现按组删除:W074/W084/W085 的 JSON 锁过期解析、W140 的旧 memory 删除 helper、W143/W144/W146 的旧 client-exit shutdown 请求/专属协议与测试。现役 OS 文件锁、Linux 过期 metadata 不可抢活锁回归、memory 读写、正式 GUI 退出和多窗口参与锁保持有效;不删除用户数据,不改权限 id 或持久格式。主实施计划中的旧 memory 删除/斜杠承诺同步更新。 + +W076 只将 Pending(String) 收窄为 Pending;三处生产返回前继续记录脱敏错误状态、事件和 Agent DB。实施时发现初审漏列了 `tests/project_tools.rs`、`tests/response_stream.rs` 的额外错误载荷断言,已逐处处理:5 处具体错误断言检查现役 state.error 或同一条 finalization_pending 审计记录,其余匹配保留 Pending 状态。没有移除故障注入和恢复断言。 + +### 验证 + +默认 Windows dev 编译通过,warning **120 → 50,净减少 70 条,无新增诊断**(dead_code 25、unused_imports 15、unused_variables 9、unused_assignments 1);4 条明确保留项仍在。另有 5 条 ts-rs 提示不变。测试构建初次发现测试导出可见性及旧 fixture 导入残留,已修正后重新验证。 + + +- `cargo check --locked --offline --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml -p genarrative-ai-game-creator-shell --message-format=json` 通过;与合并 master 后的 120 条诊断逐条比较,无新增诊断。 +- Rust 二进制定向测试 **155/155 通过**:finalization、静态委派 claim/恢复、GUI owner/shutdown、Unity 适配、patchset、memory 读写、项目验证、session/stream、计划协议、脱敏、Direct 启动/设计运行时和 prompt bundle。 +- `prompt_source_boundaries` **6/6**、`runtime_prompt_bundle_build` **17/17** 通过;最终代码的正常二进制构建仍报告 50 条 warning。 +- `node apps/ai-game-creator-shell/scripts/check-config.mjs` 通过(含 3 个 vendor 源码许可测试);修改文件的 Rust 格式检查、`npm run check:encoding`、`npm run check:doc-index` 和 `git diff --check` 通过。 +- 未运行全量 Rust 测试、Linux/macOS 构建、正式 editor features、release 打包或 GUI 端到端流程;Linux 文件锁测试仅保留源码,未在本轮 Windows 环境执行。 + ## 附录:237 条编译器诊断位置 以下是诊断的人工可读整理,不保存原始构建日志、本机路径或 target 缓存路径。位置相对 `apps/ai-game-creator-shell/src-tauri/`;行号只对应本节基线,修改后以符号搜索及重新编译为准。每条保留一个主要 span,编号用于本清单内跟踪,不表示独立业务缺陷。生成产物项须回到 `build_support/runtime_prompt_bundle.rs` 处理。 diff --git a/docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md b/docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md index dbe77e802..ce058c6a1 100644 --- a/docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md +++ b/docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md @@ -1173,7 +1173,7 @@ game-project/ - 聊天输入 `/export` 会生成待确认的 `project.export_package` 内置命令,确认后只把 `game/**`、`assets/**` 和 `exports/README.md` 打包到 `exports/playtest-package-*.zip`;缺少 `exports/README.md` 时先按项目 manifest 生成最小试玩说明,已有文件原样保留。发布前若 `code-prototype` 未完成且没有运行中的预览,直接阻止发布,不触发用户项目构建;可运行原型完成后才允许按 `build` 脚本补齐产物。发布进度使用独立模态弹窗展示,遮罩覆盖整个工作区并阻止交互,不再使用聊天确认卡;导出前重新校验可玩入口,拒绝符号链接和越界路径,不把 `.agent/`、`memory/`、日志、trace、运行时配置或密钥文件写入 ZIP。 - 聊天输入 `/exports` 会只读执行 `project.export_list`,列出当前项目 `exports/playtest-package-*.zip` 历史试玩包,并提供显示目录或继续 `/export` 的草稿;该命令不删除文件、不分享文件、不新增面板。 - 聊天输入 `/preview` 会生成待确认的 `preview.start` 内置命令,确认后启动只读本地 HTTP 预览并切换到客户端内运行视图;`/open-preview` 在本地项目已初始化后生成待确认的 `preview.open`,只激活当前授权项目对应的 `127.0.0.1` 运行容器;`/preview-status` 只查询当前授权项目的本地 HTTP 预览并写入 `preview.status` 命令日志;`/preview-stop` 只停止当前项目预览,不展示或停止其它项目遗留的全局预览。 -- 聊天输入 `/memory [short|long|blackboard]` 读取短期、长期或黑板记忆;`/remember [short|long|blackboard] 内容` 生成待确认的 `memory.write` 并追加短期、长期或黑板记忆,未写 scope 时默认追加长期记忆;主窗口“记到黑板”“覆盖黑板”“清空黑板”只填入 `/remember blackboard `、`/memory-set blackboard ` 或 `/forget-memory blackboard` 草稿,仍由用户补内容并走聊天确认;`/memory-set [short|long|blackboard] 内容` 生成待确认的 `memory.write` 并覆盖保存对应记忆;`/forget-memory [short|long|blackboard]` 生成待确认的 `memory.delete`。 +- 短期、长期和黑板记忆由现役 Runtime 工具与原生读写入口维护;记忆斜杠命令已按 2026-09-22 退役 ADR 清理,未接入正式调用的本地记忆删除 helper 及专属测试一并移除,不删除用户现存记忆文件。 - 聊天输入 `/canvas 画板项目ID` 会生成待确认的 `canvas.project_open`,只打开本机 Genarrative 编辑器里的指定画板项目,不开放任意 URL;确认后聊天先反馈正在打开,再回写真实打开 URL。画板项目 ID 为空或包含控制字符时在聊天侧直接拒绝。 - 聊天输入 `/sync-canvas-project 画板项目ID` 会生成待确认的 `canvas.project_sync`,通过 External Editor API 把该画板项目资源下载到 `assets/canvas-sync/` 并登记为画板来源资产;画板项目 ID 为空或包含控制字符时在聊天侧直接拒绝。 - 聊天输入 `/generate-art 提示词` 会生成待确认的 `canvas.asset_generate`,通过 External Editor API 生成首版美术素材并写入 `assets/canvas-generated/`;提示词为空时在聊天侧直接拒绝。 @@ -1192,7 +1192,7 @@ game-project/ - `check:native-shells` 会运行 `ai-game-creator-shell:check` 和 `ai-game-creator-shell:build -- --no-bundle`,并静态检查 release 与 debug 启动都只登记 `client / index.html` 这一个默认窗口、禁止 Tauri setup 自动打开 developer 窗口、开发面板必须挂在 `devMode` 分支内,正式用户 App 的运行容器只接受 `http://127.0.0.1:*`,release / dev CSP 都只为该 loopback origin 开放 `frame-src`,Tauri 预览激活命令不得调用 opener,用户主流程不得调用旧工作区窗口切换 command。 - 共享契约提供 `GAME_CREATION_AGENT_CAPABILITIES` 和内置命令权限枚举;开发模式会展示能力列表。 - 共享契约提供 manifest task schema 和 ready-task 选择器,用于记录任务拆分、专业组、角色模板、依赖、产物、验收条件和当前可执行任务。 -- 开发模式可读取、保存、删除短期记忆和长期记忆文件;正式用户界面不提供记忆管理入口,短期 / 长期 / 黑板记忆只由 `project-supervisor` 运行期的记忆工具在授权项目内读写。 +- 原生入口保留短期、长期和黑板记忆的读取与保存;正式用户界面不提供记忆管理入口,运行期的记忆工具只在授权项目内读写。项目命令权限 id 的去留与具体 helper 分开判断。 - 共享契约提供 `GAME_CREATION_APP_LIMITED_RUN_COMMANDS`;当前真实命令为 `game.static_smoke`,用于检查 `game/index.html` 的可玩原型门槛并写入 `.agent/logs/command.log`。 - 后台 Agent 的项目 revision 以 `.agent/runtime/project-revision.json` 为唯一事实源,per-run 验证门禁以 `.agent/runtime/verification//.json` 为事实源。每次 `file.write`、`file.patch`、`file.delete` 或 `project.restore` 都必须在实际修改前保守推进 revision,并永久记住当前 run 的 `requiresVerification=true`;失败或崩溃不回退。只有成功且绑定当前 revision 的 `project.verify` 或 `command.run_limited / game.static_smoke` 才能放行空 actions;未修改项目的只读任务不强制验证,但最终回复仍必须绑定请求开始时的 `responseRevision`。per-run context bundle 使用 v2,pending action 使用 v3 并绑定创建时的全局 revision;旧版恢复失败关闭。最终 assistant 和 completed 必须在项目写锁内重读 revision / gate 后依次落盘,文件回读、observation 或锁外旧快照都不能替代验证凭证。验收必须分别模拟待执行动作、修改 run 与只读 run 的跨 Agent revision 漂移,证明旧动作不执行、旧回复不落盘、不产生 completed 或 failed、per-Agent 锁不提前释放、原 run/session 在收到 blocker 后保持可恢复;stale continuation 经重启仍从原 `nextLoopIndex` 续跑,revision 数值或成功验证输出中的动态时间戳不能绕过 context stall。 - `.agent/manifest.json` 会记录当前 `preview` 状态和 `commandRuns` 受限命令运行结果,作为本地产物索引的最小真相源。