补齐 DirectProject 更早历史入口
补充 DirectProject 初始历史 hasMore 时的按钮与滚动入口 修正 rebase 后 Tauri 命令与 Rust 测试辅助函数可见性 同步更新 DirectProject 历史恢复规范与里程碑验收记录
This commit is contained in:
+5
-2
@@ -20,7 +20,7 @@ pub(crate) fn direct_codex_canonical_project_identity(
|
||||
))
|
||||
}
|
||||
|
||||
fn direct_codex_os_path_identity_bytes(path: &std::path::Path) -> Vec<u8> {
|
||||
pub(super) fn direct_codex_os_path_identity_bytes(path: &std::path::Path) -> Vec<u8> {
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
@@ -39,7 +39,10 @@ fn direct_codex_os_path_identity_bytes(path: &std::path::Path) -> Vec<u8> {
|
||||
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());
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -5,7 +5,9 @@
|
||||
|
||||
use super::*;
|
||||
|
||||
fn normalize_direct_client_turn_id(client_turn_id: Option<&str>) -> Result<String, String> {
|
||||
pub(crate) fn normalize_direct_client_turn_id(
|
||||
client_turn_id: Option<&str>,
|
||||
) -> Result<String, String> {
|
||||
let Some(client_turn_id) = client_turn_id else {
|
||||
return Err("Direct 客户端回合缺少稳定 clientTurnId,已拒绝创建可计费生成身份".to_string());
|
||||
};
|
||||
|
||||
@@ -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<DirectThreadSubscriptionBootstrap, String> {
|
||||
|
||||
@@ -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<HTMLElement>) {
|
||||
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}
|
||||
|
||||
@@ -85,6 +85,7 @@ type ProjectSupervisorViewProps = RuntimePanelProps & {
|
||||
directProcessDetail?: string;
|
||||
directProcessKey?: string;
|
||||
hiddenConversationCount: number;
|
||||
hasEarlierConversationMessages?: boolean;
|
||||
messagesRef: RefObject<HTMLDivElement | null>;
|
||||
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 ? (
|
||||
<button
|
||||
type="button"
|
||||
className="message-history-more"
|
||||
onClick={onShowEarlierMessages}
|
||||
>
|
||||
{`显示更早 · 还有 ${hiddenConversationCount} 条对话`}
|
||||
{hiddenConversationCount > 0
|
||||
? `显示更早 · 还有 ${hiddenConversationCount} 条对话`
|
||||
: '显示更早的对话'}
|
||||
</button>
|
||||
) : null}
|
||||
{visibleMessages.map((message, index) => (
|
||||
|
||||
+6
-2
@@ -157,6 +157,7 @@ type ProjectWorkspaceChatPaneProps = {
|
||||
handleRevealCurrentProjectDirectory: () => Promise<void>;
|
||||
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({
|
||||
</div>
|
||||
) : null}
|
||||
<div className="message-list" onScroll={handleConversationScroll}>
|
||||
{hiddenConversationCount > 0 ? (
|
||||
{hiddenConversationCount > 0 || hasEarlierConversationMessages ? (
|
||||
<button
|
||||
type="button"
|
||||
className="message-history-more"
|
||||
onClick={showEarlierConversationMessages}
|
||||
>
|
||||
{`显示更早 · 还有 ${hiddenConversationCount} 条对话`}
|
||||
{hiddenConversationCount > 0
|
||||
? `显示更早 · 还有 ${hiddenConversationCount} 条对话`
|
||||
: '显示更早的对话'}
|
||||
</button>
|
||||
) : null}
|
||||
{visibleMessages.map((message, index) => (
|
||||
|
||||
+8
-2
@@ -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 ? (
|
||||
<button
|
||||
type="button"
|
||||
className="message-history-more"
|
||||
onClick={onShowEarlierMessages}
|
||||
>{`显示更早 · 还有 ${hiddenConversationCount} 条对话`}</button>
|
||||
>
|
||||
{hiddenConversationCount > 0
|
||||
? `显示更早 · 还有 ${hiddenConversationCount} 条对话`
|
||||
: '显示更早的对话'}
|
||||
</button>
|
||||
) : null}
|
||||
{visibleMessages.map((message, index) => (
|
||||
<div
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
2. 将 app-server 公开事件安全标准化后接入 Thread Manager;在 item 完成持久化成功后追加完成事件,并追加 turn 生命周期事件。
|
||||
3. 增加 Tauri `subscribe/consume/readHistory` 命令与 notify 事件,固定错误和 bootstrap 原子边界。
|
||||
4. 前端改为 subscriptionId 驱动的 raw event reducer;重进/过期时先 bootstrap,完成后原子替换;历史按 itemId 懒加载。
|
||||
5. 移除 DirectProject legacy conversation 读取分支,补齐契约、并发、恢复和失败关闭测试。
|
||||
5. 移除 DirectProject legacy conversation 读取分支,补齐契约、并发、恢复和失败关闭测试;让初始历史切片的 `hasMore` 独立驱动“显示更早”按钮和滚动入口。
|
||||
6. 每个独立切片分别运行定向验证并形成中文小提交;里程碑验收后再清理临时计划。
|
||||
|
||||
## 验证命令
|
||||
|
||||
@@ -44,6 +44,7 @@
|
||||
- [ ] subscribe 返回 item 历史锚点而不是完整 history;前端可按 itemId 懒加载。
|
||||
- [ ] 未完成 item 的每个 delta 可从 `item.started` 开始重放;不截断 active item。
|
||||
- [ ] legacy conversation 行直接失败关闭,无 fallback、无迁移。
|
||||
- [x] DirectProject 首页历史切片存在 `hasMore` 时,即使当前可见窗口没有隐藏消息,也提供按钮和滚动两种“显示更早”入口。
|
||||
|
||||
## 证据要求
|
||||
|
||||
|
||||
@@ -92,7 +92,7 @@ readHistory(threadId, { beforeItemId?, limit }) -> {
|
||||
}
|
||||
```
|
||||
|
||||
`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` 不重复返回同一批事件。
|
||||
|
||||
|
||||
Reference in New Issue
Block a user