修复历史消息回看并重新打包

This commit is contained in:
shiyue
2026-09-11 09:29:53 +08:00
parent 6fff9023b2
commit b8fcc12229
18 changed files with 494 additions and 73 deletions

View File

@@ -4020,6 +4020,67 @@ function assertServerSessionHistoryRequestIdContract() {
);
}
function assertHistoryRecallContract() {
const frontendSource = fs.readFileSync(PUBLIC_APP_PATH, 'utf8');
const serverSource = fs.readFileSync(SERVER_PATH, 'utf8');
const indexSource = fs.readFileSync(PUBLIC_INDEX_PATH, 'utf8');
const styleSource = fs.readFileSync(PUBLIC_STYLE_PATH, 'utf8');
assert(indexSource.includes('id="history-load-more"'), 'Chat view should expose a history load-more control');
assert(frontendSource.includes('function requestOlderHistory'), 'Frontend should request older history on demand');
assert(frontendSource.includes("type: 'load_history_page'"), 'Frontend should use the paged history WebSocket request');
assert(frontendSource.includes('mergeHistoryChunkIntoCachedSnapshot'), 'Frontend should merge history pages into the session cache');
assert(
/const existingIndexes = new Set\([\s\S]*?data-message-index/.test(frontendSource),
'Frontend should deduplicate history messages by stable message index'
);
assert(styleSource.includes('.history-load-more-button'), 'History load-more control should have dedicated styling');
assert(serverSource.includes('function loadNativeSessionHistory'), 'Server should be able to recover native session history');
assert(serverSource.includes('function resolveSessionHistory'), 'Server should resolve persisted and native history sources');
assert(
/function handleLoadHistoryPage[\s\S]*?resolveSessionHistory\(session\)[\s\S]*?attachClientRequestId/.test(serverSource),
'Paged history requests should use the resolved full history and echo requestId'
);
assert(
/prefetchChunks: targetMessageIndex === null \? 0/.test(serverSource),
'Normal session loads should keep older history on demand instead of prefetching it all'
);
const splitStart = serverSource.indexOf('function splitHistoryMessages');
const splitEnd = serverSource.indexOf('const IS_WIN', splitStart);
assert(splitStart >= 0 && splitEnd > splitStart, 'Server should keep an extractable history splitter');
const splitHistoryMessages = new Function(
'INITIAL_HISTORY_COUNT',
'HISTORY_CHUNK_SIZE',
'HISTORY_PREFETCH_CHUNKS',
'HISTORY_MAX_CHUNKS_PER_LOAD',
'sanitizeMessagesForTransport',
`${serverSource.slice(splitStart, splitEnd)}; return splitHistoryMessages;`,
)(45, 24, 0, 8, (messages) => messages);
const fixture = Array.from({ length: 95 }, (_, index) => ({ id: `history-${index}` }));
const split = splitHistoryMessages(fixture, { prefetchChunks: 0 });
assert(split.recentMessages.length === 45, 'History splitter should keep the configured recent window');
assert(split.recentMessages[0]?.id === 'history-50', 'Recent history window should preserve chronological order');
assert(split.historyRemaining === 50, 'History splitter should expose the exact older-message cursor');
assert(split.olderChunks.length === 0, 'Normal history split should not prefetch older chunks');
const { createCodexRolloutStore } = require(path.join(REPO_DIR, 'lib', 'codex-rollouts.js'));
const store = createCodexRolloutStore({
codexSessionsDir: '/tmp/cc-web-regression-no-rollouts',
sessionsDir: '/tmp/cc-web-regression-no-sessions',
normalizeSession: (value) => value,
sanitizeToolInput: (name, value) => value,
});
const parsed = store.parseCodexRolloutLines([
JSON.stringify({ type: 'session_meta', payload: { id: 'history-thread', cwd: '/tmp' } }),
JSON.stringify({ type: 'event_msg', timestamp: '2026-09-11T00:00:00.000Z', payload: { type: 'user_message', message: '旧问题' } }),
JSON.stringify({ type: 'response_item', timestamp: '2026-09-11T00:00:01.000Z', payload: { type: 'message', role: 'assistant', content: [{ type: 'output_text', text: '旧回答' }] } }),
]);
assert(parsed.messages.length === 2, 'Codex rollout parser should recover user and assistant history');
assert(parsed.messages[0]?.content === '旧问题' && parsed.messages[1]?.content === '旧回答',
'Recovered native history should preserve message order and content');
}
function assertSessionSwitchRaceContract() {
const checks = [
['render epoch behavior', assertSessionRenderEpochRaceContract],
@@ -6427,6 +6488,11 @@ async function main() {
console.log('Session switch race regression checks passed.');
return;
}
if (regressionTarget === 'history-recall') {
assertHistoryRecallContract();
console.log('History recall regression checks passed.');
return;
}
if (regressionTarget === 'title-history-outline') {
assertSetTitleMcpContract();
assertTitleHistoryOutlineContract();
@@ -6542,6 +6608,7 @@ async function main() {
assertCcwebMcpChildUpdateCoalescingContract();
assertTitleHistoryOutlineContract();
assertSessionSwitchResilienceContract();
assertHistoryRecallContract();
assertCodexAppGoalLifecycleContract();
assertSessionSwitchRaceContract();
assertMcpSessionVisibilityContract();