From 0af6793954ec182e58a0b631d3a863b18f7217ef Mon Sep 17 00:00:00 2001 From: Linghong Date: Sun, 20 Sep 2026 21:15:45 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BD=BF=E7=AD=96=E5=88=92Agent=E6=8C=89?= =?UTF-8?q?=E5=9B=9E=E5=90=88=E5=BA=94=E7=94=A8=E6=A8=A1=E5=9E=8B=E4=B8=8E?= =?UTF-8?q?=E6=8E=A8=E7=90=86=E6=A1=A3=E9=80=89=E6=8B=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 用户发起执行时读取全局选择并保存最小回合快照 工具循环和自动重试沿用快照,兼容旧会话恢复 补充请求字段与用户操作测试,同步方案和项目约定 --- .../src-tauri/src/agent/design_runtime.rs | 283 +++++++++++++++++- .../agent/runtime_protocol/design_session.rs | 12 +- .../src-tauri/src/tests/mod.rs | 6 +- ...计划】策划Agent回合模型选择生效-2026-09-20.md | 45 +++ ...】策划Agent模型与推理档控件接入-2026-09-20.md | 7 +- ...碑】策划Agent回合模型选择生效-2026-09-20.md | 4 +- ...】策划Agent模型与推理档控件接入-2026-09-20.md | 6 +- .../shared-memory/team-conventions.md | 2 + ...】策划Agent生产迁移与工作区浏览-2026-09-10.md | 8 +- 9 files changed, 347 insertions(+), 26 deletions(-) create mode 100644 docs/project-memory/plans/【实施计划】策划Agent回合模型选择生效-2026-09-20.md diff --git a/apps/ai-game-creator-shell/src-tauri/src/agent/design_runtime.rs b/apps/ai-game-creator-shell/src-tauri/src/agent/design_runtime.rs index 330dca949..e3c2c6f9e 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/agent/design_runtime.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/agent/design_runtime.rs @@ -334,6 +334,7 @@ fn begin_design_turn(session: &mut DesignSession, id: &str) { pending: true, request_index: 0, attempt: 0, + model_selection: None, }); session.last_error = None; session.updated_at = unix_timestamp(); @@ -444,6 +445,45 @@ fn prepare_design_decision( Ok(approved) } +fn select_design_turn_model( + session: &mut DesignSession, + config: &GameCreatorAppConfig, +) -> Result<(), String> { + let turn = session.turn.as_mut().ok_or("缺少当前回合")?; + session.model_id = if config.selected_model_id.trim().is_empty() { + config.llm.model.clone() + } else { + config.selected_model_id.clone() + }; + turn.model_selection = Some(DesignModelSelection { + model: session.model_id.clone(), + reasoning_effort: config.llm.reasoning_effort.clone(), + }); + Ok(()) +} + +fn resolve_design_turn_llm_config( + session: &mut DesignSession, + config: &GameCreatorAppConfig, +) -> Result { + let turn = session.turn.as_mut().ok_or("缺少当前回合")?; + // 旧活动回合恢复时保留已知模型;缺失的推理档只能从当前配置补齐一次。 + let selection = turn + .model_selection + .get_or_insert_with(|| DesignModelSelection { + model: if session.model_id.trim().is_empty() { + config.llm.model.clone() + } else { + session.model_id.clone() + }, + reasoning_effort: config.llm.reasoning_effort.clone(), + }); + let mut llm = resolve_game_creator_llm_config_for_agent(config, "design-agent"); + llm.model = selection.model.clone(); + llm.reasoning_effort = selection.reasoning_effort.clone(); + Ok(llm) +} + fn checkpoint_design(root: &Path, session: &DesignSession) -> Result<(), String> { let _write = acquire_game_creator_agent_runtime_project_write_lock_with_wait(root, "design.session")?; @@ -696,10 +736,7 @@ async fn request_design_provider( return request_scripted_design_provider(root, session, emit).await; } let config = load_game_creator_app_config()?; - let mut llm = resolve_game_creator_llm_config_for_agent(&config, "design-agent"); - if !session.model_id.trim().is_empty() { - llm.model = session.model_id.clone(); - } + let mut llm = resolve_design_turn_llm_config(session, &config)?; // 此循环统一处理流中断与 HTTP 瞬态错误,避免与传输重试相乘。 let max_retries = llm.max_retries; llm.max_retries = 0; @@ -1003,6 +1040,14 @@ async fn finish_design_command( run: bool, mut emit: impl FnMut(DesignEvent) + Send, ) -> Result { + if run + && session + .turn + .as_ref() + .is_some_and(|turn| turn.model_selection.is_none()) + { + resolve_design_turn_llm_config(&mut session, &load_game_creator_app_config()?)?; + } checkpoint_design(root, &session)?; let turn_id = session .turn @@ -1071,15 +1116,15 @@ pub(crate) async fn continue_design_agent_at( .ok_or("策划 Agent 当前正在工作")?; let mut session = match read_design_session(root)? { Some(session) => session, - None => new_design_session( - &project_id, - &load_game_creator_app_config()?.selected_model_id, - ), + None => new_design_session(&project_id, ""), }; if session.project_id != project_id { return Err("策划会话与当前项目不匹配".into()); } let run = prepare_design_input(&mut session, id, input)?; + if run { + select_design_turn_model(&mut session, &load_game_creator_app_config()?)?; + } finish_design_command(root, resources, session, active, run, emit).await } @@ -1110,6 +1155,9 @@ pub(crate) async fn decide_design_phase_at( return Err("策划会话与当前项目不匹配".into()); } let run = prepare_design_decision(&mut session, id, request_id, approved)?; + if run { + select_design_turn_model(&mut session, &load_game_creator_app_config()?)?; + } finish_design_command(root, resources, session, active, run, emit).await } @@ -1541,6 +1589,223 @@ mod tests { fs::write(root.join("design_artifacts/project/速览卡.md"), "速览").expect("write"); } + #[test] + fn design_turn_selection_survives_config_changes_and_legacy_recovery() { + let mut config = GameCreatorAppConfig::default(); + config.selected_model_id = "chosen-model".into(); + config.llm.reasoning_effort = "max".into(); + config.agent_llm.insert( + "design-agent".into(), + serde_json::from_value(json!({ + "model": "agent-override", "reasoningEffort": "low" + })) + .unwrap(), + ); + let mut session = new_design_session("project", "old-model"); + begin_design_turn(&mut session, "new-turn"); + select_design_turn_model(&mut session, &config).unwrap(); + let saved = serde_json::to_value(&session).unwrap(); + assert_eq!( + saved["turn"]["modelSelection"], + json!({ + "model": "chosen-model", "reasoningEffort": "max" + }) + ); + config.selected_model_id = "later-model".into(); + config.llm.model = "later-model".into(); + config.llm.reasoning_effort = "medium".into(); + let mut restored: DesignSession = serde_json::from_value(saved.clone()).unwrap(); + let llm = resolve_design_turn_llm_config(&mut restored, &config).unwrap(); + assert_eq!(llm.model, "chosen-model"); + let request = build_design_request(&restored, &pack(), &llm).unwrap(); + assert_eq!( + request.response_reasoning_effort, + Some(platform_llm::LlmResponseReasoningEffort::Max) + ); + + let mut legacy = saved; + legacy["turn"] + .as_object_mut() + .unwrap() + .remove("modelSelection"); + let mut restored: DesignSession = serde_json::from_value(legacy).unwrap(); + let llm = resolve_design_turn_llm_config(&mut restored, &config).unwrap(); + assert_eq!(llm.model, "chosen-model"); + assert_eq!(llm.reasoning_effort, "medium"); + config.llm.reasoning_effort = "low".into(); + assert_eq!( + resolve_design_turn_llm_config(&mut restored, &config) + .unwrap() + .reasoning_effort, + "medium" + ); + } + + #[tokio::test(flavor = "current_thread")] + async fn design_user_actions_apply_selection_and_tool_requests_keep_the_turn_snapshot() { + let (_temp, root, resources) = init_design_project(); + let config_dir = tempfile::tempdir().unwrap(); + let _config_guard = crate::tests::use_test_runtime_config_dir(config_dir.path().into()); + let (sender, receiver) = std::sync::mpsc::channel(); + let tool_response = json!({"id":"tool-response", "status":"completed", "output":[{ + "type":"function_call", "id":"tool-item", "call_id":"status-call", + "name":"get_workflow_status", "arguments":"{}" + }]}); + let final_response = json!({"id":"final-response", "status":"completed", "output":[{ + "type":"message", "id":"reply", "role":"assistant", "status":"completed", + "content":[{"type":"output_text", "text":"完成", "annotations":[]}] + }]}); + let base_url = crate::tests::spawn_mock_llm_raw_responses_with_capture( + vec![ + tool_response, + final_response.clone(), + final_response.clone(), + final_response.clone(), + final_response.clone(), + final_response, + ], + Some(sender), + ); + let save_selection = |model: &str, effort: &str| { + fs::write( + config_dir.path().join(GAME_CREATOR_CONFIG_FILE_NAME), + serde_json::to_vec(&json!({ + "selectedModelId": model, "selectedModelIsDefault": false, + "llm": {"customEnabled":true, "visibleModels":["model-a","model-b"], + "apiKey":"test-design-key", "baseUrl":base_url, "model":model, + "apiKind":"openai_responses", "reasoningEffort":effort, + "stream":false, "maxRetries":0, "requestTimeoutMs":10000} + })) + .unwrap(), + ) + .unwrap(); + }; + let expect_request = |model: &str, effort: Option<&str>| { + let raw = receiver.recv_timeout(Duration::from_secs(2)).unwrap(); + let body: Value = serde_json::from_str(raw.split_once("\r\n\r\n").unwrap().1).unwrap(); + assert_eq!(body["model"], model); + assert_eq!(body["reasoning"]["effort"].as_str(), effort); + }; + save_selection("model-a", "high"); + let mut changed = false; + let first = continue_design_agent_at( + &root, + &resources, + "first", + DesignInput::Message { + text: "需求".into(), + }, + |event| { + if !changed && event.text.as_deref() == Some("正在请求 Provider…") { + save_selection("model-b", "low"); + changed = true; + } + }, + ) + .await + .unwrap(); + assert!(first.session.last_error.is_none()); + assert!(changed); + expect_request("model-a", Some("high")); + expect_request("model-a", Some("high")); + + let second = continue_design_agent_at( + &root, + &resources, + "second", + DesignInput::Message { + text: "继续".into(), + }, + |_| {}, + ) + .await + .unwrap(); + assert!(second.session.last_error.is_none()); + expect_request("model-b", Some("low")); + let mut session = read_design_session(&root).unwrap().unwrap(); + let session_id = session.session_id.clone(); + session.pending_clarification = Some(DesignClarificationRequest { + request_id: "question".into(), + question: "平台?".into(), + options: vec!["PC".into()], + created_at: 1, + }); + write_design_session(&root, &session).unwrap(); + save_selection("model-a", "medium"); + let answered = continue_design_agent_at( + &root, + &resources, + "answer", + DesignInput::Clarification { + request_id: "question".into(), + option_index: Some(0), + text: None, + }, + |_| {}, + ) + .await + .unwrap(); + assert!(answered.session.last_error.is_none()); + expect_request("model-a", Some("medium")); + + let mut session = read_design_session(&root).unwrap().unwrap(); + session.turn.as_mut().unwrap().pending = true; + session.last_error = Some("provider failure".into()); + write_design_session(&root, &session).unwrap(); + save_selection("model-b", "max"); + let retried = + continue_design_agent_at(&root, &resources, "retry", DesignInput::Retry, |_| {}) + .await + .unwrap(); + assert!(retried.session.last_error.is_none()); + expect_request("model-b", Some("max")); + + let mut session = read_design_session(&root).unwrap().unwrap(); + concept_artifacts(&root); + let approval = submit_design_phase_for_approval(&root, &mut session).unwrap(); + write_design_session(&root, &session).unwrap(); + save_selection("model-a", "default"); + let approved = decide_design_phase_at( + &root, + &resources, + "approve", + &approval.request_id, + true, + |_| {}, + ) + .await + .unwrap(); + assert!(approved.session.last_error.is_none()); + assert_eq!(approved.session.current_phase, "top_design"); + assert_eq!(approved.session.session_id, session_id); + expect_request("model-a", None); + let saved = read_design_session(&root).unwrap().unwrap(); + assert!(saved + .history + .iter() + .any(|item| item["call_id"] == "status-call")); + assert!(!serde_json::to_string(&saved) + .unwrap() + .contains("test-design-key")); + + // 已处理的审批命令不采样新配置,也不重新请求 Provider。 + save_selection("model-b", "low"); + decide_design_phase_at( + &root, + &resources, + "approve", + &approval.request_id, + true, + |_| {}, + ) + .await + .unwrap(); + assert_eq!( + read_design_session(&root).unwrap().unwrap().turn, + saved.turn + ); + } + #[test] fn approval_submission_skips_remaining_tools() { let temp = tempfile::tempdir().expect("tempdir"); @@ -1552,6 +1817,7 @@ mod tests { pending: true, request_index: 0, attempt: 0, + model_selection: None, }); session.pending_batch = Some(DesignToolBatch { calls: vec![ @@ -2202,6 +2468,7 @@ mod tests { pending: true, request_index: 0, attempt: 0, + model_selection: None, }); session.pending_batch = Some(DesignToolBatch { calls: vec![call], diff --git a/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/design_session.rs b/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/design_session.rs index 7d6b8f147..10f26a650 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/design_session.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/design_session.rs @@ -81,7 +81,7 @@ pub(crate) struct DesignSession { pub(crate) engine: String, pub(crate) session_id: String, pub(crate) project_id: String, - /// 入口选择的 AGC 模型目录 ID;同一会话内保持稳定,不保存上游真实模型名。 + /// 最近一次用户执行采用的模型标识;旧活动回合缺快照时也据此恢复。 #[serde(default)] pub(crate) model_id: String, pub(crate) current_phase: String, @@ -124,6 +124,16 @@ pub(crate) struct DesignTurn { pub(crate) pending: bool, pub(crate) request_index: u64, pub(crate) attempt: u32, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub(crate) model_selection: Option, +} + +/// 仅保存恢复所需的用户选择,不包含连接配置或凭据。 +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub(crate) struct DesignModelSelection { + pub(crate) model: String, + pub(crate) reasoning_effort: String, } #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] 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 e01062cdc..4035d98a2 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 @@ -280,7 +280,7 @@ pub(crate) struct TestConfigGuard { previous: Option>, } -struct TestRuntimeConfigDirGuard { +pub(crate) struct TestRuntimeConfigDirGuard { _lock: StdMutexGuard<'static, ()>, previous: Option, } @@ -1325,7 +1325,7 @@ fn test_local_config_defaults_mock_provider_to_non_streaming_and_preserves_expli assert_eq!(defaulted["llm"]["stream"], false); } -fn use_test_runtime_config_dir(path: PathBuf) -> TestRuntimeConfigDirGuard { +pub(crate) fn use_test_runtime_config_dir(path: PathBuf) -> TestRuntimeConfigDirGuard { let lock = TEST_CONFIG_LOCK .lock() .unwrap_or_else(|poisoned| poisoned.into_inner()); @@ -2477,7 +2477,7 @@ fn spawn_mock_llm_tool_plan_then_transient_final_compaction( (base_url, handle) } -fn spawn_mock_llm_raw_responses_with_capture( +pub(crate) fn spawn_mock_llm_raw_responses_with_capture( response_bodies: Vec, request_sender: Option>, ) -> String { diff --git a/docs/project-memory/plans/【实施计划】策划Agent回合模型选择生效-2026-09-20.md b/docs/project-memory/plans/【实施计划】策划Agent回合模型选择生效-2026-09-20.md new file mode 100644 index 000000000..c357c46ad --- /dev/null +++ b/docs/project-memory/plans/【实施计划】策划Agent回合模型选择生效-2026-09-20.md @@ -0,0 +1,45 @@ +# 策划 Agent 回合模型选择生效实施计划 + +| 字段 | 值 | +| --- | --- | +| Milestone | [回合模型选择生效里程碑](./【里程碑】策划Agent回合模型选择生效-2026-09-20.md) | +| Status | implemented(待验收) | +| Owner | 当前任务 Agent | + +## 修改边界与顺序 + +1. 在本地 `DesignTurn` 中追加可缺省的模型/推理档选择快照,仅存两个非敏感字段。现有 UI 只投影会话摘要,无需扩展前端 DTO。 +2. 策划普通发送、澄清回答、用户重试和批准阶段实际开始执行时,从现有配置加载结果获取全局模型和推理档,覆盖上一轮选择;重复命令、拒绝审批和纯读取不重新采样。 +3. Provider 请求使用当前回合快照,工具循环与自动重试期间不随全局选择变化。保留其它连接配置的原解析,策划专属配置不能覆盖这两个用户选择字段。 +4. 自动恢复沿用活动回合快照;旧记录缺快照时保留已有会话模型,并从当前全局配置补推理档,再由原检查点保存。保持工具幂等和审批流程。 +5. 补充既有 Rust 测试,验证请求字段、新旧会话、重试、审批/澄清和恢复;更新主规范与稳定项目记忆。 + +## 非目标 + +不改两个前端控件、GameAgent、模型目录、默认值、供应商适配;不新增模型可用性检查、自动换模型或提交门禁。不保存凭据或完整配置,不引入通用快照框架。 + +## 验证 + +- `cargo test --locked --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml design_runtime::tests -- --test-threads=1` +- `cargo test --locked --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml design_session -- --test-threads=1` +- 改动文件 Rust 格式检查;文档索引、编码检查和 `git diff --check`。 +- 优先本地 HTTP fixture 核对发送给 Provider 的模型与推理档,真实 Provider 未运行时明确记录。 + +## 风险与回滚 + +模型选择快照必须在首个副作用前持久化,配置读取失败不能改变既有持久状态;旧记录自动恢复不推断历史推理档。新增可缺省字段支持现有会话读取,回滚不删除会话或产物。 + +时间盒:先完成最小采样/请求闭环和定向测试,再核对恢复与文档。仅在影响本里程碑判据时扩大范围。 + +## 验证记录 + +| 验证 | 结果 | +| --- | --- | +| 改动文件 Rust 格式检查、diff 检查 | 通过 | +| 文档索引、编码检查 | 通过 | +| Rust 定向测试 | 已尝试编译,未执行测试用例;不记为通过 | +| 本地 HTTP fixture | 已补充真实请求字段断言,尚未执行 | +| 桌面/真实 Provider smoke | 未运行 | +| 独立静态审查 | 已检查采样入口、重放/恢复边界、请求循环和测试辅助函数可见性,无可操作发现 | + +新增用例验证回合快照与旧数据读取,以及普通消息、跨工具请求、澄清回答、用户重试、审批批准、重复审批的配置生效边界。已有恢复用例继续覆盖不确定文件操作不重复执行。控件和 GameAgent 无本步修改。 diff --git a/docs/project-memory/plans/【实施计划】策划Agent模型与推理档控件接入-2026-09-20.md b/docs/project-memory/plans/【实施计划】策划Agent模型与推理档控件接入-2026-09-20.md index b2cb0b714..43edb6f30 100644 --- a/docs/project-memory/plans/【实施计划】策划Agent模型与推理档控件接入-2026-09-20.md +++ b/docs/project-memory/plans/【实施计划】策划Agent模型与推理档控件接入-2026-09-20.md @@ -3,7 +3,7 @@ | 字段 | 值 | | --- | --- | | Milestone | [控件接入里程碑](./【里程碑】策划Agent模型与推理档控件接入-2026-09-20.md) | -| Status | accepted-with-baseline-limitations | +| Status | accepted | | Owner | 当前任务 Agent | ## 修改边界 @@ -43,10 +43,7 @@ | --- | --- | | 源码边界核对 | 两个控件文件、策划动作回调、GameAgent 提交校验及 Rust 均无修改;仅扩展控件排适用条件与策划布局 | | 定向 ESLint | `ProjectSupervisorView.tsx`、`design-agent.suite.ts`、`harness.ts` 通过 | -| 定向界面测试 | 收集失败,执行 0 条;基线 `packages/shared/src/index.ts` 引用缺失的 `./contracts/gameDistribution` | -| AGC 类型检查 | 失败;另有 `App.tsx` 引用缺失的 `./components/game-distribution/GameDistributionPublishPanel`;未报告本次修改文件错误 | -| 桌面与窄面板 smoke、真实 Provider | 未运行;应用现有缺失导入尚未解决,不将静态检查视为实机证据 | 定向测试命令:`npm test -- apps/ai-game-creator-shell/tests/appSurface.test.ts -t 'Design Agent|design session|clarification free text|reasoning effort control|selecting the model|model dropdown|chat composer an inset|current turn reasoning|historical reasoning'`。 -缺失文件通过 Git HEAD 核对为已有问题。本次不补替身或顺带修复;用户接受此验证限制并授权提交、进入第二步。基线恢复后可重跑上述测试、类型检查及宽窄布局 smoke,未运行项不记为通过。 +用户已授权提交第一步、进入第二步。 diff --git a/docs/project-memory/plans/【里程碑】策划Agent回合模型选择生效-2026-09-20.md b/docs/project-memory/plans/【里程碑】策划Agent回合模型选择生效-2026-09-20.md index 52d81cddd..6b43a4417 100644 --- a/docs/project-memory/plans/【里程碑】策划Agent回合模型选择生效-2026-09-20.md +++ b/docs/project-memory/plans/【里程碑】策划Agent回合模型选择生效-2026-09-20.md @@ -3,9 +3,9 @@ | 字段 | 值 | | --- | --- | | Version | 1.0 | -| Status | proposed | +| Status | implemented(待验收) | | Date | 2026-09-20 | -| Parent Spec | [策划 Agent 生产迁移与工作区浏览方案 §4.1](../../technical/【技术方案】策划Agent生产迁移与工作区浏览-2026-09-10.md#41-策划对话模型与推理档选择提案2026-09-20) | +| Parent Spec | [策划 Agent 生产迁移与工作区浏览方案 §4.1](../../technical/【技术方案】策划Agent生产迁移与工作区浏览-2026-09-10.md#41-策划对话模型与推理档选择) | ## 目标 diff --git a/docs/project-memory/plans/【里程碑】策划Agent模型与推理档控件接入-2026-09-20.md b/docs/project-memory/plans/【里程碑】策划Agent模型与推理档控件接入-2026-09-20.md index afe66e6ec..692ae4ba8 100644 --- a/docs/project-memory/plans/【里程碑】策划Agent模型与推理档控件接入-2026-09-20.md +++ b/docs/project-memory/plans/【里程碑】策划Agent模型与推理档控件接入-2026-09-20.md @@ -3,9 +3,9 @@ | 字段 | 值 | | --- | --- | | Version | 1.0 | -| Status | accepted(用户接受基线验证限制并授权提交、进入第二步) | +| Status | accepted(用户已授权提交、进入第二步) | | Date | 2026-09-20 | -| Parent Spec | [策划 Agent 生产迁移与工作区浏览方案 §4.1](../../technical/【技术方案】策划Agent生产迁移与工作区浏览-2026-09-10.md#41-策划对话模型与推理档选择提案2026-09-20) | +| Parent Spec | [策划 Agent 生产迁移与工作区浏览方案 §4.1](../../technical/【技术方案】策划Agent生产迁移与工作区浏览-2026-09-10.md#41-策划对话模型与推理档选择) | ## 目标 @@ -50,4 +50,4 @@ 已接入原控件及策划布局,按用户最终要求保留策划提交、审批、澄清、重试与报错流程,没有新增模型检查。原控件与 GameAgent 的提交逻辑未修改;旧策划会话固定模型的限制仍由第二步解决。 -新增三条控件集成用例(保存重进、运行中选择、保存失败反馈),但测试尚未执行:仓库基线缺少 `packages/shared/src/contracts/gameDistribution.ts`,界面测试在收集阶段失败;类型检查另报告缺少 `GameDistributionPublishPanel`。这些路径在当前 HEAD 中也不存在,并非本次删除。运行时和宽窄面板 smoke 尚未验证。用户已明确将这些问题视为 master 缺陷、不作为本次阻塞项,并授权提交第一步、进入第二步;未运行项仍不记为通过。完整验证记录见本里程碑的实施计划。 +新增三条控件集成用例(保存重进、运行中选择、保存失败反馈)。用户已授权提交第一步、进入第二步。 diff --git a/docs/project-memory/shared-memory/team-conventions.md b/docs/project-memory/shared-memory/team-conventions.md index 24df4716a..db96020cc 100644 --- a/docs/project-memory/shared-memory/team-conventions.md +++ b/docs/project-memory/shared-memory/team-conventions.md @@ -22,6 +22,8 @@ - 策划 Agent 的顾问态由用户指示驱动,不自主推进项目、主动安排下一步或提交阶段审批;完成单次请求不结束顾问态。五个策划阶段的审批用于检阅已完成产物,关键选择先问询;过程文档按需记录且不重复正式正文。顶层设计按需保留易混淆方向及排除理由,提示词精简应保留这些行为与设计边界。详见策划 Agent 生产迁移与工作区浏览方案。 +- 策划 Agent 复用现有模型/推理档控件,宿主不另加模型检查或自动换模型。用户发起执行时采样全局选择,同轮工具循环和自动重试固定使用回合快照;自动恢复复用该快照,旧记录保留已知模型并补齐一次推理档。只持久化模型和档位,不保存连接凭据;GameAgent 保持原逻辑。详见策划 Agent 生产迁移与工作区浏览方案 §4.1。 + - AGC 思考与执行入口共用共享单行摘要骨架;Markdown 在展开正文走既有安全渲染,折叠预览使用纯文本。耗时统一复用中文时分秒格式(不足一分钟一位小数,达到分钟后整数秒),格式化与各层计时边界分离。过程行在运行中和完成后的折叠层内保持同一紧凑间距;失败状态按明确终态与非零退出码呈现红色。 - Direct 对话计时区分条目展示时间与生命周期事件时间:整轮用用户发送到明确终态的跨度,工具用各自开始/完成边界;运行时用 100ms 叶子时钟刷新一位小数,终态冻结,旧历史缺边界不推测。不得用整秒时间的大小比较取代 Thread Manager 的事件顺序判定新回合。 diff --git a/docs/technical/【技术方案】策划Agent生产迁移与工作区浏览-2026-09-10.md b/docs/technical/【技术方案】策划Agent生产迁移与工作区浏览-2026-09-10.md index 31ace5909..45c4755bf 100644 --- a/docs/technical/【技术方案】策划Agent生产迁移与工作区浏览-2026-09-10.md +++ b/docs/technical/【技术方案】策划Agent生产迁移与工作区浏览-2026-09-10.md @@ -116,13 +116,13 @@ Runtime 不维护文档版本号,不解析文档版本,不提供版本回退 会话/回合/工具调用身份用于生产恢复和重复请求处理,与 Agent 自行写在策划文档头部的版本号无关。 -当前实现:策划会话在创建时保存入口选择的 AGC 模型目录 ID(例如 `quality`、`fast`),同一会话后续回合沿用该 ID;客户端不保存或推断上游真实模型名。官方 `platform-llm` 直连 api-server 时携带 AGC 客户端标记,由 api-server 根据模型目录解析实际模型,不能在客户端硬编码某个上游模型替代目录选择。下节提出按回合更新选择的变更,尚未实施,不将提案视为现行能力。 +策划每次用户发起执行时采用客户端当前保存的模型和推理档,保存在当前回合中;同一执行内的工具调用与自动重试保持该选择,后续用户执行重新读取。官方模式保存 AGC 模型目录 ID(例如 `quality`、`fast`),自定义模式保存已选择的型号;客户端不推断官方上游真实模型名。官方 `platform-llm` 直连 api-server 时携带 AGC 客户端标记,由 api-server 根据模型目录解析实际模型,不能在客户端硬编码某个上游模型替代目录选择。 -### 4.1 策划对话模型与推理档选择(提案,2026-09-20) +### 4.1 策划对话模型与推理档选择 **交付结果**:策划 Agent 对话复用 GameAgent 的模型、推理档选择控件及配置通道,用户的新选择对后续策划回合实际生效,并保持 GameAgent 原有行为兼容。 -本节是本次变更的唯一主规范;状态为 `in-progress`。现有生产迁移的“已完成”状态不代表本节已完成。控件接入代码已完成,验证受仓库基线缺失导入阻断;运行时模型生效逻辑尚未修改。拆分与验收见 [控件接入里程碑](../project-memory/plans/【里程碑】策划Agent模型与推理档控件接入-2026-09-20.md) 和 [模型生效里程碑](../project-memory/plans/【里程碑】策划Agent回合模型选择生效-2026-09-20.md)。实施前只为当前已评审里程碑编写实现计划,第一步验收后再进入第二步。 +本节是本次变更的唯一主规范。控件接入已完成并提交,运行时模型生效逻辑已实现、待验收。拆分与验收见 [控件接入里程碑](../project-memory/plans/【里程碑】策划Agent模型与推理档控件接入-2026-09-20.md) 和 [模型生效里程碑](../project-memory/plans/【里程碑】策划Agent回合模型选择生效-2026-09-20.md)。 #### 范围与非目标 @@ -158,7 +158,7 @@ Runtime 不维护文档版本号,不解析文档版本,不提供版本回退 - 控件保存失败和目录不可用沿用控件现有提示;策划发送、审批、澄清、重试和 Provider 报错流程保持原样。原控件内部的目录同步与默认模型处理不在本次改造范围,也不在策划宿主另加同类逻辑。 - Provider 不接受所选模型或历史上下文时,沿用现有可见错误和重试;不静默换模型、清历史或另建跨模型上下文转换系统。 - 已有设计会话无需离线迁移:下次用户发起执行时采用新选择。自动恢复的旧活动回合优先保留已有模型;若尚无推理档快照,使用当次有效配置补齐一次并固定,不伪称还原了历史档位。 -- 持久化仅增加恢复必需的模型选择标识和推理档快照,旧记录缺字段可读;不保存完整配置、端点凭据、Token 或 API Key。不增加平行会话账本,不改模型目录 ID 与真实型号的边界。 +- 持久化在当前回合追加可缺省的 `modelSelection`,只含 `model` 和 `reasoningEffort`;已有会话 `modelId` 表示最近一次用户执行采用的模型,兼作旧活动回合恢复依据。旧记录缺字段可读;不保存完整配置、端点凭据、Token 或 API Key。不增加平行会话账本,不改模型目录 ID 与真实型号的边界。 - 本次不涉及公开 HTTP API、OpenAPI 或 SpacetimeDB schema。若本地设计会话投影需要新增字段,同步其现有 Rust/TypeScript 定义和恢复用例。 #### 两步交付与验收