Merge remote-tracking branch 'origin/master' into codex/session-scoped-sandbox-roots

# Conflicts:
#	.agents/notes/implemented/feature/2026-07-14-cross-family-fs-sandbox.i18n.yaml
#	.agents/notes/implemented/feature/2026-07-14-cross-family-fs-sandbox.md
#	.agents/notes/implemented/feature/2026-07-14-cross-family-fs-sandbox.zh.md
#	examples/acp-agent/README.md
#	packages/examples/agent-spine-demo/package.json
#	packages/fs/fs-sandbox/src/index.ts
#	packages/fs/tool-fs-search/tests/tools.spec.ts
#	packages/support/acp-snapshot/README.md
#	packages/support/acp-snapshot/src/suite.ts
#	pnpm-lock.yaml
#	scripts/type-equiv.manifest.json
This commit is contained in:
Tianyi Cui
2026-07-22 21:37:30 +08:00
1133 changed files with 67897 additions and 10909 deletions
+9 -9
View File
@@ -30,7 +30,6 @@
* @module @deepseek-ai/dsh-fs-sandbox
*/
import { sep } from 'node:path'
import { Context } from 'cordis'
import { LocalFileSystem } from '@deepseek-ai/dsh-fs-local'
import type { Config as LocalConfig } from '@deepseek-ai/dsh-fs-local'
@@ -39,6 +38,7 @@ import type { FsEditOutcome, FsEditRequest, FsTarget, FsVersion, FsWriteIntent,
import { writableRoots } from '@deepseek-ai/dsh-sandbox'
import type { SandboxExecutionPolicy, SandboxMode } from '@deepseek-ai/dsh-sandbox'
import type {} from '@deepseek-ai/dsh-sandbox-policy'
import { isPathUnder } from './containment.ts'
/**
* Plugin config: the local backend's knobs, verbatim (only `cwd`, the resolve
@@ -48,13 +48,6 @@ import type {} from '@deepseek-ai/dsh-sandbox-policy'
*/
export type Config = LocalConfig
/** Whether `path` is `root` itself or lies beneath it (both already canonical). */
function isUnder(path: string, root: string): boolean {
if (path === root) return true
const prefix = root.endsWith(sep) ? root : root + sep
return path.startsWith(prefix)
}
/**
* Sandbox-enforcing filesystem backend. Registers as `ctx.fs` (loading it
* INSTEAD OF `dsh-fs-local`, together with a `ctx.sandboxPolicy`, is the whole
@@ -141,7 +134,14 @@ export class SandboxedFileSystem extends LocalFileSystem {
// symlink ancestor swapped since the tool resolved this target), and the
// mutation delegates with THIS fresh target — never the stale one.
const fresh = await this.resolve(target.displayPath)
if (!writableRoots(policy).some(root => isUnder(fresh.targetKey, root))) {
let contained = false
for (const root of writableRoots(policy)) {
if (await isPathUnder(fresh.targetKey, root)) {
contained = true
break
}
}
if (!contained) {
throw new FsError(`cannot write "${target.displayPath}": file access denied under workspace-write mode`, 'FS_SANDBOX_DENIED')
}
return fresh