Skip to content

Commit 69d60b0

Browse files
test(sandbox): allow remote writes under absent skill roots
1 parent a1e75ce commit 69d60b0

1 file changed

Lines changed: 60 additions & 0 deletions

File tree

src/agents/sandbox/workspace-skills-bridge-readonly.test.ts

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
1+
import { spawnSync } from "node:child_process";
12
import fs from "node:fs/promises";
23
import path from "node:path";
34
import { describe, expect, it } from "vitest";
45
import { buildSandboxFsMounts, resolveSandboxFsPathWithMounts } from "./fs-paths.js";
6+
import { SANDBOX_PINNED_MUTATION_PYTHON } from "./fs-bridge-mutation-helper.js";
57
import { createSandbox, withTempDir } from "./fs-bridge.test-helpers.js";
8+
import { createRemoteShellSandboxFsBridge } from "./remote-fs-bridge.js";
69

710
describe("workspace skills bridge mount policy", () => {
811
it("resolves workspace skill roots as read-only", async () => {
@@ -28,4 +31,61 @@ describe("workspace skills bridge mount policy", () => {
2831
expect(resolve("/workspace/skills/demo/SKILL.md").writable).toBe(false);
2932
});
3033
});
34+
35+
it.runIf(process.platform !== "win32")(
36+
"allows remote bridge writes under absent skill roots",
37+
async () => {
38+
await withTempDir("openclaw-skills-remote-absent-", async (stateDir) => {
39+
const workspaceDir = path.join(stateDir, "workspace");
40+
await fs.mkdir(workspaceDir, { recursive: true });
41+
42+
const bridge = createRemoteShellSandboxFsBridge({
43+
sandbox: createSandbox({ workspaceDir, agentWorkspaceDir: workspaceDir }),
44+
runtime: {
45+
remoteWorkspaceDir: workspaceDir,
46+
remoteAgentWorkspaceDir: workspaceDir,
47+
runRemoteShellScript: async (command) => {
48+
const result = command.script.includes('python3 /dev/fd/3 "$@" 3<<')
49+
? spawnSync(
50+
"python3",
51+
["-c", SANDBOX_PINNED_MUTATION_PYTHON, ...(command.args ?? [])],
52+
{
53+
input: command.stdin,
54+
encoding: "buffer",
55+
stdio: ["pipe", "pipe", "pipe"],
56+
},
57+
)
58+
: spawnSync("sh", ["-c", command.script, "openclaw-test", ...(command.args ?? [])], {
59+
input: command.stdin,
60+
encoding: "buffer",
61+
stdio: ["pipe", "pipe", "pipe"],
62+
});
63+
const stdout = Buffer.isBuffer(result.stdout)
64+
? result.stdout
65+
: Buffer.from(result.stdout ?? []);
66+
const stderr = Buffer.isBuffer(result.stderr)
67+
? result.stderr
68+
: Buffer.from(result.stderr ?? []);
69+
const code = result.status ?? (result.signal ? 128 : 1);
70+
if (result.error) {
71+
throw result.error;
72+
}
73+
if (code !== 0 && !command.allowFailure) {
74+
throw Object.assign(
75+
new Error(stderr.toString("utf8").trim() || `shell exited with code ${code}`),
76+
{ code, stdout, stderr },
77+
);
78+
}
79+
return { stdout, stderr, code };
80+
},
81+
},
82+
});
83+
84+
await bridge.writeFile({ filePath: "skills/new.txt", data: "created" });
85+
await expect(fs.readFile(path.join(workspaceDir, "skills", "new.txt"), "utf8")).resolves.toBe(
86+
"created",
87+
);
88+
});
89+
},
90+
);
3191
});

0 commit comments

Comments
 (0)