✨ feat(vscode): 移交时显示步骤,会话包超限先停
This commit is contained in:
1 parent
9220bad599
commit
162134dcc0
6 files changed
+412
-32
No files matched your search
@@ -0,0 +1,111 @@
|
||||
import type { HandoffRoots, HandoffSource } from './handoffBundle'
|
||||
import { promises as fsp } from 'node:fs'
|
||||
import * as os from 'node:os'
|
||||
import * as path from 'node:path'
|
||||
import { formatByteSize, HANDOFF_ATTACHMENT_MAX_BYTES, HandoffArchiveTooLargeError, packHandoff } from './handoffArchive'
|
||||
import { stageHandoffBundle } from './handoffBundle'
|
||||
import { CLAUDE_SESSION_FIELDS } from './handoffManifest'
|
||||
import { codexRolloutRelPathFor, findClaudeSessionFiles } from './sessionBundle'
|
||||
|
||||
const ROLE_LABEL = {
|
||||
brainstorm: '头脑风暴',
|
||||
implement: '实施',
|
||||
test: '测试',
|
||||
prototype: '原型',
|
||||
review: '审查',
|
||||
} as const
|
||||
|
||||
export interface HandoffSourceSize {
|
||||
role: string
|
||||
bytes: number
|
||||
}
|
||||
|
||||
export class HandoffSourcesTooLargeError extends Error {
|
||||
readonly items: HandoffSourceSize[]
|
||||
readonly limitBytes: number
|
||||
|
||||
constructor(items: HandoffSourceSize[], limitBytes: number) {
|
||||
super(formatHandoffTooLarge(items, limitBytes))
|
||||
this.name = 'HandoffSourcesTooLargeError'
|
||||
this.items = items
|
||||
this.limitBytes = limitBytes
|
||||
}
|
||||
}
|
||||
|
||||
export function formatHandoffTooLarge(items: HandoffSourceSize[], limitBytes: number): string {
|
||||
const parts = items.filter(item => item.bytes > 0).map(item => `${item.role} ${formatByteSize(item.bytes)}`)
|
||||
const total = items.reduce((sum, item) => sum + item.bytes, 0)
|
||||
return `会话太大,没有开始移交:${parts.join('、')},合计 ${formatByteSize(total)}。压缩后超过 Gitea 附件上限 ${formatByteSize(limitBytes)}`
|
||||
}
|
||||
|
||||
async function treeBytes(abs: string): Promise<number> {
|
||||
const st = await fsp.lstat(abs)
|
||||
if (st.isSymbolicLink())
|
||||
return 0
|
||||
if (!st.isDirectory())
|
||||
return st.size
|
||||
let total = 0
|
||||
for (const name of await fsp.readdir(abs))
|
||||
total += await treeBytes(path.join(abs, name))
|
||||
return total
|
||||
}
|
||||
|
||||
/** 统计即将打进会话包的文件。缺失的会话不出现。 */
|
||||
export async function measureHandoffSources(src: HandoffSource, roots: HandoffRoots): Promise<HandoffSourceSize[]> {
|
||||
const items: HandoffSourceSize[] = []
|
||||
for (const { field, kind } of CLAUDE_SESSION_FIELDS) {
|
||||
const id = src.sessions[field]
|
||||
if (!id)
|
||||
continue
|
||||
const files = await findClaudeSessionFiles(id, roots.projectsRoot)
|
||||
if (!files)
|
||||
continue
|
||||
let bytes = await treeBytes(files.jsonl)
|
||||
if (files.dir)
|
||||
bytes += await treeBytes(files.dir)
|
||||
items.push({ role: ROLE_LABEL[kind], bytes })
|
||||
}
|
||||
if (src.sessions.reviewSessionId) {
|
||||
const relPath = await codexRolloutRelPathFor(src.sessions.reviewSessionId, roots.codexRoot)
|
||||
if (relPath) {
|
||||
items.push({
|
||||
role: ROLE_LABEL.review,
|
||||
bytes: await treeBytes(path.join(roots.codexRoot, relPath)),
|
||||
})
|
||||
}
|
||||
}
|
||||
return items
|
||||
}
|
||||
|
||||
/**
|
||||
* 关终端、上传之前调用。
|
||||
* 原始大小不超过上限时直接过(压缩只会更小,最多多出可忽略的包头)。
|
||||
* 超过才真正压缩,产出一过上限就停。
|
||||
*/
|
||||
export async function ensureHandoffFits(
|
||||
src: HandoffSource,
|
||||
roots: HandoffRoots,
|
||||
opts?: { maxBytes?: number, onProgress?: (packedBytes: number) => void },
|
||||
): Promise<HandoffSourceSize[]> {
|
||||
const maxBytes = opts?.maxBytes ?? HANDOFF_ATTACHMENT_MAX_BYTES
|
||||
const items = await measureHandoffSources(src, roots)
|
||||
const total = items.reduce((sum, item) => sum + item.bytes, 0)
|
||||
if (total <= maxBytes)
|
||||
return items
|
||||
const staging = await fsp.mkdtemp(path.join(os.tmpdir(), 'spx-handoff-size-'))
|
||||
const outDir = await fsp.mkdtemp(path.join(os.tmpdir(), 'spx-handoff-size-out-'))
|
||||
try {
|
||||
await stageHandoffBundle(src, staging, roots)
|
||||
await packHandoff(staging, path.join(outDir, 'probe.tgz'), { maxBytes, onProgress: opts?.onProgress })
|
||||
return items
|
||||
}
|
||||
catch (err) {
|
||||
if (err instanceof HandoffArchiveTooLargeError)
|
||||
throw new HandoffSourcesTooLargeError(items, maxBytes)
|
||||
throw err
|
||||
}
|
||||
finally {
|
||||
await fsp.rm(staging, { recursive: true, force: true }).catch(() => {})
|
||||
await fsp.rm(outDir, { recursive: true, force: true }).catch(() => {})
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user