✨ feat(vscode): 移交时显示步骤,会话包超限先停

This commit is contained in:
cruldra committed 2026-09-27 18:36:01 +08:00
1 parent 9220bad599
commit 162134dcc0
6 files changed
+412 -32

No files matched your search

+112
View File
@@ -0,0 +1,112 @@
import { Buffer } from 'node:buffer'
import { randomBytes } from 'node:crypto'
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'
import * as os from 'node:os'
import * as path from 'node:path'
import { afterEach, describe, expect, it } from 'vitest'
import { formatByteSize, HANDOFF_ATTACHMENT_MAX_BYTES, HandoffArchiveTooLargeError, packHandoff } from './handoffArchive'
import { ensureHandoffFits, HandoffSourcesTooLargeError, measureHandoffSources } from './handoffSize'
const tempDirs: string[] = []
afterEach(() => {
while (tempDirs.length > 0) {
const dir = tempDirs.pop()
if (dir)
rmSync(dir, { recursive: true, force: true })
}
})
function tempDir(prefix: string): string {
const dir = mkdtempSync(path.join(os.tmpdir(), prefix))
tempDirs.push(dir)
return dir
}
const BRAIN = 'aaaaaaaa-0000-4000-8000-000000000001'
const IMPL = 'bbbbbbbb-0000-4000-8000-000000000002'
const REVIEW = '019dcb27-58a6-70a1-a5d1-bfc7f3ed9d0a'
function fixture(implBytes: Buffer): { projectsRoot: string, codexRoot: string } {
const projectsRoot = tempDir('handoff-size-projects-')
const codexRoot = tempDir('handoff-size-codex-')
const proj = path.join(projectsRoot, '-Users-chw-proj')
const wt = path.join(projectsRoot, '-Users-chw-wt-x')
mkdirSync(proj, { recursive: true })
mkdirSync(path.join(wt, IMPL, 'subagents'), { recursive: true })
writeFileSync(path.join(proj, `${BRAIN}.jsonl`), 'brain\n')
writeFileSync(path.join(wt, `${IMPL}.jsonl`), implBytes)
writeFileSync(path.join(wt, IMPL, 'subagents', 'agent-1.jsonl'), '{}\n')
const day = path.join(codexRoot, '2026', '08', '26')
mkdirSync(day, { recursive: true })
writeFileSync(path.join(day, `rollout-2026-08-26T01-02-03-${REVIEW}.jsonl`), 'review\n')
return { projectsRoot, codexRoot }
}
function source(roots: { projectsRoot: string, codexRoot: string }) {
return {
src: {
issueNumber: 448,
from: 'chw',
sessions: {
sessionId: BRAIN,
implementSessionId: IMPL,
testSessionId: 'missing-0000-4000-8000-000000000000',
reviewSessionId: REVIEW,
},
profiles: {},
},
roots,
}
}
describe('formatByteSize', () => {
it('100MB 上限按整数 MB 显示', () => {
expect(formatByteSize(HANDOFF_ATTACHMENT_MAX_BYTES)).toBe('100MB')
})
})
describe('measureHandoffSources', () => {
it('jsonl、会话目录、codex 加在一起,缺失的会话不出现', async () => {
const roots = fixture(Buffer.from('impl\n'))
const { src } = source(roots)
const items = await measureHandoffSources(src, roots)
expect(items).toEqual([
{ role: '头脑风暴', bytes: Buffer.byteLength('brain\n') },
{ role: '实施', bytes: Buffer.byteLength('impl\n') + Buffer.byteLength('{}\n') },
{ role: '审查', bytes: Buffer.byteLength('review\n') },
])
})
})
describe('ensureHandoffFits', () => {
it('原始大小不超过上限时直接通过', async () => {
const roots = fixture(Buffer.from('impl\n'))
const { src } = source(roots)
const items = await ensureHandoffFits(src, roots, { maxBytes: 1024 * 1024 })
expect(items.map(item => item.role)).toEqual(['头脑风暴', '实施', '审查'])
})
it('压缩后超过上限就停,并说明还没开始移交', async () => {
const roots = fixture(randomBytes(80 * 1024))
const { src } = source(roots)
await expect(ensureHandoffFits(src, roots, { maxBytes: 1024 })).rejects.toBeInstanceOf(HandoffSourcesTooLargeError)
try {
await ensureHandoffFits(src, roots, { maxBytes: 1024 })
}
catch (err) {
expect(err).toBeInstanceOf(HandoffSourcesTooLargeError)
expect((err as Error).message).toContain('没有开始移交')
expect((err as Error).message).toContain('实施')
expect((err as Error).message).toContain('1.0KB')
}
})
})
describe('packHandoff', () => {
it('产出超过 maxBytes 就失败,不等到打完', async () => {
const staging = tempDir('handoff-pack-')
writeFileSync(path.join(staging, 'big.bin'), randomBytes(80 * 1024))
const out = path.join(tempDir('handoff-pack-out-'), 'bundle.tgz')
await expect(packHandoff(staging, out, { maxBytes: 1024 })).rejects.toBeInstanceOf(HandoffArchiveTooLargeError)
})
})