Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public class AgentRunFilesystemSpec extends SandboxFilesystemSpec {
private SandboxClient<?> client;
private final AgentRunSandboxClientOptions options = new AgentRunSandboxClientOptions();
private SandboxSnapshotSpec snapshotSpec = new NoopSnapshotSpec();
private WorkspaceSpec defaultWorkspaceSpec = new WorkspaceSpec();
private WorkspaceSpec defaultWorkspaceSpec = createDefaultWorkspaceSpec();

public AgentRunFilesystemSpec client(SandboxClient<?> client) {
this.client = client;
Expand Down Expand Up @@ -86,7 +86,7 @@ public AgentRunFilesystemSpec addOssMount(AgentRunOssMountConfig mount) {
}

public AgentRunFilesystemSpec workspaceRoot(String workspaceRoot) {
options.setWorkspaceRoot(workspaceRoot);
defaultWorkspaceSpec.setRoot(workspaceRoot);
return this;
}

Expand Down Expand Up @@ -115,6 +115,12 @@ public AgentRunFilesystemSpec workspaceSpec(WorkspaceSpec workspaceSpec) {
return this;
}

private static WorkspaceSpec createDefaultWorkspaceSpec() {
WorkspaceSpec spec = new WorkspaceSpec();
spec.setRoot(AgentRunSandboxState.DEFAULT_WORKSPACE_ROOT);
return spec;
}

@Override
protected SandboxClient<?> createClient() {
return client != null ? client : options.createClient();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ protected InputStream doPersistWorkspace() throws Exception {
// Persistence is handled by the NAS/OSS mount — nothing to archive.
return InputStream.nullInputStream();
}
String root = arState.getWorkspaceRoot();
String root = arState.getWorkspaceSpec().getRoot();
String cmd = "tar -cf - -C " + shellSingleQuote(root) + " . | base64 -w0";
AgentRunMcpChannel.ExecResult r = mcp.exec(cmd, null, TAR_TIMEOUT_SECONDS);
if (r.exitCode != 0) {
Expand All @@ -150,7 +150,7 @@ protected InputStream doPersistWorkspace() throws Exception {

@Override
protected void doHydrateWorkspace(InputStream archive) throws Exception {
String root = arState.getWorkspaceRoot();
String root = arState.getWorkspaceSpec().getRoot();
byte[] all = archive.readAllBytes();
if (all.length == 0) {
return;
Expand Down Expand Up @@ -187,7 +187,7 @@ protected void doHydrateWorkspace(InputStream archive) throws Exception {

@Override
protected void doSetupWorkspace() throws Exception {
mcp.exec("mkdir -p " + shellSingleQuote(arState.getWorkspaceRoot()), null, 30);
mcp.exec("mkdir -p " + shellSingleQuote(arState.getWorkspaceSpec().getRoot()), null, 30);
}

@Override
Expand All @@ -197,15 +197,15 @@ protected void doDestroyWorkspace() throws Exception {
return;
}
try {
mcp.exec("rm -rf " + shellSingleQuote(arState.getWorkspaceRoot()), null, 30);
mcp.exec("rm -rf " + shellSingleQuote(arState.getWorkspaceSpec().getRoot()), null, 30);
} catch (Exception e) {
// best-effort
}
}

@Override
protected String getWorkspaceRoot() {
return arState.getWorkspaceRoot();
public String getWorkspaceRoot() {
return arState.getWorkspaceSpec().getRoot();
}

private void ensureSandbox() throws Exception {
Expand Down Expand Up @@ -236,7 +236,7 @@ private static boolean isNotFound(Exception e) {
}

private String relativeOrAbsoluteCwd() {
String root = arState.getWorkspaceRoot();
String root = arState.getWorkspaceSpec().getRoot();
return root != null && !root.isBlank() ? root : null;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,15 +74,16 @@ public Sandbox create(
AgentRunSandboxState state = new AgentRunSandboxState();
state.setSessionId(sessionId);
state.setWorkspaceSpec(workspaceSpec);
state.setWorkspaceRoot(merged.getWorkspaceRoot());
state.setTemplateName(merged.getTemplateName());
state.setAccountId(merged.getAccountId());
state.setRegion(merged.getRegion());
state.setMcpServerUrl(merged.getMcpServerUrl());
state.setSandboxId(sandboxId);
state.setSandboxOwned(true);
state.setWorkspaceRootReady(false);
state.setWorkspaceOnNas(isWorkspaceUnderMounts(merged));
state.setWorkspaceOnNas(
isWorkspaceUnderMounts(
merged, workspaceSpec != null ? workspaceSpec.getRoot() : null));

if (snapshotSpec != null) {
state.setSnapshot(snapshotSpec.build(sessionId));
Expand Down Expand Up @@ -143,8 +144,7 @@ private Sandbox build(AgentRunSandboxState state, AgentRunSandboxClientOptions m
return new AgentRunSandbox(state, merged, http, mcp);
}

private static boolean isWorkspaceUnderMounts(AgentRunSandboxClientOptions opt) {
String root = opt.getWorkspaceRoot();
private static boolean isWorkspaceUnderMounts(AgentRunSandboxClientOptions opt, String root) {
if (root == null || root.isBlank()) {
return false;
}
Expand Down Expand Up @@ -197,9 +197,6 @@ private AgentRunSandboxClientOptions merge(AgentRunSandboxClientOptions call) {
if (call.getOssMountConfigs() != null && !call.getOssMountConfigs().isEmpty()) {
o.setOssMountConfigs(call.getOssMountConfigs());
}
if (call.getWorkspaceRoot() != null) {
o.setWorkspaceRoot(call.getWorkspaceRoot());
}
if (call.getHttpClient() != null) {
o.setHttpClient(call.getHttpClient());
}
Expand All @@ -222,7 +219,6 @@ private static AgentRunSandboxClientOptions copy(AgentRunSandboxClientOptions sr
o.setSandboxIdleTimeoutSeconds(src.getSandboxIdleTimeoutSeconds());
o.setNasConfig(src.getNasConfig());
o.setOssMountConfigs(src.getOssMountConfigs());
o.setWorkspaceRoot(src.getWorkspaceRoot());
o.setHttpClient(src.getHttpClient());
o.setConnectTimeoutSeconds(src.getConnectTimeoutSeconds());
o.setReadTimeoutSeconds(src.getReadTimeoutSeconds());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ public class AgentRunSandboxClientOptions extends SandboxClientOptions {
private int sandboxIdleTimeoutSeconds = 1800;
private AgentRunNasMountConfig nasConfig;
private List<AgentRunOssMountConfig> ossMountConfigs = new ArrayList<>();
private String workspaceRoot = AgentRunSandboxState.DEFAULT_WORKSPACE_ROOT;
private int connectTimeoutSeconds = 30;
private int readTimeoutSeconds = 120;
private int maxRetries = 3;
Expand Down Expand Up @@ -238,16 +237,6 @@ public AgentRunSandboxClientOptions addOssMount(AgentRunOssMountConfig mount) {
return this;
}

public String getWorkspaceRoot() {
return workspaceRoot;
}

public AgentRunSandboxClientOptions setWorkspaceRoot(String workspaceRoot) {
this.workspaceRoot =
workspaceRoot != null ? workspaceRoot : AgentRunSandboxState.DEFAULT_WORKSPACE_ROOT;
return this;
}

public int getConnectTimeoutSeconds() {
return connectTimeoutSeconds;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ public class AgentRunSandboxState extends SandboxState {
public static final String DEFAULT_WORKSPACE_ROOT = "/home/agentscope/workspace";

private String sandboxId;
private String workspaceRoot = DEFAULT_WORKSPACE_ROOT;
private String templateName;
private String accountId;
private String region;
Expand All @@ -40,14 +39,6 @@ public void setSandboxId(String sandboxId) {
this.sandboxId = sandboxId;
}

public String getWorkspaceRoot() {
return workspaceRoot;
}

public void setWorkspaceRoot(String workspaceRoot) {
this.workspaceRoot = workspaceRoot != null ? workspaceRoot : DEFAULT_WORKSPACE_ROOT;
}

public String getTemplateName() {
return templateName;
}
Expand Down Expand Up @@ -89,7 +80,7 @@ public void setSandboxOwned(boolean sandboxOwned) {
}

/**
* Returns whether {@link #workspaceRoot} lives on a NAS or OSS persistent mount.
* Returns whether the workspace root configured via {@link io.agentscope.harness.agent.sandbox.WorkspaceSpec#getRoot()} lives on a NAS or OSS persistent mount.
*
* <p>When {@code true}, the workspace survives sandbox deletion and the adapter skips tar-based
* persistence; "resume" is achieved by recreating the sandbox with the same deterministic id
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* Copyright 2024-2026 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.agentscope.extensions.sandbox.agentrun;

import static org.junit.jupiter.api.Assertions.assertEquals;

import io.agentscope.harness.agent.sandbox.WorkspaceSpec;
import org.junit.jupiter.api.Test;

class AgentRunFilesystemSpecTest {

@Test
void defaultWorkspaceSpec_hasAgentRunRoot() {
AgentRunFilesystemSpec spec = new AgentRunFilesystemSpec();
WorkspaceSpec ws = spec.workspaceSpec();
assertEquals(AgentRunSandboxState.DEFAULT_WORKSPACE_ROOT, ws.getRoot());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ void roundTripPreservesAllFields() {
AgentRunSandboxState state = new AgentRunSandboxState();
state.setSessionId("session-42");
state.setSandboxId("01KE8DAJ35JC8SKP9CNFRZ8CW7");
state.setWorkspaceRoot("/mnt/nas/workspace");
state.setTemplateName("agentscope-default");
state.setAccountId("123456789012");
state.setRegion("cn-hangzhou");
Expand All @@ -50,7 +49,6 @@ void roundTripPreservesAllFields() {
AgentRunSandboxState r = (AgentRunSandboxState) read;
Assertions.assertEquals("session-42", r.getSessionId());
Assertions.assertEquals("01KE8DAJ35JC8SKP9CNFRZ8CW7", r.getSandboxId());
Assertions.assertEquals("/mnt/nas/workspace", r.getWorkspaceRoot());
Assertions.assertEquals("agentscope-default", r.getTemplateName());
Assertions.assertEquals("123456789012", r.getAccountId());
Assertions.assertEquals("cn-hangzhou", r.getRegion());
Expand Down Expand Up @@ -78,7 +76,6 @@ void roundTripWithDefaultsKeepsTypeId() {
SandboxState read = client.deserializeState(json);
Assertions.assertInstanceOf(AgentRunSandboxState.class, read);
AgentRunSandboxState r = (AgentRunSandboxState) read;
Assertions.assertEquals(AgentRunSandboxState.DEFAULT_WORKSPACE_ROOT, r.getWorkspaceRoot());
Assertions.assertFalse(r.isWorkspaceOnNas());
Assertions.assertTrue(r.isSandboxOwned());
Assertions.assertFalse(r.isWorkspaceRootReady());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public class DaytonaFilesystemSpec extends SandboxFilesystemSpec {
private SandboxClient<?> client;
private final DaytonaSandboxClientOptions options = new DaytonaSandboxClientOptions();
private SandboxSnapshotSpec snapshotSpec = new NoopSnapshotSpec();
private WorkspaceSpec defaultWorkspaceSpec = new WorkspaceSpec();
private WorkspaceSpec defaultWorkspaceSpec = createDefaultWorkspaceSpec();

public DaytonaFilesystemSpec client(SandboxClient<?> client) {
this.client = client;
Expand Down Expand Up @@ -76,7 +76,7 @@ public DaytonaFilesystemSpec disk(int diskGiB) {
}

public DaytonaFilesystemSpec workspaceRoot(String workspaceRoot) {
options.setWorkspaceRoot(workspaceRoot);
defaultWorkspaceSpec.setRoot(workspaceRoot);
return this;
}

Expand All @@ -90,6 +90,12 @@ public DaytonaFilesystemSpec workspaceSpec(WorkspaceSpec workspaceSpec) {
return this;
}

private static WorkspaceSpec createDefaultWorkspaceSpec() {
WorkspaceSpec spec = new WorkspaceSpec();
spec.setRoot(DaytonaSandboxState.DEFAULT_WORKSPACE_ROOT);
return spec;
}

@Override
protected SandboxClient<?> createClient() {
return client != null ? client : options.createClient();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ protected ExecResult doExec(RuntimeContext runtimeContext, String command, int t
http.execute(
daytonaState.getSandboxId(),
command,
relativeCwd(daytonaState.getWorkspaceRoot()),
relativeCwd(daytonaState.getWorkspaceSpec().getRoot()),
timeoutSeconds);
int exit = j.path("exitCode").asInt(-1);
String out = j.path("result").asText("");
Expand All @@ -93,7 +93,7 @@ protected ExecResult doExec(RuntimeContext runtimeContext, String command, int t

@Override
protected InputStream doPersistWorkspace() throws Exception {
String root = daytonaState.getWorkspaceRoot();
String root = daytonaState.getWorkspaceSpec().getRoot();
String cmd = "tar -cf - -C " + shellSingleQuote(root) + " . | base64 -w0";
JsonNode j =
http.execute(
Expand All @@ -111,7 +111,7 @@ protected InputStream doPersistWorkspace() throws Exception {

@Override
protected void doHydrateWorkspace(InputStream archive) throws Exception {
String root = daytonaState.getWorkspaceRoot();
String root = daytonaState.getWorkspaceSpec().getRoot();
String rel = relativeCwd(root);
byte[] all = archive.readAllBytes();
String b64 = Base64.getEncoder().encodeToString(all);
Expand Down Expand Up @@ -156,21 +156,21 @@ protected void doHydrateWorkspace(InputStream archive) throws Exception {

@Override
protected void doSetupWorkspace() throws Exception {
exec(null, "mkdir -p " + shellSingleQuote(daytonaState.getWorkspaceRoot()), 30);
exec(null, "mkdir -p " + shellSingleQuote(daytonaState.getWorkspaceSpec().getRoot()), 30);
}

@Override
protected void doDestroyWorkspace() throws Exception {
try {
exec(null, "rm -rf " + shellSingleQuote(daytonaState.getWorkspaceRoot()), 30);
exec(null, "rm -rf " + shellSingleQuote(daytonaState.getWorkspaceSpec().getRoot()), 30);
} catch (Exception e) {
// best-effort
}
}

@Override
protected String getWorkspaceRoot() {
return daytonaState.getWorkspaceRoot();
public String getWorkspaceRoot() {
return daytonaState.getWorkspaceSpec().getRoot();
}

private void ensureSandbox() throws Exception {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,6 @@ public Sandbox create(
DaytonaSandboxState state = new DaytonaSandboxState();
state.setSessionId(sessionId);
state.setWorkspaceSpec(workspaceSpec);
state.setWorkspaceRoot(merged.getWorkspaceRoot());
state.setImage(merged.getImage());
state.setSnapshotId(merged.getSnapshotId());
state.setSandboxOwned(true);
Expand Down Expand Up @@ -138,9 +137,6 @@ private DaytonaSandboxClientOptions merge(DaytonaSandboxClientOptions call) {
if (call.getDisk() != null) {
o.setDisk(call.getDisk());
}
if (call.getWorkspaceRoot() != null) {
o.setWorkspaceRoot(call.getWorkspaceRoot());
}
if (call.getHttpClient() != null) {
o.setHttpClient(call.getHttpClient());
}
Expand All @@ -160,7 +156,6 @@ private static DaytonaSandboxClientOptions copy(DaytonaSandboxClientOptions src)
o.setCpu(src.getCpu());
o.setMemory(src.getMemory());
o.setDisk(src.getDisk());
o.setWorkspaceRoot(src.getWorkspaceRoot());
o.setHttpClient(src.getHttpClient());
o.setConnectTimeoutSeconds(src.getConnectTimeoutSeconds());
o.setReadTimeoutSeconds(src.getReadTimeoutSeconds());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ public class DaytonaSandboxClientOptions extends SandboxClientOptions {
private Integer cpu = 1;
private Integer memory = 1;
private Integer disk = 3;
private String workspaceRoot = DaytonaSandboxState.DEFAULT_WORKSPACE_ROOT;
private int connectTimeoutSeconds = 30;
private int readTimeoutSeconds = 120;
private int maxRetries = 3;
Expand Down Expand Up @@ -118,15 +117,6 @@ public void setDisk(Integer disk) {
this.disk = disk;
}

public String getWorkspaceRoot() {
return workspaceRoot;
}

public void setWorkspaceRoot(String workspaceRoot) {
this.workspaceRoot =
workspaceRoot != null ? workspaceRoot : DaytonaSandboxState.DEFAULT_WORKSPACE_ROOT;
}

public int getConnectTimeoutSeconds() {
return connectTimeoutSeconds;
}
Expand Down
Loading
Loading