@dawn-ai/workspace

Use this when

Use this package to implement or wrap filesystem, command, and execution-sandbox backends for a Dawn application. The root owns the portable backend and sandbox contracts, middleware composition, and logging wrappers. Import /node only for the local filesystem and shell implementations.

Install and import

bash
pnpm add @dawn-ai/workspace
ts
import { compose, withExecLogging, type SandboxProvider } from "@dawn-ai/workspace"
import { localExec, localFilesystem } from "@dawn-ai/workspace/node"

Compatibility and audience

SurfaceRuntimePurityAudienceStability
@dawn-ai/workspaceedge-safedependency-freeapplicationsupported
@dawn-ai/workspace/nodenode-onlynot-claimedapplicationsupported

The root's emitted subpath graph has no runtime package or Node built-in dependencies. Its LocalExecOptions and LocalFilesystemOptions exports are types only. The /node factories use Node child-process, filesystem, path, and utility APIs.

Public exports

@dawn-ai/workspace

ExportResponsibility
composeCompose backend middleware right-to-left.
LocalExecOptionsConfigure the local command backend without importing its runtime.
LocalFilesystemOptionsConfigure the local filesystem backend without importing its runtime.
SandboxConfigConfigure provider selection and lifecycle policy.
SandboxHandleDescribe one acquired sandbox.
SandboxPolicyDescribe per-thread network, environment, resource, and security policy.
SandboxProviderDefine sandbox acquisition, release, and destruction.
SandboxSecurityPolicyDescribe provider-agnostic hardening intent.
BackendContextCarry cancellation and the active workspace root.
ExecBackendDefine shell-command execution.
ExecMiddlewareWrap an execution backend.
FilesystemBackendDefine text, binary, directory, canonicalization, and optional file operations.
FilesystemMiddlewareWrap a filesystem backend.
LoggingOptionsConfigure backend log delivery.
withExecLoggingLog command calls around an execution backend.
withFilesystemLoggingLog public filesystem calls while preserving optional capabilities.

@dawn-ai/workspace/node

The /node entry independently owns the following four exports. The two option types are also available from the dependency-free root for type-only consumers; the factories are not.

ExportResponsibility
LocalExecOptionsConfigure timeout and command allowlisting.
localExecCreate the Node shell-command backend.
LocalFilesystemOptionsConfigure the default file-size limit.
localFilesystemCreate the Node filesystem backend.

Key contracts

ts
export interface BackendContext {
  readonly signal: AbortSignal
  readonly workspaceRoot: string
}

Fields: @dawn-ai/workspace#.:BackendContext

FieldTypeRequiredDescription
readonly signalAbortSignalyesAbort work when the parent run is cancelled.
readonly workspaceRootstringyesName the active route workspace's absolute root.
ts
export interface FilesystemBackend {
  readFile(
    path: string,
    ctx: BackendContext,
    opts?: { readonly maxBytes?: number },
  ): Promise<string>
  readBinaryFile?(
    path: string,
    ctx: BackendContext,
    opts?: { readonly maxBytes?: number },
  ): Promise<Uint8Array>
  writeFile(
    path: string,
    content: string,
    ctx: BackendContext,
  ): Promise<{ readonly bytesWritten: number }>
  listDir(path: string, ctx: BackendContext): Promise<readonly string[]>
  realPath(path: string, ctx: BackendContext): Promise<string>
  statFile?(
    path: string,
    ctx: BackendContext,
  ): Promise<{ readonly size: number; readonly mtimeMs: number }>
  removeFile?(path: string, ctx: BackendContext): Promise<void>
  touchFile?(path: string, ctx: BackendContext): Promise<void>
  mkdir?(path: string, ctx: BackendContext): Promise<void>
}
ts
export interface ExecBackend {
  runCommand(
    args: {
      readonly command: string
      readonly cwd?: string
      readonly env?: Readonly<Record<string, string>>
    },
    ctx: BackendContext,
  ): Promise<{
    readonly stdout: string
    readonly stderr: string
    readonly exitCode: number
  }>
}
ts
export declare function compose<T>(
  ...middlewares: ReadonlyArray<(next: T) => T>
): (base: T) => T

Behavior contract workspace.compose.order

Backend middleware composes right-to-left, with the first listed middleware outermost.

Sandbox contracts

ts
export interface SandboxPolicy {
  readonly network:
    | { readonly mode: "allow"; readonly denylist?: readonly string[] }
    | { readonly mode: "deny"; readonly allowlist?: readonly string[] }
  readonly env?: Readonly<Record<string, string>>
  readonly resources?: {
    readonly memoryMb?: number
    readonly cpus?: number
    readonly timeoutMs?: number
    readonly diskGb?: number
  }
  readonly security?: SandboxSecurityPolicy
}

Fields: @dawn-ai/workspace#.:SandboxPolicy

