fix(agents): split sandbox backend handle types

This commit is contained in:
Vincent Koc
2026-04-09 08:50:30 +01:00
parent f374fff3bd
commit 7d6af7e154
7 changed files with 101 additions and 66 deletions

View File

@@ -1,7 +1,6 @@
import fsPromises from "node:fs/promises";
import path from "node:path";
import type {
SandboxContext,
SandboxFsBridge,
SandboxFsStat,
SandboxResolvedPath,
@@ -10,6 +9,10 @@ import { createWritableRenameTargetResolver } from "openclaw/plugin-sdk/sandbox"
import type { OpenShellSandboxBackend } from "./backend.js";
import { movePathWithCopyFallback } from "./mirror.js";
type OpenShellFsBridgeContext = Parameters<
NonNullable<OpenShellSandboxBackend["createFsBridge"]>
>[0]["sandbox"];
type ResolvedMountPath = SandboxResolvedPath & {
mountHostRoot: string;
writable: boolean;
@@ -17,7 +20,7 @@ type ResolvedMountPath = SandboxResolvedPath & {
};
export function createOpenShellFsBridge(params: {
sandbox: SandboxContext;
sandbox: OpenShellFsBridgeContext;
backend: OpenShellSandboxBackend;
}): SandboxFsBridge {
return new OpenShellFsBridge(params.sandbox, params.backend);
@@ -30,7 +33,7 @@ class OpenShellFsBridge implements SandboxFsBridge {
);
constructor(
private readonly sandbox: SandboxContext,
private readonly sandbox: OpenShellFsBridgeContext,
private readonly backend: OpenShellSandboxBackend,
) {}

View File

@@ -0,0 +1,65 @@
import type { SandboxFsBridge } from "./fs-bridge.types.js";
export type SandboxBackendId = string;
export type SandboxBackendExecSpec = {
argv: string[];
env: NodeJS.ProcessEnv;
stdinMode: "pipe-open" | "pipe-closed";
finalizeToken?: unknown;
};
export type SandboxBackendCommandParams = {
script: string;
args?: string[];
stdin?: Buffer | string;
allowFailure?: boolean;
signal?: AbortSignal;
};
export type SandboxBackendCommandResult = {
stdout: Buffer;
stderr: Buffer;
code: number;
};
export type SandboxFsBridgeContext = {
workspaceDir: string;
agentWorkspaceDir: string;
workspaceAccess: "none" | "ro" | "rw";
containerName: string;
containerWorkdir: string;
docker: {
binds?: string[];
};
backend?: {
runShellCommand(params: SandboxBackendCommandParams): Promise<SandboxBackendCommandResult>;
};
};
export type SandboxBackendHandle = {
id: SandboxBackendId;
runtimeId: string;
runtimeLabel: string;
workdir: string;
env?: Record<string, string>;
configLabel?: string;
configLabelKind?: string;
capabilities?: {
browser?: boolean;
};
buildExecSpec(params: {
command: string;
workdir?: string;
env: Record<string, string>;
usePty: boolean;
}): Promise<SandboxBackendExecSpec>;
finalizeExec?: (params: {
status: "completed" | "failed";
exitCode: number | null;
timedOut: boolean;
token?: unknown;
}) => Promise<void>;
runShellCommand(params: SandboxBackendCommandParams): Promise<SandboxBackendCommandResult>;
createFsBridge?: (params: { sandbox: SandboxFsBridgeContext }) => SandboxFsBridge;
};

View File

@@ -1,58 +1,17 @@
import type { OpenClawConfig } from "../../config/config.js";
import { normalizeOptionalLowercaseString } from "../../shared/string-coerce.js";
import type { SandboxFsBridge } from "./fs-bridge.types.js";
import type { SandboxBackendHandle, SandboxBackendId } from "./backend-handle.types.js";
import type { SandboxRegistryEntry } from "./registry.js";
import type { SandboxConfig, SandboxContext } from "./types.js";
import type { SandboxConfig } from "./types.js";
export type SandboxBackendId = string;
export type SandboxBackendExecSpec = {
argv: string[];
env: NodeJS.ProcessEnv;
stdinMode: "pipe-open" | "pipe-closed";
finalizeToken?: unknown;
};
export type SandboxBackendCommandParams = {
script: string;
args?: string[];
stdin?: Buffer | string;
allowFailure?: boolean;
signal?: AbortSignal;
};
export type SandboxBackendCommandResult = {
stdout: Buffer;
stderr: Buffer;
code: number;
};
export type SandboxBackendHandle = {
id: SandboxBackendId;
runtimeId: string;
runtimeLabel: string;
workdir: string;
env?: Record<string, string>;
configLabel?: string;
configLabelKind?: string;
capabilities?: {
browser?: boolean;
};
buildExecSpec(params: {
command: string;
workdir?: string;
env: Record<string, string>;
usePty: boolean;
}): Promise<SandboxBackendExecSpec>;
finalizeExec?: (params: {
status: "completed" | "failed";
exitCode: number | null;
timedOut: boolean;
token?: unknown;
}) => Promise<void>;
runShellCommand(params: SandboxBackendCommandParams): Promise<SandboxBackendCommandResult>;
createFsBridge?: (params: { sandbox: SandboxContext }) => SandboxFsBridge;
};
export type {
SandboxBackendCommandParams,
SandboxBackendCommandResult,
SandboxBackendExecSpec,
SandboxBackendHandle,
SandboxBackendId,
SandboxFsBridgeContext,
} from "./backend-handle.types.js";
export type SandboxBackendRuntimeInfo = {
running: boolean;

View File

@@ -1,6 +1,9 @@
import fs from "node:fs";
import { normalizeOptionalLowercaseString } from "../../shared/string-coerce.js";
import type { SandboxBackendCommandResult } from "./backend.js";
import type {
SandboxBackendCommandResult,
SandboxFsBridgeContext,
} from "./backend-handle.types.js";
import { runDockerSandboxShellCommand } from "./docker-backend.js";
import {
buildPinnedMkdirpPlan,
@@ -16,7 +19,7 @@ import {
resolveSandboxFsPathWithMounts,
type SandboxResolvedFsPath,
} from "./fs-paths.js";
import type { SandboxContext, SandboxWorkspaceAccess } from "./types.js";
import type { SandboxWorkspaceAccess } from "./types.js";
type RunCommandOptions = {
args?: string[];
@@ -27,16 +30,18 @@ type RunCommandOptions = {
export type { SandboxFsBridge, SandboxFsStat, SandboxResolvedPath } from "./fs-bridge.types.js";
export function createSandboxFsBridge(params: { sandbox: SandboxContext }): SandboxFsBridge {
export function createSandboxFsBridge(params: {
sandbox: SandboxFsBridgeContext;
}): SandboxFsBridge {
return new SandboxFsBridgeImpl(params.sandbox);
}
class SandboxFsBridgeImpl implements SandboxFsBridge {
private readonly sandbox: SandboxContext;
private readonly sandbox: SandboxFsBridgeContext;
private readonly mounts: ReturnType<typeof buildSandboxFsMounts>;
private readonly pathGuard: SandboxFsPathGuard;
constructor(sandbox: SandboxContext) {
constructor(sandbox: SandboxFsBridgeContext) {
this.sandbox = sandbox;
this.mounts = buildSandboxFsMounts(sandbox);
const mountsByContainer = [...this.mounts].toSorted(

View File

@@ -1,11 +1,11 @@
import path from "node:path";
import { normalizeOptionalLowercaseString } from "../../shared/string-coerce.js";
import { resolveSandboxInputPath, resolveSandboxPath } from "../sandbox-paths.js";
import type { SandboxFsBridgeContext } from "./backend-handle.types.js";
import { splitSandboxBindSpec } from "./bind-spec.js";
import { SANDBOX_AGENT_WORKSPACE_MOUNT } from "./constants.js";
import { resolveSandboxHostPathViaExistingAncestor } from "./host-paths.js";
import { isPathInsideContainerRoot, normalizeContainerPath } from "./path-utils.js";
import type { SandboxContext } from "./types.js";
export type SandboxFsMount = {
hostRoot: string;
@@ -58,7 +58,7 @@ export function parseSandboxBindMount(spec: string): ParsedBindMount | null {
};
}
export function buildSandboxFsMounts(sandbox: SandboxContext): SandboxFsMount[] {
export function buildSandboxFsMounts(sandbox: SandboxFsBridgeContext): SandboxFsMount[] {
const mounts: SandboxFsMount[] = [
{
hostRoot: path.resolve(sandbox.workspaceDir),

View File

@@ -1,6 +1,10 @@
import path from "node:path";
import { isPathInside } from "../../infra/path-guards.js";
import type { SandboxBackendCommandParams, SandboxBackendCommandResult } from "./backend.js";
import type {
SandboxBackendCommandParams,
SandboxBackendCommandResult,
SandboxFsBridgeContext,
} from "./backend-handle.types.js";
import { SANDBOX_PINNED_MUTATION_PYTHON } from "./fs-bridge-mutation-helper.js";
import { createWritableRenameTargetResolver } from "./fs-bridge-rename-targets.js";
import type { SandboxFsBridge, SandboxFsStat, SandboxResolvedPath } from "./fs-bridge.types.js";
@@ -8,7 +12,6 @@ import {
isPathInsideContainerRoot,
normalizeContainerPath as normalizeSandboxContainerPath,
} from "./path-utils.js";
import type { SandboxContext } from "./types.js";
type ResolvedRemotePath = SandboxResolvedPath & {
writable: boolean;
@@ -29,7 +32,7 @@ export type RemoteShellSandboxHandle = {
};
export function createRemoteShellSandboxFsBridge(params: {
sandbox: SandboxContext;
sandbox: SandboxFsBridgeContext;
runtime: RemoteShellSandboxHandle;
}): SandboxFsBridge {
return new RemoteShellSandboxFsBridge(params.sandbox, params.runtime);
@@ -42,7 +45,7 @@ class RemoteShellSandboxFsBridge implements SandboxFsBridge {
);
constructor(
private readonly sandbox: SandboxContext,
private readonly sandbox: SandboxFsBridgeContext,
private readonly runtime: RemoteShellSandboxHandle,
) {}

View File

@@ -1,4 +1,4 @@
import type { SandboxBackendHandle, SandboxBackendId } from "./backend.js";
import type { SandboxBackendHandle, SandboxBackendId } from "./backend-handle.types.js";
import type { SandboxFsBridge } from "./fs-bridge.types.js";
import type { SandboxDockerConfig } from "./types.docker.js";