修复完整历史与运行态内存放大并重建发布包

This commit is contained in:
shiyue
2026-10-14 18:13:02 +08:00
parent 69b6db0f5b
commit 2b1c91488d
28 changed files with 2344 additions and 159 deletions

View File

@@ -36,11 +36,13 @@ function createServerHarness(sessionsDir, options = {}) {
'sanitizeToolCallsForPersist', 'sanitizeMessageForPersist', 'sanitizeMessagesForPersist',
'sanitizeSessionForPersist', 'buildSessionJsonForPersist', 'writeFileAtomicSync',
'safeReadSessionJson', 'loadSession', 'preserveNewerTaskTrackingForSessionSave',
'findSessionMessage',
'saveSession', 'sessionHistoryCacheKey', 'cacheSessionHistory', 'loadNativeSessionHistory',
'resolveSessionHistory',
'listSessionFiles', 'refreshSessionSnapshotCache', 'prepareNativeHistoryImport',
'handleSlashCommand', 'handleDeleteSession', 'findCcwebPromptMessage', 'removeCcwebPromptMessage',
'rollbackPersistedCodexAppSteerMessage', 'handleLoadHistoryPage',
'buildSessionInfoPayload', 'handleResumeSession',
];
const api = new Function('fs', 'path', 'crypto', 'SESSIONS_DIR', 'sessionHistoryStore', 'rollouts', 'options', 'logs', `
const SESSION_MAX_JSON_BYTES = options.snapshotMaxBytes || 512 * 1024;
@@ -60,6 +62,8 @@ function createServerHarness(sessionsDir, options = {}) {
const retainedCodexAppTurns = new Map();
const activeCodexAppGoalCommands = new Map();
const activeCodexAppCompactions = new Map();
const pendingCodexCapacityRetries = new Map();
const wsSessionMap = new Map();
const codexAppGoalStates = new Map();
const pendingSlashCommands = new Map();
const pendingCompactRetries = new Map();
@@ -83,6 +87,10 @@ function createServerHarness(sessionsDir, options = {}) {
function wsSend(ws, payload) { responses.push(payload); }
function attachClientRequestId(payload, source) { return { ...payload, requestId: source?.requestId }; }
function taskTrackingSnapshotForSession() { return null; }
function crossConversationWaitState() { return {}; }
function detachWsFromActiveRuntimes() {}
function isSessionRunning() { return false; }
function attachActiveRuntimeToWs() {}
function publicTitleMetadata() { return {}; }
function sessionModelLabel(session) { return session.model; }
function isCodexAppSession(session) { return session.agent === 'codexapp'; }
@@ -105,9 +113,16 @@ function createServerHarness(sessionsDir, options = {}) {
${pureSource}
${names.map(name => extract(name)).join('\n')}
return {
loadSession, saveSession, resolveSessionHistory, listSessionFiles, refreshSessionSnapshotCache,
loadSession: (id, loadOptions = {}) => {
const explicitWindow = ['metadata', 'count', 'window', 'messageId', 'promptId'].some(key => Object.prototype.hasOwnProperty.call(loadOptions, key));
const full = Object.prototype.hasOwnProperty.call(loadOptions, 'full')
? loadOptions.full : options.fullHistory !== false && !explicitWindow;
return loadSession(id, { ...loadOptions, full });
},
saveSession, resolveSessionHistory, listSessionFiles, refreshSessionSnapshotCache, findSessionMessage,
prepareNativeHistoryImport, handleSlashCommand, handleDeleteSession, removeCcwebPromptMessage,
rollbackPersistedCodexAppSteerMessage, handleLoadHistoryPage, responses, logs, sessionHistoryStore,
buildSessionInfoPayload, handleResumeSession,
};
`)(filesystem, path, crypto, sessionsDir, sessionHistoryStore, rollouts, options, logs);
return api;
@@ -302,6 +317,16 @@ check('首次快照失败的孤立档案重开后仍可读取并参与列表且
assert(fs.existsSync(path.join(dir, 'orphan.json')));
});
check('运行态保留目录和同名旧文件不会干扰完整归档与会话列表', dir => {
const api = createServerHarness(dir);
assert.equal(api.saveSession(fixture('listed-session', 3)), true);
api.sessionHistoryStore.saveTurn('listed-session', { sessionId: 'listed-session', agent: 'codexapp' });
fs.writeFileSync(path.join(dir, '_history', '_turns.json'), '{}');
assert.deepEqual(api.sessionHistoryStore.listTurnIds(), ['listed-session']);
assert.deepEqual(api.sessionHistoryStore.listIds(), ['listed-session']);
assert.deepEqual(api.listSessionFiles(), ['listed-session.json']);
});
check('可信旧修订并发追加和插入保留锚点顺序以及特殊 JSON 键', dir => {
const api = createServerHarness(dir);
const original = fixture('concurrent', 4);
@@ -389,7 +414,7 @@ check('档案异常禁止回退预览覆盖,删除档案后旧加载对象不
assert.equal(api.saveSession(fixture('corrupt', 220)), true);
const stale = api.loadSession('corrupt');
const oldSnapshot = fs.readFileSync(path.join(dir, 'corrupt.json'), 'utf8');
fs.writeFileSync(path.join(dir, '_history', 'corrupt.json'), '{损坏文件');
fs.writeFileSync(path.join(dir, '_history', 'corrupt', 'manifest.json'), '{损坏文件');
assert.equal(api.loadSession('corrupt'), null);
assert.equal(api.saveSession(stale), false);
assert.equal(fs.readFileSync(path.join(dir, 'corrupt.json'), 'utf8'), oldSnapshot);
@@ -445,7 +470,7 @@ check('旧快照的真实 rollout 前缀确认后先归档,移除 rollout 后
const api = createServerHarness(dir, { nativeDir, nativeFiles: [rolloutPath] });
const resolved = api.resolveSessionHistory(api.loadSession(id));
assert.equal(resolved.messages.length, 8);
assert(fs.existsSync(path.join(dir, '_history', `${id}.json`)), '确认前缀后应提交自身档案');
assert(fs.existsSync(path.join(dir, '_history', id, 'manifest.json')), '确认前缀后应提交自身档案');
fs.unlinkSync(rolloutPath);
const reopened = createServerHarness(dir);
const reloaded = reopened.loadSession(id);
@@ -462,7 +487,8 @@ check('原生日志不匹配和不存在时只返回已有旧快照并保留缺
assert.equal(history.messages.length, 57);
assert.equal(history.available, false);
assert.equal(api.saveSession(loaded), true);
assert.equal(createServerHarness(dir).resolveSessionHistory(createServerHarness(dir).loadSession('unconfirmed')).available, false);
const reopened = createServerHarness(dir);
assert.equal(reopened.resolveSessionHistory(reopened.loadSession('unconfirmed')).available, false);
});
check('归档提交失败的旧前缀恢复只提供原快照而不宣称恢复成功', dir => {
@@ -538,6 +564,402 @@ check('生产分页出口读取57条旧快照:首屏12以外45条完整有序
assert.equal(new Set(chunks.map(message => message.id)).size, 45);
});
check('真实默认加载650条历史只持有180条尾部且显式窗口不走完整加载', dir => {
const api = createServerHarness(dir, { fullHistory: false });
const original = fixture('bounded-default', 650);
assert.equal(api.saveSession(original), true);
const loaded = api.loadSession(original.id);
assert.equal(loaded.messages.length, 180);
assert.equal(loaded.historySnapshotBaseIndex, 470);
assert.deepEqual(loaded.messages, original.messages.slice(470));
const state = api.sessionHistoryStore.state(loaded);
assert.equal(state.partial, true);
assert.equal(state.start, 470);
assert.equal(state.total, 650);
assert.deepEqual(api.loadSession(original.id, { full: true }).messages, original.messages);
const fullDefault = createServerHarness(dir);
assert.equal(fullDefault.loadSession(original.id, { count: 7 }).messages.length, 7);
assert.equal(fullDefault.loadSession(original.id, { metadata: true }).messages.length, 0);
assert.equal(fullDefault.loadSession(original.id, { window: { start: 470, end: 650 } }).messages.length, 180);
});
check('有界尾部修改删除前插追加精确保存且未加载前缀原样保留', dir => {
const api = createServerHarness(dir, { fullHistory: false });
const original = fixture('bounded-edit', 650);
assert.equal(api.saveSession(original), true);
const loaded = api.loadSession(original.id);
loaded.messages[0].content = '尾部同 ID 更新';
loaded.messages[0].toolCalls = [{ id: 'changed-tool', input: { retained: true }, result: '完整工具结果' }];
loaded.messages.splice(1, 1);
loaded.messages.splice(2, 0, { id: 'inserted-before-tail', role: 'assistant', content: '已有气泡前插入' });
loaded.messages.push({ id: 'new-tail', role: 'user', content: '追加完整内容' });
const expected = [...original.messages.slice(0, 470), ...loaded.messages];
assert.equal(api.saveSession(loaded), true);
const restored = api.loadSession(original.id, { full: true });
assert.deepEqual(restored.messages, expected);
assert.deepEqual(restored.messages.slice(0, 470), original.messages.slice(0, 470));
assert.equal(restored.messages.some(message => message.id === 'message-471'), false);
});
check('中间加载窗口修改删除前插保存同时保留未加载前缀和后缀', dir => {
const api = createServerHarness(dir, { fullHistory: false });
const original = fixture('middle-window', 650);
original.messages[12].ccwebPrompt = { id: 'early-prompt' };
assert.equal(api.saveSession(original), true);
const tail = api.loadSession(original.id);
assert.deepEqual(api.findSessionMessage(tail, message => message?.ccwebPrompt?.id === 'early-prompt'), original.messages[12]);
const promptWindow = api.loadSession(original.id, { promptId: 'early-prompt' });
assert.equal(promptWindow.messages.length, 1);
assert.equal(promptWindow.messages[0].id, 'message-12');
const messageWindow = api.loadSession(original.id, { messageId: 'message-10' });
assert.equal(messageWindow.messages.length, 1);
assert.equal(messageWindow.messages[0].id, 'message-10');
const loaded = api.loadSession(original.id, { window: { start: 250, end: 255 } });
assert.equal(loaded.messages.length, 5);
assert.equal(loaded.historySnapshotBaseIndex, 250);
loaded.messages[0].content = '中间窗口同 ID 更新';
loaded.messages.splice(1, 1);
loaded.messages.splice(2, 0, { id: 'middle-insert', role: 'assistant', content: '中间窗口前插内容' });
const expected = [...original.messages.slice(0, 250), ...loaded.messages, ...original.messages.slice(255)];
assert.equal(api.saveSession(loaded), true);
const restored = api.loadSession(original.id, { full: true });
assert.deepEqual(restored.messages, expected);
assert.deepEqual(restored.messages.slice(0, 250), original.messages.slice(0, 250));
assert.deepEqual(restored.messages.slice(255), original.messages.slice(255));
assert.equal(restored.messages.some(message => message.id === 'message-251'), false);
});
check('仅元数据加载更新标题及taskTracking不读取或覆盖未加载消息', dir => {
const api = createServerHarness(dir);
const original = fixture('metadata-only', 650);
original.taskTracking = { version: 2, tasks: [{ id: 'task-a', title: '原任务', status: 'pending' }] };
assert.equal(api.saveSession(original), true);
const metadata = api.loadSession(original.id, { metadata: true });
assert.equal(metadata.messages.length, 0);
assert.equal(api.sessionHistoryStore.state(metadata).partial, true);
metadata.title = '仅元数据更新的标题';
metadata.taskTracking = { version: 3, tasks: [{ id: 'task-a', title: '新任务', status: 'completed' }] };
assert.equal(api.saveSession(metadata), true);
const restored = api.loadSession(original.id);
assert.equal(restored.title, metadata.title);
assert.deepEqual(restored.taskTracking, metadata.taskTracking);
assert.deepEqual(restored.messages, original.messages);
});
check('同修订任务版本保护只读取修订头且提交后同步活跃对象', dir => {
let tracking = false;
let wholeReads = 0;
const filesystem = new Proxy(fs, {
get(target, key) {
if (key === 'readFileSync') return (...args) => {
if (tracking) wholeReads += 1;
return target.readFileSync(...args);
};
return target[key];
},
});
const api = createServerHarness(dir, { fs: filesystem, fullHistory: false });
const original = fixture('same-task-version', 650);
original.taskTracking = { version: 5, statusId: 'waiting', summary: '已提交任务' };
assert.equal(api.saveSession(original), true);
const loaded = api.loadSession(original.id);
const taskReference = loaded.taskTracking;
loaded.taskTracking.version = 1;
loaded.taskTracking.statusId = '旧任务';
tracking = true;
const committed = api.sessionHistoryStore.save(loaded);
tracking = false;
assert.equal(wholeReads, 0, '同修订提交不得重新读取清单或消息正文');
assert.deepEqual(committed.taskTrackingPreserved, { incomingVersion: 1, persistedVersion: 5 });
assert.equal(loaded.taskTracking, taskReference, '任务对象仍原位协调');
assert.deepEqual(loaded.taskTracking, original.taskTracking);
loaded.taskTracking.summary = '提交后修改';
const disk = api.loadSession(original.id, { metadata: true });
assert.equal(disk.taskTracking.summary, '已提交任务', '活跃对象不能污染可信清单');
assert.equal(api.loadSession(original.id, { full: true }).messages.length, 650);
});
check('旧修订普通保存完整保留更高任务版本与流式消息引用', dir => {
const api = createServerHarness(dir, { fullHistory: false });
const original = fixture('stale-task-version', 650);
original.taskTracking = { version: 2, statusId: 'initial', nested: { value: '原任务' } };
assert.equal(api.saveSession(original), true);
const streaming = api.loadSession(original.id);
const message = streaming.messages.at(-1);
const metadata = api.loadSession(original.id, { metadata: true });
metadata.taskTracking = { version: 6, statusId: 'newer', nested: { value: '新任务' } };
assert.equal(api.saveSession(metadata, { taskBoardMetadata: true }), true);
streaming.taskTracking = { version: 4, statusId: 'stale', nested: { value: '旧对象修改' } };
message.content += '|增量';
assert.equal(api.saveSession(streaming), true);
assert.deepEqual(streaming.taskTracking, metadata.taskTracking);
assert.equal(streaming.messages.at(-1), message);
const restored = api.loadSession(original.id, { full: true });
assert.equal(restored.messages.length, 650);
assert.equal(restored.messages.at(-1).content, message.content);
assert.deepEqual(restored.taskTracking, metadata.taskTracking);
});
check('任务看板显式提交可删除任务状态而普通保存保留已提交版本', dir => {
const api = createServerHarness(dir);
const original = fixture('task-metadata-removal', 3);
original.taskTracking = { version: 3, statusId: 'waiting' };
assert.equal(api.saveSession(original), true);
const ordinary = api.loadSession(original.id);
delete ordinary.taskTracking;
assert.equal(api.saveSession(ordinary), true);
assert.deepEqual(ordinary.taskTracking, original.taskTracking);
const board = api.loadSession(original.id, { metadata: true });
delete board.taskTracking;
assert.equal(api.saveSession(board, { taskBoardMetadata: true }), true);
const restored = api.loadSession(original.id);
assert.equal(Object.prototype.hasOwnProperty.call(restored, 'taskTracking'), false);
assert.deepEqual(restored.messages, original.messages);
});
check('任务状态保护遇清单提交失败不提前修改对象或推进基线', dir => {
let failManifest = false;
const filesystem = new Proxy(fs, {
get(target, key) {
if (key === 'renameSync') return (from, to) => {
if (failManifest && path.basename(to) === 'manifest.json') throw new Error('模拟任务清单提交失败');
return target.renameSync(from, to);
};
return target[key];
},
});
const api = createServerHarness(dir, { fs: filesystem });
const original = fixture('task-failed-commit', 4);
original.taskTracking = { version: 5, statusId: 'saved' };
assert.equal(api.saveSession(original), true);
const loaded = api.loadSession(original.id);
loaded.taskTracking = { version: 1, statusId: 'stale' };
const pendingTracking = loaded.taskTracking;
const revision = api.sessionHistoryStore.state(loaded).revision;
failManifest = true;
assert.equal(api.saveSession(loaded), false);
assert.equal(loaded.taskTracking, pendingTracking);
assert.equal(loaded.taskTracking.version, 1);
assert.equal(api.sessionHistoryStore.state(loaded).revision, revision);
failManifest = false;
assert.equal(api.saveSession(loaded), true);
assert.deepEqual(loaded.taskTracking, original.taskTracking);
});
check('不同尾部加载范围的旧修订合并修改删除及新增并保留各自顺序', dir => {
const api = createServerHarness(dir, { fullHistory: false });
const original = fixture('partial-ranges', 650);
assert.equal(api.saveSession(original), true);
const narrow = api.sessionHistoryStore.loadTail(original.id, 5);
const wide = api.sessionHistoryStore.loadTail(original.id, 30);
wide.messages[0].content = '较宽窗口的修改';
wide.messages.splice(wide.messages.findIndex(message => message.id === 'message-640'), 1);
wide.messages.push({ id: 'wide-new', role: 'user', content: '较宽窗口追加' });
assert.equal(api.saveSession(wide), true);
narrow.messages[0].content = '旧修订较窄窗口的修改';
narrow.messages.splice(1, 0, { id: 'narrow-insert', role: 'assistant', content: '较窄窗口前插' });
narrow.messages.push({ id: 'narrow-new', role: 'assistant', content: '较窄窗口追加' });
assert.equal(api.saveSession(narrow), true);
const restored = api.loadSession(original.id, { full: true });
const expected = original.messages.filter(message => message.id !== 'message-640').map(message => ({ ...message }));
expected.find(message => message.id === 'message-620').content = '较宽窗口的修改';
expected.find(message => message.id === 'message-645').content = '旧修订较窄窗口的修改';
expected.splice(expected.findIndex(message => message.id === 'message-646'), 0, { id: 'narrow-insert', role: 'assistant', content: '较窄窗口前插' });
expected.push({ id: 'narrow-new', role: 'assistant', content: '较窄窗口追加' }, { id: 'wide-new', role: 'user', content: '较宽窗口追加' });
assert.deepEqual(restored.messages, expected);
assert.deepEqual(narrow.messages, expected.slice(644));
});
check('部分加载明确reset清空全部历史后旧运行对象只保存元数据', dir => {
const api = createServerHarness(dir, { fullHistory: false });
const original = fixture('partial-clear', 650);
assert.equal(api.saveSession(original), true);
const stale = api.loadSession(original.id);
const current = api.loadSession(original.id);
current.messages = [];
current.codexAppThreadId = null;
api.sessionHistoryStore.markComplete(current, { reset: true });
assert.equal(api.saveSession(current), true);
assert.deepEqual(api.loadSession(original.id, { full: true }).messages, []);
stale.messages[0].content = '清空后旧轮才返回的输出';
stale.messages.push({ id: 'stale-output', role: 'assistant', content: '不能复活旧历史' });
stale.model = '旧对象本次修改的模型';
assert.equal(api.saveSession(stale), true);
const restored = api.loadSession(original.id, { full: true });
assert.deepEqual(restored.messages, []);
assert.equal(restored.codexAppThreadId, null);
assert.equal(restored.model, stale.model);
});
check('尾部manifest提交失败不推进基线且重试保留并发元数据与完整前缀', dir => {
let failManifest = false;
const filesystem = new Proxy(fs, { get(target, key) {
if (key === 'renameSync') return (from, to) => {
if (failManifest && path.basename(to) === 'manifest.json') throw new Error('模拟清单提交失败');
return target.renameSync(from, to);
};
return target[key];
} });
const api = createServerHarness(dir, { fullHistory: false, fs: filesystem });
const original = fixture('partial-retry', 650);
assert.equal(api.saveSession(original), true);
const pending = api.loadSession(original.id);
const baselineRevision = api.sessionHistoryStore.state(pending).revision;
const oldSnapshot = fs.readFileSync(path.join(dir, `${original.id}.json`), 'utf8');
pending.messages[0].content = '失败重试后应保存的正文';
pending.messages.push({ id: 'retry-new', role: 'assistant', content: '只提交一次的新消息' });
failManifest = true;
assert.equal(api.saveSession(pending), false);
assert.equal(api.sessionHistoryStore.state(pending).revision, baselineRevision);
assert.equal(fs.readFileSync(path.join(dir, `${original.id}.json`), 'utf8'), oldSnapshot);
assert.deepEqual(api.loadSession(original.id, { full: true }).messages, original.messages);
failManifest = false;
const metadata = api.loadSession(original.id, { metadata: true });
metadata.title = '失败后其他对象提交的标题';
assert.equal(api.saveSession(metadata), true);
assert.equal(api.saveSession(pending), true);
assert.notEqual(api.sessionHistoryStore.state(pending).revision, baselineRevision);
const restored = api.loadSession(original.id, { full: true });
assert.equal(restored.title, metadata.title);
assert.deepEqual(restored.messages.slice(0, 470), original.messages.slice(0, 470));
assert.equal(restored.messages[470].content, '失败重试后应保存的正文');
assert.equal(restored.messages.length, 651);
assert.equal(restored.messages.filter(message => message.id === 'retry-new').length, 1);
});
check('不完整归档分页总量和消息不计入持久化裁剪提示', dir => {
const api = createServerHarness(dir, { fullHistory: false });
const original = fixture('incomplete-pages', 57);
original.messages.unshift({ id: 'persistence-notice', role: 'system', ccwebPersistenceNotice: true, content: '仅显示的旧截断提示' });
original.historySnapshotBaseIndex = 500;
const expectedMessages = original.messages.slice(1);
assert.equal(api.saveSession(original), true);
const directPage = api.sessionHistoryStore.loadHistoryPage(original.id, 57, 24);
assert.equal(directPage.total, 57);
assert.equal(directPage.complete, false);
const chunks = [];
let cursor = 57;
while (cursor > 0) {
api.handleLoadHistoryPage(null, { sessionId: original.id, before: cursor, requestId: `archive-page-${cursor}` });
const page = api.responses.at(-1);
assert.equal(page.type, 'session_history_chunk');
assert.equal(page.historyTotal, 57);
assert.equal(page.historyAvailable, false);
assert.equal(page.messages.some(message => message.ccwebPersistenceNotice === true), false);
chunks.unshift(...page.messages);
assert(page.historyCursor < cursor, '分页游标必须前进');
cursor = page.historyCursor;
}
assert.deepEqual(chunks, expectedMessages);
});
check('尾部追加ID撞未加载前缀时保留原始前缀并去重', dir => {
const api = createServerHarness(dir, { fullHistory: false });
const original = fixture('partial-collision', 650);
assert.equal(api.saveSession(original), true);
const loaded = api.loadSession(original.id);
loaded.messages.push({ id: 'message-5', role: 'assistant', content: '碰撞内容不能覆盖未知前缀' });
loaded.messages.push({ id: 'unique-tail', role: 'assistant', content: '真正新增的尾部内容' });
assert.equal(api.saveSession(loaded), true);
const restored = api.loadSession(original.id, { full: true });
assert.deepEqual(restored.messages.slice(0, 650), original.messages);
assert.equal(restored.messages.length, 651);
assert.equal(restored.messages.filter(message => message.id === 'message-5').length, 1);
assert.equal(restored.messages.at(-1).id, 'unique-tail');
});
check('真实归档首屏及恢复仅返回12条并逐页读取650条完整有序历史', dir => {
const api = createServerHarness(dir, { fullHistory: false });
const original = fixture('bounded-first-screen', 650);
assert.equal(api.saveSession(original), true);
const loaded = api.loadSession(original.id);
assert.equal(loaded.messages.length, 180);
const info = api.buildSessionInfoPayload(loaded);
assert.equal(info.type, 'session_info');
assert.equal(info.messages.length, 12);
assert.deepEqual(info.messages, original.messages.slice(638));
assert.equal(info.historyTotal, 650);
assert.equal(info.historyBaseIndex, 638);
assert.equal(info.historyCursor, 638);
assert.equal(info.historyTruncated, true);
assert.equal(info.historyAvailable, true);
api.handleLoadHistoryPage(null, { sessionId: original.id, before: 638, requestId: 'first-earlier-page' });
const firstPage = api.responses.at(-1);
assert.equal(firstPage.type, 'session_history_chunk');
assert.equal(firstPage.messages.length, 24);
assert.deepEqual(firstPage.messages, original.messages.slice(614, 638));
assert.equal(firstPage.historyTotal, 650);
assert.equal(firstPage.historyBaseIndex, 614);
assert.equal(firstPage.historyCursor, 614);
const older = [...firstPage.messages];
let cursor = firstPage.historyCursor;
while (cursor > 0) {
api.handleLoadHistoryPage(null, { sessionId: original.id, before: cursor, requestId: `complete-page-${cursor}` });
const page = api.responses.at(-1);
assert.equal(page.type, 'session_history_chunk');
assert.equal(page.historyTotal, 650);
assert.equal(page.historyAvailable, true);
assert(page.messages.length <= 24, '每页消息数必须有界');
assert(page.historyCursor < cursor, '历史游标必须前进');
older.unshift(...page.messages);
cursor = page.historyCursor;
}
const complete = [...older, ...info.messages];
assert.deepEqual(complete, original.messages);
assert.equal(new Set(complete.map(message => message.id)).size, 650);
api.handleResumeSession({}, { sessionId: original.id, requestId: 'resume-bounded-history' });
const resumed = api.responses.at(-1);
assert.equal(resumed.type, 'resume_session_result');
assert.equal(resumed.messages.length, 12);
assert.deepEqual(resumed.messages, info.messages);
assert.equal(resumed.historyTotal, info.historyTotal);
assert.equal(resumed.historyBaseIndex, info.historyBaseIndex);
assert.equal(resumed.historyAvailable, true);
assert.equal(resumed.isRunning, false);
});
check('旧尾部对象协调新增深层元数据后原位修改不污染不可变基线', dir => {
const api = createServerHarness(dir, { fullHistory: false });
const original = fixture('partial-nested-baseline', 650);
assert.equal(api.saveSession(original), true);
const streaming = api.loadSession(original.id);
const metadata = api.loadSession(original.id, { metadata: true });
metadata.nested = { left: 1, right: 1 };
assert.equal(api.saveSession(metadata), true);
assert.equal(api.saveSession(streaming), true);
assert.deepEqual(streaming.nested, { left: 1, right: 1 });
const laterMetadata = api.loadSession(original.id, { metadata: true });
laterMetadata.nested.right = 2;
assert.equal(api.saveSession(laterMetadata), true);
streaming.nested.left = 2;
assert.equal(api.saveSession(streaming), true);
const restored = api.loadSession(original.id, { full: true });
assert.deepEqual(restored.nested, { left: 2, right: 2 });
assert.deepEqual(streaming.nested, { left: 2, right: 2 });
assert.deepEqual(restored.messages, original.messages);
});
check('中间窗口协调并发尾部提交后再次保存仍保留前后缀', dir => {
const api = createServerHarness(dir, { fullHistory: false });
const original = fixture('middle-window-rebase', 650);
assert.equal(api.saveSession(original), true);
const middle = api.loadSession(original.id, { window: { start: 30, end: 33 } });
const tail = api.loadSession(original.id);
const addedTail = { id: 'concurrent-tail', role: 'assistant', content: '另一运行对象追加的尾部' };
tail.messages.push(addedTail);
assert.equal(api.saveSession(tail), true);
middle.messages[0].content = '中间窗口第一次更新';
middle.messages.splice(1, 1);
assert.equal(api.saveSession(middle), true);
assert.deepEqual(middle.messages.map(message => message.id), ['message-30', 'message-32']);
assert.equal(middle.historySnapshotBaseIndex, 30);
middle.messages[0].content = '中间窗口第二次更新';
middle.messages.splice(1, 0, { id: 'middle-second-insert', role: 'assistant', content: '第二次保存前插内容' });
const expected = [...original.messages.slice(0, 30), ...middle.messages, ...original.messages.slice(33), addedTail];
assert.equal(api.saveSession(middle), true);
assert.deepEqual(api.loadSession(original.id, { full: true }).messages, expected);
});
if (failures.length > 0) {
console.error(`完整历史回归失败:${failures.length} 项,已通过 ${checks} 项`);
process.exitCode = 1;