From 3cd772b8750bfee31bbb92c8414e4482d0d98773 Mon Sep 17 00:00:00 2001 From: kdletters <61648117+kdletters@users.noreply.github.com> Date: Tue, 15 Sep 2026 21:53:57 +0800 Subject: [PATCH] =?UTF-8?q?=E8=A1=A5=E9=BD=90=20DirectProject=20=E6=9B=B4?= =?UTF-8?q?=E6=97=A9=E5=8E=86=E5=8F=B2=E5=85=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 补充 DirectProject 初始历史 hasMore 时的按钮与滚动入口 修正 rebase 后 Tauri 命令与 Rust 测试辅助函数可见性 同步更新 DirectProject 历史恢复规范与里程碑验收记录 --- .../agent/codex_app_server/direct_project_identity.rs | 7 +++++-- .../src-tauri/src/agent/direct_runtime/mod.rs | 2 +- .../src-tauri/src/agent/direct_runtime/user_input.rs | 4 +++- apps/ai-game-creator-shell/src-tauri/src/commands.rs | 2 ++ apps/ai-game-creator-shell/src/App.tsx | 8 +++++++- .../project-workspace/ProjectSupervisorView.tsx | 8 ++++++-- .../project-workspace/ProjectWorkspaceChatPane.tsx | 8 ++++++-- .../project-workspace/SupervisorChatOnlyView.tsx | 10 ++++++++-- ...划】DirectProject Thread Manager事件订阅-2026-09-15.md | 2 +- ...碑】DirectProject Thread Manager事件订阅-2026-09-15.md | 1 + ...案】DirectProject Codex原始历史与异常恢复-2026-09-04.md | 2 +- 11 files changed, 41 insertions(+), 13 deletions(-) diff --git a/apps/ai-game-creator-shell/src-tauri/src/agent/codex_app_server/direct_project_identity.rs b/apps/ai-game-creator-shell/src-tauri/src/agent/codex_app_server/direct_project_identity.rs index 5ec9f28a1..901467454 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/agent/codex_app_server/direct_project_identity.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/agent/codex_app_server/direct_project_identity.rs @@ -20,7 +20,7 @@ pub(crate) fn direct_codex_canonical_project_identity( )) } -fn direct_codex_os_path_identity_bytes(path: &std::path::Path) -> Vec { +pub(super) fn direct_codex_os_path_identity_bytes(path: &std::path::Path) -> Vec { #[cfg(unix)] { use std::os::unix::ffi::OsStrExt; @@ -39,7 +39,10 @@ fn direct_codex_os_path_identity_bytes(path: &std::path::Path) -> Vec { path.as_os_str().to_string_lossy().as_bytes().to_vec() } -fn direct_codex_project_identity_digest(path_identity: &[u8], project_id: &[u8]) -> String { +pub(super) fn direct_codex_project_identity_digest( + path_identity: &[u8], + project_id: &[u8], +) -> String { let mut digest = Sha256::new(); digest.update(b"genarrative-direct-project-identity.v1\0"); digest.update((path_identity.len() as u64).to_le_bytes()); 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 806014bdb..ee32bb97a 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 @@ -7,7 +7,7 @@ use std::path::{Path, PathBuf}; use std::sync::{Mutex, OnceLock}; mod user_input; -pub(crate) use user_input::chat_with_game_creator_direct_codex; +pub(crate) use user_input::{chat_with_game_creator_direct_codex, normalize_direct_client_turn_id}; const MAX_DIRECT_SYSTEM_PROMPT_CHARS: usize = 16 * 1024; const MIN_DIRECT_CLIENT_TURN_ID_CHARS: usize = 6; diff --git a/apps/ai-game-creator-shell/src-tauri/src/agent/direct_runtime/user_input.rs b/apps/ai-game-creator-shell/src-tauri/src/agent/direct_runtime/user_input.rs index 4188bfd8f..efdc044e7 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/agent/direct_runtime/user_input.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/agent/direct_runtime/user_input.rs @@ -5,7 +5,9 @@ use super::*; -fn normalize_direct_client_turn_id(client_turn_id: Option<&str>) -> Result { +pub(crate) fn normalize_direct_client_turn_id( + client_turn_id: Option<&str>, +) -> Result { let Some(client_turn_id) = client_turn_id else { return Err("Direct 客户端回合缺少稳定 clientTurnId,已拒绝创建可计费生成身份".to_string()); }; diff --git a/apps/ai-game-creator-shell/src-tauri/src/commands.rs b/apps/ai-game-creator-shell/src-tauri/src/commands.rs index f0558e956..a17b0e3f3 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/commands.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/commands.rs @@ -5277,7 +5277,9 @@ pub(crate) async fn read_agent_runtime_error_detail( }) .await .map_err(|error| format!("读取统一错误诊断后台任务失败:{error}"))? +} +#[tauri::command] pub(crate) async fn subscribe_direct_project_thread( project_path: String, ) -> Result { diff --git a/apps/ai-game-creator-shell/src/App.tsx b/apps/ai-game-creator-shell/src/App.tsx index d5fb40130..9d847517d 100644 --- a/apps/ai-game-creator-shell/src/App.tsx +++ b/apps/ai-game-creator-shell/src/App.tsx @@ -11885,6 +11885,9 @@ export function App({ 0, messages.length - visibleMessages.length, ); + const hasEarlierConversationMessages = + hiddenConversationCount > 0 || + (directCodexProductRuntime && directHistoryHasMore); const projectSupervisorTransientReply = projectSupervisorResponseStream?.accumulatedText.trim() ?? ''; const projectSupervisorNeedsUserInput = agentRuntimeNeedsUserInput( @@ -11981,7 +11984,7 @@ export function App({ } function handleConversationScroll(event: UIEvent) { - if (hiddenConversationCount === 0) { + if (!hasEarlierConversationMessages) { return; } if (event.currentTarget.scrollTop <= 24) { @@ -12147,6 +12150,7 @@ export function App({ onOpenRuntimeConfig={() => setRuntimeConfigOpen(true)} onScroll={handleSupervisorChatScroll} onShowEarlierMessages={showEarlierConversationMessages} + hasEarlierConversationMessages={hasEarlierConversationMessages} onSubmit={handleProjectSupervisorOnlySubmit} onToolAction={handleProjectSupervisorToolAction} onUserInput={handleProjectSupervisorUserInput} @@ -12191,6 +12195,7 @@ export function App({ } directProcessKey={directCodexProcessKey} hiddenConversationCount={hiddenConversationCount} + hasEarlierConversationMessages={hasEarlierConversationMessages} messagesRef={supervisorChatMessagesRef} needsUserInput={ directCodexProductRuntime ? false : projectSupervisorNeedsUserInput @@ -12426,6 +12431,7 @@ export function App({ } handleRuntimeConfigOpen={handleRuntimeConfigOpen} hiddenConversationCount={hiddenConversationCount} + hasEarlierConversationMessages={hasEarlierConversationMessages} llmConfigStatus={llmConfigStatus} loadProjectConversation={loadProjectConversation} localProject={localProject} diff --git a/apps/ai-game-creator-shell/src/features/project-workspace/ProjectSupervisorView.tsx b/apps/ai-game-creator-shell/src/features/project-workspace/ProjectSupervisorView.tsx index 096a53a00..655d5cef5 100644 --- a/apps/ai-game-creator-shell/src/features/project-workspace/ProjectSupervisorView.tsx +++ b/apps/ai-game-creator-shell/src/features/project-workspace/ProjectSupervisorView.tsx @@ -85,6 +85,7 @@ type ProjectSupervisorViewProps = RuntimePanelProps & { directProcessDetail?: string; directProcessKey?: string; hiddenConversationCount: number; + hasEarlierConversationMessages?: boolean; messagesRef: RefObject; needsUserInput: boolean; onCancelConfirmation: () => void; @@ -139,6 +140,7 @@ export function ProjectSupervisorView({ directProcessDetail = '', directProcessKey = '', hiddenConversationCount, + hasEarlierConversationMessages = false, messagesRef, needsUserInput, onCancelConfirmation, @@ -250,13 +252,15 @@ export function ProjectSupervisorView({ aria-label={directCodex ? '陶泥儿消息' : '项目总控消息'} onScroll={onScroll} > - {hiddenConversationCount > 0 ? ( + {hiddenConversationCount > 0 || hasEarlierConversationMessages ? ( ) : null} {visibleMessages.map((message, index) => ( diff --git a/apps/ai-game-creator-shell/src/features/project-workspace/ProjectWorkspaceChatPane.tsx b/apps/ai-game-creator-shell/src/features/project-workspace/ProjectWorkspaceChatPane.tsx index dd699846a..05e7c1056 100644 --- a/apps/ai-game-creator-shell/src/features/project-workspace/ProjectWorkspaceChatPane.tsx +++ b/apps/ai-game-creator-shell/src/features/project-workspace/ProjectWorkspaceChatPane.tsx @@ -157,6 +157,7 @@ type ProjectWorkspaceChatPaneProps = { handleRevealCurrentProjectDirectory: () => Promise; handleRuntimeConfigOpen: () => void; hiddenConversationCount: number; + hasEarlierConversationMessages?: boolean; llmConfigStatus: GameCreatorLlmConfigStatus | null; loadProjectConversation: ( nextProjectPath: string, @@ -256,6 +257,7 @@ export function ProjectWorkspaceChatPane({ handleRevealCurrentProjectDirectory, handleRuntimeConfigOpen, hiddenConversationCount, + hasEarlierConversationMessages = false, llmConfigStatus, loadProjectConversation, localProject, @@ -809,13 +811,15 @@ export function ProjectWorkspaceChatPane({ ) : null}
- {hiddenConversationCount > 0 ? ( + {hiddenConversationCount > 0 || hasEarlierConversationMessages ? ( ) : null} {visibleMessages.map((message, index) => ( diff --git a/apps/ai-game-creator-shell/src/features/project-workspace/SupervisorChatOnlyView.tsx b/apps/ai-game-creator-shell/src/features/project-workspace/SupervisorChatOnlyView.tsx index c4aa1640c..5e4a391e5 100644 --- a/apps/ai-game-creator-shell/src/features/project-workspace/SupervisorChatOnlyView.tsx +++ b/apps/ai-game-creator-shell/src/features/project-workspace/SupervisorChatOnlyView.tsx @@ -71,6 +71,7 @@ type SupervisorChatOnlyViewProps = { transientReply: string; hasConversationControls: boolean; hiddenConversationCount: number; + hasEarlierConversationMessages?: boolean; needsUserInput: boolean; visibleMessages: ChatMessage[]; workspaceStatus: string; @@ -108,6 +109,7 @@ export function SupervisorChatOnlyView({ transientReply, hasConversationControls, hiddenConversationCount, + hasEarlierConversationMessages = false, needsUserInput, visibleMessages, workspaceStatus, @@ -188,12 +190,16 @@ export function SupervisorChatOnlyView({ aria-label={directCodex ? '陶泥儿消息' : '项目总控消息'} onScroll={handleScroll} > - {hiddenConversationCount > 0 ? ( + {hiddenConversationCount > 0 || hasEarlierConversationMessages ? ( + > + {hiddenConversationCount > 0 + ? `显示更早 · 还有 ${hiddenConversationCount} 条对话` + : '显示更早的对话'} + ) : null} {visibleMessages.map((message, index) => (
{ } ``` -`subscribe` 不返回完整历史。`lastCompletedItemId` 只是历史读取锚点,前端自行按 item ID 懒加载需要的历史切片。`events` 是当前运行态重建所需的未完成 item 原始事件,以及当前 turn 的生命周期锚点;前端用同一个 reducer 重放 bootstrap 和后续事件。Rust 不保存或理解前端 reducer state。 +`subscribe` 不返回完整历史。`lastCompletedItemId` 只是历史读取锚点,前端自行按 item ID 懒加载需要的历史切片。`events` 是当前运行态重建所需的未完成 item 原始事件,以及当前 turn 的生命周期锚点;前端用同一个 reducer 重放 bootstrap 和后续事件。Rust 不保存或理解前端 reducer state。只要 DirectProject 历史切片返回 `hasMore`,聊天视图必须显示“显示更早的对话”入口,并允许按钮或滚动触发下一页,即使当前可见消息窗口没有隐藏消息。 `consume` 不接收或返回 cursor。每个 subscriber 在 Rust 内部持有自己的 cursor,并在加锁的临界区内完成过期判断、读取和 cursor 前进。前端只持有 `subscriptionId` 与 reducer state。并发 `consume` 不重复返回同一批事件。