fix: 消除会话列表解析阻塞并按需加载工具详情
This commit is contained in:
95
scripts/session-metadata-unit.js
Normal file
95
scripts/session-metadata-unit.js
Normal file
@@ -0,0 +1,95 @@
|
||||
'use strict';
|
||||
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
const os = require('node:os');
|
||||
const path = require('node:path');
|
||||
const { performance } = require('node:perf_hooks');
|
||||
const { createSessionHistoryStore } = require('../lib/session-history-store');
|
||||
const source = fs.readFileSync(path.join(__dirname, '..', 'server.js'), 'utf8');
|
||||
function extract(name) {
|
||||
const start = source.indexOf(`function ${name}(`);
|
||||
assert(start >= 0, `缺少生产函数 ${name}`);
|
||||
const end = source.indexOf('\nfunction ', start + 1);
|
||||
return source.slice(start, end < 0 ? undefined : end);
|
||||
}
|
||||
const keys = source.slice(source.indexOf('const SESSION_META_PREVIEW_TOP_LEVEL_KEYS ='), source.indexOf('\nfunction isJsonWhitespace('));
|
||||
const names = ['isJsonWhitespace', 'skipJsonWhitespace', 'readJsonStringEnd', 'readJsonContainerEnd', 'readJsonValueEnd',
|
||||
'parseJsonTopLevelPrefixFields', 'skipJsonWhitespaceBackward', 'readJsonStringStart', 'readJsonValueStart',
|
||||
'parseJsonTopLevelSuffixFields', 'readSessionPreviewTopLevelFields', 'previewStringField', 'previewBooleanField',
|
||||
'previewCreatedFromKind', 'normalizeCreatedFromSourceSessionId', 'readSessionPreview', 'loadSessionMetaFromFile'];
|
||||
let parses = 0;
|
||||
const json = { parse(value) { parses += 1; return JSON.parse(value); } };
|
||||
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'ccweb-session-metadata-'));
|
||||
const observations = [];
|
||||
const trackedFs = new Proxy(fs, { get(target, name) {
|
||||
if (!['readFileSync', 'readSync', 'writeFileSync', 'renameSync'].includes(name)) return target[name];
|
||||
return (...args) => { observations.push({ name, file: String(args[0]) }); return target[name](...args); };
|
||||
} });
|
||||
const store = createSessionHistoryStore({ sessionsDir: root, fs: trackedFs });
|
||||
const api = new Function('fs', 'path', 'sessionHistoryStore', 'JSON', `
|
||||
const SESSION_META_FULL_PARSE_MAX_BYTES = 65536;
|
||||
const SESSION_META_PREVIEW_BYTES = 131072;
|
||||
const SESSION_LOAD_MAX_BYTES = 33554432;
|
||||
function normalizeSession(value) { if (value.messages.length) throw Error('列表不应规范化历史正文'); return value; }
|
||||
function normalizeAgent(value) { return value || 'claude'; }
|
||||
function getSessionAgent(value) { return normalizeAgent(value.agent); }
|
||||
function normalizeGiteaSource(value) { return value || null; }
|
||||
function sessionCreatedFromKind(value) { return value.createdFrom?.kind; }
|
||||
function sanitizeId(value) { return /^[a-zA-Z0-9_-]+$/.test(value) ? value : ''; }
|
||||
function isCodexThreadGoalActive(value) { return value?.status === 'active'; }
|
||||
function plog() {}
|
||||
${keys}
|
||||
${names.map(extract).join('\n')}
|
||||
return { suffix: parseJsonTopLevelSuffixFields, preview: readSessionPreviewTopLevelFields, meta: loadSessionMetaFromFile };
|
||||
`)(trackedFs, path, store, json);
|
||||
|
||||
const fields = { id: '真实会话', title: '带"引号"及\\反斜杠', cwd: '/home/项目', hasUnread: false,
|
||||
createdFrom: { kind: 'mcp', sourceSessionId: 'parent', nested: { title: '不能混入顶层' } },
|
||||
giteaSource: { title: '来源', number: 2 }, pinnedAt: null };
|
||||
const nested = { id: '嵌套ID', title: '工具标题', cwd: '/错误目录', input: '复杂字符串\\\\\"尾部,}]' };
|
||||
const doc = JSON.stringify({ messages: Array.from({ length: 8000 }, () => nested), ...fields });
|
||||
const tail = doc.slice(-131072);
|
||||
parses = 0;
|
||||
const start = performance.now();
|
||||
assert.deepEqual(api.suffix(tail), fields);
|
||||
assert(parses < 40, `尾部解析不应随嵌套伪字段增加而重复调用JSON.parse,实际${parses}`);
|
||||
console.log(`通过:128KB高密度嵌套伪字段只解析${parses}次,耗时${Math.round(performance.now() - start)}ms`);
|
||||
const fieldsStart = doc.lastIndexOf(',"id":"真实会话"') + 1;
|
||||
for (let cut = 0; cut < fieldsStart; cut += 1031) assert.deepEqual(api.suffix(doc.slice(cut)), fields);
|
||||
assert.deepEqual(api.suffix('{"title":"旧","title":"新","messages":[]}'), { title: '新' });
|
||||
assert.deepEqual(api.suffix(JSON.stringify({ messages: [nested] }).slice(6)), {});
|
||||
assert.equal(api.preview({ head: '{"id":"boundary","cwd":', tail: '"/不能拼接","messages":[]}' }).cwd, undefined);
|
||||
for (const value of ['尾部\\', '尾部\\\\', '引号"', '\\"\\\\"\n', '转义括号}]\\"']) {
|
||||
const text = JSON.stringify({ messages: [nested], title: value, createdFrom: { kind: value } });
|
||||
assert.deepEqual(api.suffix(text), { title: value, createdFrom: { kind: value } });
|
||||
}
|
||||
console.log('通过:截断边界、转义引号、重复键、嵌套键和对象元数据');
|
||||
|
||||
const id = 'archive-only';
|
||||
store.save({ id, title: '归档最新标题', agent: 'codexapp', cwd: '/home/真实项目', hasUnread: true,
|
||||
codexAppGoal: { status: 'active' }, messages: [{ id: 'message', role: 'assistant', content: '正文'.repeat(1000000) }] }, { complete: true });
|
||||
observations.length = 0;
|
||||
assert.equal(api.meta(path.join(root, `${id}.json`)).title, '归档最新标题');
|
||||
assert(!fs.existsSync(path.join(root, `${id}.json`)), '列举归档不应生成预览快照');
|
||||
fs.writeFileSync(path.join(root, `${id}.json`), '{这是陈旧且损坏的缓存');
|
||||
const meta = api.meta(path.join(root, `${id}.json`));
|
||||
assert.equal(meta.title, '归档最新标题');
|
||||
assert.equal(meta.hasUnread, true);
|
||||
assert.equal(meta.codexAppGoalActive, true);
|
||||
const manifestReads = observations.filter(item => item.name === 'readFileSync' && /manifest\.json$/.test(item.file)).length;
|
||||
api.meta(path.join(root, `${id}.json`));
|
||||
assert.equal(observations.filter(item => item.name === 'readFileSync' && /manifest\.json$/.test(item.file)).length, manifestReads,
|
||||
'修订未变时列表不能反复解析归档清单');
|
||||
assert(!observations.some(item => /chunks/.test(item.file)), '列表不得读取任何正文块');
|
||||
assert(!observations.some(item => /write|rename/.test(item.name)), '列表不得重建快照或写文件');
|
||||
assert(!observations.some(item => item.name === 'readFileSync' && item.file === path.join(root, `${id}.json`)), '有归档时不得解析快照');
|
||||
const latest = store.loadMetadata(id);
|
||||
latest.title = '修订后的标题';
|
||||
latest.hasUnread = false;
|
||||
store.save(latest);
|
||||
assert.equal(api.meta(path.join(root, `${id}.json`)).title, '修订后的标题');
|
||||
assert.equal(api.meta(path.join(root, `${id}.json`)).hasUnread, false);
|
||||
fs.writeFileSync(path.join(root, 'legacy.json'), JSON.stringify({ id: 'legacy', messages: [nested], padding: 'x'.repeat(140000), ...fields }));
|
||||
assert.equal(api.meta(path.join(root, 'legacy.json')).title, fields.title);
|
||||
console.log('通过:归档列表不读正文、不写快照,缺失/损坏快照与旧格式均可列举');
|
||||
Reference in New Issue
Block a user