'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('通过:归档列表不读正文、不写快照,缺失/损坏快照与旧格式均可列举');