fix(agents): map sandbox workdir from container path
This commit is contained in:
committed by
Peter Steinberger
parent
b1cc8ffe9e
commit
c48a0621ff
@@ -99,6 +99,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Feishu/Duplicate replies: suppress same-target reply dispatch when message-tool sends use generic provider metadata (`provider: "message"`) and normalize `lark`/`feishu` provider aliases during duplicate-target checks, preventing double-delivery in Feishu sessions. (#31526)
|
||||
- Feishu/Plugin sdk compatibility: add safe webhook default fallbacks when loading Feishu monitor state so mixed-version installs no longer crash if older `openclaw/plugin-sdk` builds omit webhook default constants. (#31606)
|
||||
- Pairing/AllowFrom account fallback: handle omitted `accountId` values in `readChannelAllowFromStore` and `readChannelAllowFromStoreSync` as `default`, while preserving legacy unscoped allowFrom merges for default-account flows. Thanks @Sid-Qin and @vincentkoc.
|
||||
- Agents/Sandbox workdir mapping: map container workdir paths (for example `/workspace`) back to the host workspace before sandbox path validation so exec requests keep the intended directory in containerized runs instead of falling back to an unavailable host path. (Related #30711)
|
||||
- Agents/Subagent announce cleanup: keep completion-message runs pending while descendants settle, add a 30 minute hard-expiry backstop to avoid indefinite pending state, and keep retry bookkeeping resumable across deferred wakes. (#23970) Thanks @tyler6204.
|
||||
- BlueBubbles/Message metadata: harden send response ID extraction, include sender identity in DM context, and normalize inbound `message_id` selection to avoid duplicate ID metadata. (#23970) Thanks @tyler6204.
|
||||
- Gateway/Control UI method guard: allow POST requests to non-UI routes to fall through when no base path is configured, and add POST regression coverage for fallthrough and base-path 405 behavior. (#23970) Thanks @tyler6204.
|
||||
|
||||
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