























@@ -1,8 +1,11 @@
1+import { spawnSync } from "node:child_process";
12import fs from "node:fs/promises";
23import path from "node:path";
34import { describe, expect, it } from "vitest";
45import { buildSandboxFsMounts, resolveSandboxFsPathWithMounts } from "./fs-paths.js";
6+import { SANDBOX_PINNED_MUTATION_PYTHON } from "./fs-bridge-mutation-helper.js";
57import { createSandbox, withTempDir } from "./fs-bridge.test-helpers.js";
8+import { createRemoteShellSandboxFsBridge } from "./remote-fs-bridge.js";
69710describe("workspace skills bridge mount policy", () => {
811it("resolves workspace skill roots as read-only", async () => {
@@ -28,4 +31,61 @@ describe("workspace skills bridge mount policy", () => {
2831expect(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});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。