修复 Codex rollout 历史合并并重新打包
This commit is contained in:
@@ -4580,6 +4580,14 @@ function assertHistoryRecallContract() {
|
||||
/const existingIndexes = new Set\([\s\S]*?data-message-index/.test(frontendSource),
|
||||
'Frontend should deduplicate history messages by stable message index'
|
||||
);
|
||||
assert(frontendSource.includes('function getStableMessageKey'), 'Frontend should use a stable message key for history reconciliation');
|
||||
assert(frontendSource.includes('data-message-key'), 'Message DOM nodes should retain stable message keys');
|
||||
assert(
|
||||
(frontendSource.match(/indexed\.set\(keyed\.get\(key\), message\)/g) || []).length >= 2,
|
||||
'History snapshot merges should update an existing stable ID in place'
|
||||
);
|
||||
assert(serverSource.includes('function mergeNativeHistoryWithSnapshot'), 'Server should merge native history into the snapshot prefix');
|
||||
assert(serverSource.includes('historySnapshotBaseIndex'), 'Persisted snapshots should record their logical history base index');
|
||||
assert(frontendSource.includes('currentOutlineHistoryState'),
|
||||
'User-message locator should keep an independent full-history index state');
|
||||
assert(frontendSource.includes('function requestOutlineHistoryPage'),
|
||||
@@ -4679,12 +4687,82 @@ function assertHistoryRecallContract() {
|
||||
});
|
||||
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: 'task_started', turn_id: 'turn-history' } }),
|
||||
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: '旧回答' }] } }),
|
||||
JSON.stringify({ type: 'response_item', timestamp: '2026-09-11T00:00:01.000Z', payload: { type: 'message', role: 'assistant', content: [{ type: 'output_text', text: '旧回答第一段' }] } }),
|
||||
JSON.stringify({ type: 'response_item', timestamp: '2026-09-11T00:00:02.000Z', payload: { type: 'message', role: 'assistant', content: [{ type: 'output_text', text: '旧回答第二段' }] } }),
|
||||
JSON.stringify({ type: 'response_item', timestamp: '2026-09-11T00:00:03.000Z', payload: { type: 'custom_tool_call', call_id: 'custom-1', name: 'ccweb_send_message', input: '{}' } }),
|
||||
JSON.stringify({ type: 'response_item', timestamp: '2026-09-11T00:00:04.000Z', payload: { type: 'custom_tool_call_output', call_id: 'custom-1', output: 'done' } }),
|
||||
JSON.stringify({ type: 'event_msg', timestamp: '2026-09-11T00:00:05.000Z', payload: { type: 'task_complete', turn_id: 'turn-history' } }),
|
||||
JSON.stringify({ type: 'event_msg', timestamp: '2026-09-11T00:00:06.000Z', payload: { type: 'task_started', turn_id: 'turn-cross' } }),
|
||||
JSON.stringify({ type: 'event_msg', timestamp: '2026-09-11T00:00:07.000Z', payload: { type: 'user_message', message: '来自「源会话」对话(ID: 00000000-0000-0000-0000-000000000001)的消息:内部输入' } }),
|
||||
JSON.stringify({ type: 'response_item', timestamp: '2026-09-11T00:00:08.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 === '旧回答',
|
||||
assert(parsed.messages.length === 3, 'Codex rollout parser should recover user and assistant history');
|
||||
assert(parsed.messages[0]?.content === '旧问题' && parsed.messages[1]?.content === '旧回答第一段\n\n旧回答第二段',
|
||||
'Recovered native history should preserve message order and content');
|
||||
assert(parsed.messages[1]?.nativeTurnKey === 'native:history-thread:turn-history', 'Aggregated native assistant should retain its turn key');
|
||||
assert(parsed.messages[1]?.toolCalls?.length === 1, 'Aggregated native assistant should retain custom tool calls');
|
||||
assert(!parsed.messages.some((message) => message.role === 'user' && message.content.includes('内部输入')),
|
||||
'Cross-conversation native input must not become a regular user bubble');
|
||||
|
||||
const implicitTurns = store.parseCodexRolloutLines([
|
||||
JSON.stringify({ type: 'session_meta', payload: { id: 'implicit-thread' } }),
|
||||
JSON.stringify({ type: 'event_msg', timestamp: '2026-09-11T00:01:00.000Z', payload: { type: 'user_message', message: '第一轮问题' } }),
|
||||
JSON.stringify({ type: 'response_item', timestamp: '2026-09-11T00:01:01.000Z', payload: { type: 'message', role: 'assistant', content: [{ type: 'output_text', text: '第一轮回答' }] } }),
|
||||
JSON.stringify({ type: 'event_msg', timestamp: '2026-09-11T00:02:00.000Z', payload: { type: 'user_message', message: '第二轮问题' } }),
|
||||
JSON.stringify({ type: 'response_item', timestamp: '2026-09-11T00:02:01.000Z', payload: { type: 'message', role: 'assistant', content: [{ type: 'output_text', text: '第二轮回答' }] } }),
|
||||
]);
|
||||
const implicitAssistants = implicitTurns.messages.filter((message) => message.role === 'assistant');
|
||||
assert(implicitAssistants.length === 2 && implicitAssistants[0].nativeTurnKey !== implicitAssistants[1].nativeTurnKey,
|
||||
'Rollout parser should assign distinct stable keys to adjacent unmarked turns');
|
||||
|
||||
assert(
|
||||
/existingElement\.dataset\.messageFingerprint[\s\S]*?existingElement\.replaceWith\(replacement\)/.test(frontendSource),
|
||||
'Realtime session messages with an existing stable key should replace changed content instead of being dropped'
|
||||
);
|
||||
|
||||
const historyMergeStart = serverSource.indexOf('function stableMessageHash');
|
||||
const historyMergeEnd = serverSource.indexOf('function sessionHistoryCacheKey', historyMergeStart);
|
||||
assert(historyMergeStart >= 0 && historyMergeEnd > historyMergeStart,
|
||||
'Server should keep an extractable stable-ID history merge implementation');
|
||||
const historyMergeApi = new Function('crypto', `${serverSource.slice(historyMergeStart, historyMergeEnd)}; return { mergeNativeHistoryWithSnapshot };`)(require('crypto'));
|
||||
const snapshotTail = [
|
||||
{ id: 'client-user-1', role: 'user', content: '没参考其他的页面么,WMSA51 是你这么写的么?' },
|
||||
{ id: 'codex-turn-1', role: 'assistant', content: '先按项目规范核对现有页面、任务票和负责会话。' },
|
||||
];
|
||||
const merged = historyMergeApi.mergeNativeHistoryWithSnapshot(
|
||||
{ historySnapshotBaseIndex: 1 },
|
||||
snapshotTail,
|
||||
[
|
||||
{ id: 'native-old', role: 'assistant', content: '更早的 native 历史' },
|
||||
...snapshotTail,
|
||||
],
|
||||
);
|
||||
assert(merged.confirmed === true, 'History merge should confirm an overlapping native prefix');
|
||||
assert(JSON.stringify(merged.messages.map((message) => message.id))
|
||||
=== JSON.stringify(['native-old', 'client-user-1', 'codex-turn-1']),
|
||||
'History merge should keep the persisted user and assistant tail adjacent');
|
||||
const duplicatedNative = historyMergeApi.mergeNativeHistoryWithSnapshot(
|
||||
{ historySnapshotBaseIndex: 1 },
|
||||
snapshotTail,
|
||||
[
|
||||
{ id: 'native-old', role: 'assistant', content: '更早的 native 历史' },
|
||||
snapshotTail[0],
|
||||
{ ...snapshotTail[0], content: 'native 重复用户消息' },
|
||||
snapshotTail[1],
|
||||
],
|
||||
);
|
||||
assert(JSON.stringify(duplicatedNative.messages.map((message) => message.id))
|
||||
=== JSON.stringify(['native-old', 'client-user-1', 'codex-turn-1']),
|
||||
'History merge should remove duplicate native IDs when the snapshot is authoritative');
|
||||
const unconfirmed = historyMergeApi.mergeNativeHistoryWithSnapshot(
|
||||
{ historySnapshotBaseIndex: 0 },
|
||||
snapshotTail,
|
||||
[{ id: 'native-unrelated', role: 'assistant', content: '无法确认的 native 消息' }],
|
||||
);
|
||||
assert(unconfirmed.confirmed === false && unconfirmed.messages.length === snapshotTail.length,
|
||||
'Unconfirmed native history must fall back to the cc-web snapshot');
|
||||
}
|
||||
|
||||
function assertSessionSwitchRaceContract() {
|
||||
|
||||
Reference in New Issue
Block a user