FieldTypeRequiredDescription
readonly network| { readonly mode: "allow"; readonly denylist?: readonly string[] } | { readonly mode: "deny"; readonly allowlist?: readonly string[] }yesSet the provider's network policy intent.
readonly envReadonly<Record<string, string>>noSupply the sandbox environment explicitly.
readonly resources{ readonly memoryMb?: number; readonly cpus?: number; readonly timeoutMs?: number; readonly diskGb?: number }noRequest provider resource limits.
readonly securitySandboxSecurityPolicynoOverride provider hardening intent.
ts
export interface SandboxSecurityPolicy {
  readonly dropAllCapabilities?: boolean
  readonly noNewPrivileges?: boolean
  readonly readOnlyRootFilesystem?: boolean
  readonly runAsNonRoot?: boolean | { readonly uid: number; readonly gid: number }
  readonly pidsLimit?: number
}

Fields: @dawn-ai/workspace#.:SandboxSecurityPolicy

FieldTypeRequiredDescription
readonly dropAllCapabilitiesbooleannoRequest dropping every Linux capability.
readonly noNewPrivilegesbooleannoRequest blocking setuid/setgid escalation.
readonly readOnlyRootFilesystembooleannoRequest an immutable root filesystem.
readonly runAsNonRootboolean | { readonly uid: number; readonly gid: number }noRequest non-root execution or an explicit identity.
readonly pidsLimitnumbernoRequest a process-count limit.
ts
export interface SandboxHandle {
  readonly threadId: string
  readonly filesystem: FilesystemBackend
  readonly exec: ExecBackend
  readonly workspaceRoot: string
}

Fields: @dawn-ai/workspace#.:SandboxHandle

FieldTypeRequiredDescription
readonly threadIdstringyesIdentify the owning conversation thread.
readonly filesystemFilesystemBackendyesRoute file operations into the sandbox.
readonly execExecBackendyesRoute command execution into the sandbox.
readonly workspaceRootstringyesName the absolute root inside the sandbox.
ts
export interface SandboxProvider {
  readonly name: string
  acquire(input: {
    readonly threadId: string
    readonly policy: SandboxPolicy
    readonly signal: AbortSignal
  }): Promise<SandboxHandle>
  release(threadId: string): Promise<void>
  destroy(threadId: string): Promise<void>
  preflight?(): Promise<{
    readonly ok: boolean
    readonly detail?: string
    readonly warnings?: readonly string[]
  }>
}

Fields: @dawn-ai/workspace#.:SandboxProvider

FieldTypeRequiredDescription
readonly namestringyesIdentify the provider.
ts
export interface SandboxConfig {
  readonly provider: SandboxProvider
  readonly network?: SandboxPolicy["network"]
  readonly env?: SandboxPolicy["env"]
  readonly resources?: SandboxPolicy["resources"]
  readonly security?: SandboxSecurityPolicy
  readonly idleTimeoutMs?: number
}

Fields: @dawn-ai/workspace#.:SandboxConfig

FieldTypeRequiredDescription
readonly providerSandboxProvideryesSupply the sandbox provider.
readonly networkSandboxPolicy["network"]noSet the default network policy.
readonly envSandboxPolicy["env"]noSet the default explicit environment.
readonly resourcesSandboxPolicy["resources"]noSet default resource requests.
readonly securitySandboxSecurityPolicynoSet default hardening intent.
readonly idleTimeoutMsnumbernoSet the manager idle-reap window; default 600,000 ms.
ts
export interface LocalExecOptions {
  readonly timeout?: number
  readonly allowedCommands?: readonly RegExp[]
}
ts
export declare function localExec(opts?: LocalExecOptions): ExecBackend

Behavior contract workspace.exec.timeout

The local exec backend enforces its configured timeout.

ts
export interface LocalFilesystemOptions {
  readonly maxFileBytes?: number
}
ts
export declare function localFilesystem(opts?: LocalFilesystemOptions): FilesystemBackend

localFilesystem.realPath resolves an escaping symlink to its outside real path; Core owns any path-jail enforcement.

Lifecycle, failure, and trust boundaries

Backend methods receive absolute paths and a cancellation signal from their caller. Core owns the path jail and canonical-root enforcement. localFilesystem canonicalizes existing ancestors, including escaping symlinks, and does not enforce that boundary. Its reads default to a 256 KiB cap, allow a per-call override, and reject missing or oversized files. Writes create parent directories.

localExec defaults to a 30-second timeout. A non-empty allowedCommands list rejects a command unless a regular expression matches. When args.env is omitted, localExec inherits process.env; when supplied, it replaces that environment. By contrast, SandboxPolicy.env is the explicit environment injected into a sandbox and does not inherit the host environment.

Sandbox release() and destroy() express distinct lifecycle responsibilities, but exact persistence and cleanup behavior belongs to each provider. Security fields express provider-agnostic intent; an unset field does not by itself prove a particular provider's enforcement.

Logging middleware defaults to console.error. A custom destination receives { method, args }. Filesystem logging does not serialize binary content and passes through realPath, statFile, removeFile, touchFile, and mkdir; treat logged paths, command text, working directories, and text write contents as potentially sensitive.

ts
import { compose, withExecLogging, withFilesystemLogging } from "@dawn-ai/workspace"
import { localExec, localFilesystem } from "@dawn-ai/workspace/node"
 
const filesystem = compose(withFilesystemLogging())(
  localFilesystem({ maxFileBytes: 512 * 1024 }),
)
const exec = compose(withExecLogging())(
  localExec({ timeout: 10_000, allowedCommands: [/^pnpm test\b/] }),
)

Continue with Workspace Filesystem, Execution Sandbox, and Permissions API.