mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-10 10:42:43 +00:00
fix(agents): map sandbox workdir from container path
This commit is contained in:
committed by
Peter Steinberger
parent
b1cc8ffe9e
commit
c48a0621ff
77
src/agents/bash-tools.shared.test.ts
Normal file
77
src/agents/bash-tools.shared.test.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
import { mkdir, mkdtemp, rm } from "node:fs/promises";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { resolveSandboxWorkdir } from "./bash-tools.shared.js";
|
||||
|
||||
async function withTempDir(run: (dir: string) => Promise<void>) {
|
||||
const dir = await mkdtemp(path.join(os.tmpdir(), "openclaw-bash-workdir-"));
|
||||
try {
|
||||
await run(dir);
|
||||
} finally {
|
||||
await rm(dir, { recursive: true, force: true });
|
||||
}
|
||||
}
|
||||
|
||||
describe("resolveSandboxWorkdir", () => {
|
||||
it("maps container root workdir to host workspace", async () => {
|
||||
await withTempDir(async (workspaceDir) => {
|
||||
const warnings: string[] = [];
|
||||
const resolved = await resolveSandboxWorkdir({
|
||||
workdir: "/workspace",
|
||||
sandbox: {
|
||||
containerName: "sandbox-1",
|
||||
workspaceDir,
|
||||
containerWorkdir: "/workspace",
|
||||
},
|
||||
warnings,
|
||||
});
|
||||
|
||||
expect(resolved.hostWorkdir).toBe(workspaceDir);
|
||||
expect(resolved.containerWorkdir).toBe("/workspace");
|
||||
expect(warnings).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
it("maps nested container workdir under the container workspace", async () => {
|
||||
await withTempDir(async (workspaceDir) => {
|
||||
const nested = path.join(workspaceDir, "scripts", "runner");
|
||||
await mkdir(nested, { recursive: true });
|
||||
const warnings: string[] = [];
|
||||
const resolved = await resolveSandboxWorkdir({
|
||||
workdir: "/workspace/scripts/runner",
|
||||
sandbox: {
|
||||
containerName: "sandbox-2",
|
||||
workspaceDir,
|
||||
containerWorkdir: "/workspace",
|
||||
},
|
||||
warnings,
|
||||
});
|
||||
|
||||
expect(resolved.hostWorkdir).toBe(nested);
|
||||
expect(resolved.containerWorkdir).toBe("/workspace/scripts/runner");
|
||||
expect(warnings).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
it("supports custom container workdir prefixes", async () => {
|
||||
await withTempDir(async (workspaceDir) => {
|
||||
const nested = path.join(workspaceDir, "project");
|
||||
await mkdir(nested, { recursive: true });
|
||||
const warnings: string[] = [];
|
||||
const resolved = await resolveSandboxWorkdir({
|
||||
workdir: "/sandbox-root/project",
|
||||
sandbox: {
|
||||
containerName: "sandbox-3",
|
||||
workspaceDir,
|
||||
containerWorkdir: "/sandbox-root",
|
||||
},
|
||||
warnings,
|
||||
});
|
||||
|
||||
expect(resolved.hostWorkdir).toBe(nested);
|
||||
expect(resolved.containerWorkdir).toBe("/sandbox-root/project");
|
||||
expect(warnings).toEqual([]);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -85,9 +85,14 @@ export async function resolveSandboxWorkdir(params: {
|
||||
warnings: string[];
|
||||
}) {
|
||||
const fallback = params.sandbox.workspaceDir;
|
||||
const mappedHostWorkdir = mapContainerWorkdirToHost({
|
||||
workdir: params.workdir,
|
||||
sandbox: params.sandbox,
|
||||
});
|
||||
const candidateWorkdir = mappedHostWorkdir ?? params.workdir;
|
||||
try {
|
||||
const resolved = await assertSandboxPath({
|
||||
filePath: params.workdir,
|
||||
filePath: candidateWorkdir,
|
||||
cwd: process.cwd(),
|
||||
root: params.sandbox.workspaceDir,
|
||||
});
|
||||
@@ -113,6 +118,36 @@ export async function resolveSandboxWorkdir(params: {
|
||||
}
|
||||
}
|
||||
|
||||
function mapContainerWorkdirToHost(params: {
|
||||
workdir: string;
|
||||
sandbox: BashSandboxConfig;
|
||||
}): string | undefined {
|
||||
const workdir = normalizeContainerPath(params.workdir);
|
||||
const containerRoot = normalizeContainerPath(params.sandbox.containerWorkdir);
|
||||
if (containerRoot === ".") {
|
||||
return undefined;
|
||||
}
|
||||
if (workdir === containerRoot) {
|
||||
return path.resolve(params.sandbox.workspaceDir);
|
||||
}
|
||||
if (!workdir.startsWith(`${containerRoot}/`)) {
|
||||
return undefined;
|
||||
}
|
||||
const rel = workdir
|
||||
.slice(containerRoot.length + 1)
|
||||
.split("/")
|
||||
.filter(Boolean);
|
||||
return path.resolve(params.sandbox.workspaceDir, ...rel);
|
||||
}
|
||||
|
||||
function normalizeContainerPath(input: string): string {
|
||||
const normalized = input.trim().replace(/\\/g, "/");
|
||||
if (!normalized) {
|
||||
return ".";
|
||||
}
|
||||
return path.posix.normalize(normalized);
|
||||
}
|
||||
|
||||
export function resolveWorkdir(workdir: string, warnings: string[]) {
|
||||
const current = safeCwd();
|
||||
const fallback = current ?? homedir();
|
||||
|
||||
Reference in New Issue
Block a user