修复历史消息回看并重新打包
This commit is contained in:
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user