fix(sandbox): ignore generated output and clean failed drafts

This commit is contained in:
xiaohan.yuan committed 2026-10-18 13:03:34 +08:00
1 parent d5512df549
commit 3be45999f7
3 files changed
+17 -7

No files matched your search

+1 -1
View File
@@ -8,7 +8,7 @@ import { assertActive, resolveSandboxPath, within } from './policy'
import { sandboxPathDenied } from './policy-store'
export type Snapshot = Map<string, { hash: string; size: number }>
const excludedDirectories = new Set(['.git', '.ycode', '.tmp', 'node_modules', '.venv', 'venv', '__pycache__'])
const excludedDirectories = new Set(['.git', '.ycode', '.tmp', 'node_modules', '.venv', 'venv', '__pycache__', 'out', 'dist', 'release'])
export async function regularBytes(file: string, fileLimit: number): Promise<Buffer> {
const before = await fs.lstat(file)
if (!before.isFile() || before.isSymbolicLink() || before.nlink !== 1 || before.size > fileLimit) throw new Error('快照包含链接、特殊文件或超过策略上限的文件')
+11 -6
View File
@@ -30,7 +30,7 @@ const same = (a?: WorkspaceEntry, b?: WorkspaceEntry): boolean => a?.kind === b?
const order = (tree: WorkspaceEntry[]): WorkspaceEntry[] => tree.sort((a, b) => a.path.localeCompare(b.path, 'en'))
const index = (tree: WorkspaceEntry[]): Map<string, WorkspaceEntry> => new Map(tree.map(entry => [key(entry.path), entry]))
const depth = (path: string): number => path.split('/').length
const excluded = new Set(['node_modules', '.tmp', '.venv', 'venv', '__pycache__'])
const excluded = new Set(['node_modules', '.tmp', '.venv', 'venv', '__pycache__', 'out', 'dist', 'release'])
export function workspaceKey(workspace: string, sessionId: string): string {
if (typeof sessionId !== 'string' || !sessionId || sessionId.length > 200 || /[\x00-\x1f]/.test(sessionId)) throw new Error('长期工作区需要有效的宿主会话身份')
@@ -240,11 +240,16 @@ export class PersistentWorkspace {
if (this.revision) { validateTree(this.revision.base, this.policy); validateTree(this.revision.tree, this.policy); return }
if (await exists(this.working)) throw new Error('长期工作区有未归属的物理目录,请先 discard')
await fs.mkdir(this.working)
const initial = await snapshot(this.policy.workspaceRoot, this.working, this.policy)
const tree = await this.capture(this.working), physicalIdentity = await sealWorkspace(this.working)
await this.assertCurrent()
await this.publish({ version: 1, workspaceRoot: this.policy.workspaceRoot, workspaceIdentity: this.rootIdentity, sessionId: this.sessionId,
policyFingerprint: this.policy.fingerprint, policyRevision: this.policy.revision, base: tree, tree, physicalIdentity, excluded: initial.excluded })
try {
const initial = await snapshot(this.policy.workspaceRoot, this.working, this.policy)
const tree = await this.capture(this.working), physicalIdentity = await sealWorkspace(this.working)
await this.assertCurrent()
await this.publish({ version: 1, workspaceRoot: this.policy.workspaceRoot, workspaceIdentity: this.rootIdentity, sessionId: this.sessionId,
policyFingerprint: this.policy.fingerprint, policyRevision: this.policy.revision, base: tree, tree, physicalIdentity, excluded: initial.excluded })
} catch (error) {
await fs.rm(this.working, { recursive: true, force: true })
throw error
}
}
async verifyPhysical(): Promise<void> {
if (!this.revision) throw new Error('长期工作区尚未初始化')