From dd339638ccfca29b70ffa2dfa5606d3682fc2741 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E5=BE=B7=E5=AE=87?= Date: Thu, 17 Sep 2026 14:38:22 +0800 Subject: [PATCH] =?UTF-8?q?Revert=20"=E4=BF=AE=E5=A4=8D=E5=8E=86=E5=8F=B2?= =?UTF-8?q?=E9=87=8D=E8=AF=BB=E5=90=8E=E6=97=A7=E6=B6=88=E6=81=AF=E8=BF=BD?= =?UTF-8?q?=E5=8A=A0=E5=88=B0=E6=9C=AB=E5=B0=BE=E7=9A=84=E4=B9=B1=E5=BA=8F?= =?UTF-8?q?"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 036395c6c979dad4091031c6942c95219d125b2c. --- apps/ai-game-creator-shell/src/App.tsx | 7 ++-- apps/ai-game-creator-shell/src/app/types.ts | 2 - .../tests/directHistoryPagination.test.tsx | 37 ------------------- ...实施计划】AGC对话历史分页恢复-2026-09-16.md | 1 - ...里程碑】AGC对话历史分页恢复-2026-09-16.md | 1 - ...案】AI游戏创作智能体App实施计划-2026-06-24.md | 1 - 6 files changed, 3 insertions(+), 46 deletions(-) diff --git a/apps/ai-game-creator-shell/src/App.tsx b/apps/ai-game-creator-shell/src/App.tsx index be33bfe93..ac1ae9a8b 100644 --- a/apps/ai-game-creator-shell/src/App.tsx +++ b/apps/ai-game-creator-shell/src/App.tsx @@ -626,7 +626,7 @@ function claimInitialSupervisorMessageForPage(projectPath: string, scope = '') { * 初始需求是**乐观插入**到 messages 的(latch 命中后先插一条 user 消息,再发起回合), * 而历史回读在 replace 分支里是无条件整体替换 —— 只要回读晚于乐观插入,那条用户消息 * 就会被冲掉(界面上看不到初始需求,但回合其实已经跑起来了)。 - * 这里只把当前 messages 里"非历史来源、运行时拥有、且回读结果里没有"的消息保留在末尾。 + * 这里把当前 messages 里"运行时拥有、且回读结果里没有"的消息保留在末尾(它们是最新的)。 */ function mergeLoadedConversationWithPendingRuntimeMessages( loaded: ChatMessage[], @@ -644,7 +644,7 @@ function mergeLoadedConversationWithPendingRuntimeMessages( loaded.map((message) => `${message.role}\u0000${message.text}`), ); const pending = current.filter((message) => { - if (!message.runtimeOwned || message.fromHistory) { + if (!message.runtimeOwned) { return false; } if (message.messageId) { @@ -4202,7 +4202,7 @@ export function App({ const conversationMessages = mergeProjectSupervisorConversation( resolvedProjectConversation.messages, supervisorConversation?.messages ?? [], - ).map((message) => ({ ...message, fromHistory: true })); + ); if ( conversationContainsProjectSupervisorResponseStream( supervisorConversation?.messages ?? [], @@ -12528,7 +12528,6 @@ export function App({ : ('assistant' as const), text: message.content, runtimeOwned: true, - fromHistory: true, messageId: message.messageId, updatedAt: message.updatedAt, })); diff --git a/apps/ai-game-creator-shell/src/app/types.ts b/apps/ai-game-creator-shell/src/app/types.ts index 6fb26adc6..88594678d 100644 --- a/apps/ai-game-creator-shell/src/app/types.ts +++ b/apps/ai-game-creator-shell/src/app/types.ts @@ -999,8 +999,6 @@ export interface ChatMessage { agentId?: string | null; updatedAt?: number; runtimeOwned?: boolean; - /** 来自历史回读,不作为尚未落盘的实时消息追加到新历史页末尾。 */ - fromHistory?: boolean; } export type DesignAgentInput = diff --git a/apps/ai-game-creator-shell/tests/directHistoryPagination.test.tsx b/apps/ai-game-creator-shell/tests/directHistoryPagination.test.tsx index 8c0f00a19..9b7b410a0 100644 --- a/apps/ai-game-creator-shell/tests/directHistoryPagination.test.tsx +++ b/apps/ai-game-creator-shell/tests/directHistoryPagination.test.tsx @@ -97,43 +97,6 @@ afterEach(() => { }); describe('Direct 聊天历史分页集成', () => { - it('加载旧页后重读历史回到最新页,再翻页仍按原顺序且不重复', async () => { - const invoke = install((args) => - args.beforeItemId - ? page(messages.slice(0, 6), false) - : page(messages.slice(6), true), - ); - mount(); - await screen.findByText('历史正文 25'); - fireEvent.click(earlier()); - await screen.findByText('历史正文 0'); - await setComposerText(screen.getByLabelText('陶泥儿对话内容'), '/history'); - fireEvent.click(screen.getByRole('button', { name: '发送' })); - await act(async () => { - await Promise.resolve(); - }); - expect(screen.queryByText('历史正文 0')).toBeNull(); - expect(screen.getByText('历史正文 25')).not.toBeNull(); - fireEvent.click(earlier()); - await screen.findByText('历史正文 0'); - for (let index = 0; index < messages.length; index += 1) { - expect(screen.getAllByText(`历史正文 ${index}`)).toHaveLength(1); - if (index > 0) { - expect( - screen - .getByText(`历史正文 ${index - 1}`) - .compareDocumentPosition(screen.getByText(`历史正文 ${index}`)) & - Node.DOCUMENT_POSITION_FOLLOWING, - ).not.toBe(0); - } - } - expect( - invoke.mock.calls.filter( - ([command]) => command === 'read_direct_project_history_slice', - ), - ).toHaveLength(4); - }); - it('首屏按消息加载20条,更早消息使用原生游标,原始工具记录不占页', async () => { const invoke = install((args) => args.beforeItemId diff --git a/docs/project-memory/plans/【实施计划】AGC对话历史分页恢复-2026-09-16.md b/docs/project-memory/plans/【实施计划】AGC对话历史分页恢复-2026-09-16.md index db90eebeb..97f4d2433 100644 --- a/docs/project-memory/plans/【实施计划】AGC对话历史分页恢复-2026-09-16.md +++ b/docs/project-memory/plans/【实施计划】AGC对话历史分页恢复-2026-09-16.md @@ -10,7 +10,6 @@ 2. 工作台首屏与更早消息读取显式请求消息模式,消费游标;加载代次隔离、单飞与 ID 去重。 3. 合成记录测试复现原始工具页卡住的形状,覆盖旧无 ID、坏行、时间、失败/重复/切项目;临时目录只读重放用户日志。 4. 前端/原生定向测试、类型、Lint、编码、文档和差异检查通过后,更新问题表及 PR 草稿并本地提交。 -5. 在临时消息投影中标记历史来源,保留 Runtime 所有权语义;刷新合并时只保留非历史来源的待回读消息。补齐「先加载旧页,再 /history,再翻页」的顺序与去重回归,保持尚未落盘用户输入的保留逻辑。 ## 边界与停止条件 diff --git a/docs/project-memory/plans/【里程碑】AGC对话历史分页恢复-2026-09-16.md b/docs/project-memory/plans/【里程碑】AGC对话历史分页恢复-2026-09-16.md index 4b8e244c4..a247be381 100644 --- a/docs/project-memory/plans/【里程碑】AGC对话历史分页恢复-2026-09-16.md +++ b/docs/project-memory/plans/【里程碑】AGC对话历史分页恢复-2026-09-16.md @@ -17,4 +17,3 @@ 2. 消息正文、原始 ID、时间和顺序保持不变,翻页能到达早期用户提问及最终回答,不重复。 3. 连点、请求失败重试、项目切换和同项目重新加载不会污染消息或游标。 4. 原始切片默认模式回归通过,用户日志只读重放可以取回全部现存消息;不把真实日志或对话正文提交到仓库。 -5. 加载更早消息后重新读取历史,旧页不出现在最新回复之后;再次翻页保持顺序且不重复。未落盘的实时用户输入不因历史刷新被丢弃。 diff --git a/docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md b/docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md index 4c39954dc..792e58a6c 100644 --- a/docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md +++ b/docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md @@ -35,7 +35,6 @@ - 聊天历史使用 `read_direct_project_history_slice` 的 `messagesOnly: true` 模式,按有正文的 user/assistant 消息分页,默认 20 条;工具/推理原始记录不占聊天页名额、不进入聊天分页响应,也不从磁盘删除。接口省略该选项时维持原始 item 切片语义。响应给出明确的 `oldestItemId` 游标;消息投影不能重新发明分页位置。 - 消息模式读取逐行过滤原始记录,不在内存中积累整份工具输出;正文、原始 ID 和信封时间原样保留。旧的无 ID 消息不能凭空生成身份,必要时向前扩展到已有消息 ID 边界;没有更早消息时结束分页。 - 首屏和加载更早消息共用分页解析;重复点击只发一个请求,重叠消息按原始 ID 去重并保留当前显示版本。切项目、同项目重新加载及 A→B→A 的迟到响应不得覆盖当前消息、游标或加载状态;失败保留已有消息与分页位置并允许重试。 -- 历史读取来源与 Runtime 所有权分开记录;从磁盘分页读出的消息不能当作未落盘实时消息保留到新页末尾。重新读取历史时回到最新页,旧页仍可从原生游标再次向前加载;真正尚未回读到的实时消息继续保留,不改原始日志、时间或内容。 - 交付合同:实时消息、历史回读、工具详情与最终回复先归一为按 `clientTurnId` 唯一的回合,再渲染一次。用户消息始终保留;同一回合的正文、工具和耗时不能从消息、实时尾部、未归属尾部等多个出口重复展示。 - 归属来自 `direct-codex:{clientTurnId}:{role}`、文本流中保留的原始 item ID,以及项目历史内明确用户记录之后的 assistant 记录。先在已加载的完整消息集合中关联,再做可见分页;持久历史继续通过 canonical item 切片懒加载,`hasMore` 为真时,未加载回合的工具流不得漂到当前页尾部。禁止将第 N 个有工具回合配给第 N 条用户消息,禁止按文本长度、标点或时间窗猜测归属。缺身份的旧记录保留,不猜造其与其它回合的关联。 - 有回合流时正文与工具位置仅来自 item 边界与 `seq`,工具详情按该回合的 `callId` 关联;没有流时同一个回合容器显示历史消息与工具。整轮累计文本仅在活动回合尚无流和持久 assistant 时作兜底,不另建实时消息出口。