Skip to content

Commit d9034da

Browse files
committed
fix(openshell): upload staged workspace contents
1 parent 4a503ed commit d9034da

3 files changed

Lines changed: 57 additions & 16 deletions

File tree

extensions/openshell/src/backend.e2e.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -382,7 +382,7 @@ function buildOpenShellPolicyYaml(params: {
382382
383383
filesystem_policy:
384384
include_workdir: true
385-
read_only: [/usr, /lib, /proc, /dev/urandom, /app, /etc, /var/log]
385+
read_only: [/usr, /lib, /proc, /dev/urandom, /app, /etc, /var/log, /opt]
386386
read_write: [/sandbox, /tmp, /dev/null]
387387
388388
landlock:

extensions/openshell/src/backend.ts

Lines changed: 30 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,21 @@ type PendingExec = {
4545
};
4646

4747
const MATERIALIZED_SKILLS_REMOTE_PARTS = [".openclaw", "sandbox-skills"] as const;
48+
export function buildOpenShellDirectoryUploadArgs(params: {
49+
sandboxName: string;
50+
localPath: string;
51+
remotePath: string;
52+
}): string[] {
53+
return [
54+
"sandbox",
55+
"upload",
56+
"--no-git-ignore",
57+
params.sandboxName,
58+
params.localPath,
59+
normalizeRemotePath(params.remotePath),
60+
];
61+
}
62+
4863
export const PINNED_REMOTE_PATH_MUTATION_SCRIPT = [
4964
"set -eu",
5065
'die() { echo "$1" >&2; exit 1; }',
@@ -738,26 +753,26 @@ class OpenShellSandboxBackendImpl {
738753
async ({ dir: tmpDir }) => {
739754
// Stage a symlink-free snapshot so upload never dereferences host paths
740755
// outside the mirrored workspace tree.
741-
const remoteRootName = path.posix.basename(remotePath);
756+
const remoteRootName = path.posix.basename(normalizeRemotePath(remotePath));
742757
const stagedRoot = path.join(tmpDir, remoteRootName);
743758
await stageDirectoryContents({
744759
sourceDir: localPath,
745760
targetDir: stagedRoot,
746761
});
747-
const result = await runOpenShellCli({
748-
context: this.params.execContext,
749-
args: [
750-
"sandbox",
751-
"upload",
752-
"--no-git-ignore",
753-
this.params.execContext.sandboxName,
754-
stagedRoot,
755-
path.posix.dirname(remotePath),
756-
],
757-
cwd: this.params.createParams.workspaceDir,
758-
});
759-
if (result.code !== 0) {
760-
throw new Error(result.stderr.trim() || "openshell sandbox upload failed");
762+
const stagedEntries = (await fs.readdir(stagedRoot)).toSorted();
763+
for (const entry of stagedEntries) {
764+
const result = await runOpenShellCli({
765+
context: this.params.execContext,
766+
args: buildOpenShellDirectoryUploadArgs({
767+
sandboxName: this.params.execContext.sandboxName,
768+
localPath: path.join(stagedRoot, entry),
769+
remotePath,
770+
}),
771+
cwd: this.params.createParams.workspaceDir,
772+
});
773+
if (result.code !== 0) {
774+
throw new Error(result.stderr.trim() || "openshell sandbox upload failed");
775+
}
761776
}
762777
},
763778
);

extensions/openshell/src/openshell-core.test.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ const cliMocks = vi.hoisted(() => ({
2929

3030
let createOpenShellSandboxBackendManager: typeof import("./backend.js").createOpenShellSandboxBackendManager;
3131
let createOpenShellSandboxBackendFactory: typeof import("./backend.js").createOpenShellSandboxBackendFactory;
32+
let buildOpenShellDirectoryUploadArgs: typeof import("./backend.js").buildOpenShellDirectoryUploadArgs;
3233
let ensureOpenShellRemoteRealDirectoryScript: typeof import("./backend.js").ENSURE_OPEN_SHELL_REMOTE_REAL_DIRECTORY_SCRIPT;
3334

3435
describe("openshell cli helpers", () => {
@@ -172,6 +173,7 @@ describe("openshell backend manager", () => {
172173
};
173174
});
174175
({
176+
buildOpenShellDirectoryUploadArgs,
175177
ENSURE_OPEN_SHELL_REMOTE_REAL_DIRECTORY_SCRIPT: ensureOpenShellRemoteRealDirectoryScript,
176178
createOpenShellSandboxBackendFactory,
177179
createOpenShellSandboxBackendManager,
@@ -187,6 +189,30 @@ describe("openshell backend manager", () => {
187189
vi.clearAllMocks();
188190
});
189191

192+
it("uploads staged directory snapshots to the managed remote directory itself", () => {
193+
expect(
194+
buildOpenShellDirectoryUploadArgs({
195+
sandboxName: "openclaw-session",
196+
localPath: "/tmp/openclaw-upload/sandbox/seed.txt",
197+
remotePath: "/sandbox",
198+
}),
199+
).toEqual([
200+
"sandbox",
201+
"upload",
202+
"--no-git-ignore",
203+
"openclaw-session",
204+
"/tmp/openclaw-upload/sandbox/seed.txt",
205+
"/sandbox",
206+
]);
207+
expect(
208+
buildOpenShellDirectoryUploadArgs({
209+
sandboxName: "openclaw-session",
210+
localPath: "/tmp/openclaw-upload/project",
211+
remotePath: "/sandbox/./project",
212+
}).at(-1),
213+
).toBe("/sandbox/project");
214+
});
215+
190216
it.runIf(process.platform !== "win32")(
191217
"preserves caller positional args after OpenShell remote directory validation",
192218
async () => {

0 commit comments

Comments
 (0)