From bd87249561d0c421aa2953c9122c1c5af714362c Mon Sep 17 00:00:00 2001 From: jialiuyang <495120021@qq.com> Date: Sun, 19 Jul 2026 16:23:27 +0800 Subject: [PATCH] fix(sandbox-agentrun): keep MCP open when sandbox is not owned AgentRunSandbox.shutdown() closed the MCP channel before checking isSandboxOwned(). For shared/non-owned sandboxes that left the cloud container alive, background SessionTree mirrors then failed with MCP session terminated. Skip close/delete when not owned, matching E2b and Daytona adapters. Fixes #2259 --- .../sandbox/agentrun/AgentRunSandbox.java | 8 ++- .../agentrun/AgentRunSandboxShutdownTest.java | 71 +++++++++++++++++++ 2 files changed, 76 insertions(+), 3 deletions(-) create mode 100644 agentscope-extensions/agentscope-extensions-sandbox/agentscope-extensions-sandbox-agentrun/src/test/java/io/agentscope/extensions/sandbox/agentrun/AgentRunSandboxShutdownTest.java diff --git a/agentscope-extensions/agentscope-extensions-sandbox/agentscope-extensions-sandbox-agentrun/src/main/java/io/agentscope/extensions/sandbox/agentrun/AgentRunSandbox.java b/agentscope-extensions/agentscope-extensions-sandbox/agentscope-extensions-sandbox-agentrun/src/main/java/io/agentscope/extensions/sandbox/agentrun/AgentRunSandbox.java index 8000113aa0..d19c24b9b6 100644 --- a/agentscope-extensions/agentscope-extensions-sandbox/agentscope-extensions-sandbox-agentrun/src/main/java/io/agentscope/extensions/sandbox/agentrun/AgentRunSandbox.java +++ b/agentscope-extensions/agentscope-extensions-sandbox/agentscope-extensions-sandbox-agentrun/src/main/java/io/agentscope/extensions/sandbox/agentrun/AgentRunSandbox.java @@ -88,14 +88,16 @@ public void stop() throws Exception { @Override public void shutdown() throws Exception { + // Match E2b/Daytona: a non-owned sandbox is shared/reused — keep the MCP channel open so + // background SessionTree mirrors can still uploadFiles() after agent teardown (#2259). + if (!arState.isSandboxOwned()) { + return; + } try { mcp.close(); } catch (Exception ignore) { // best-effort } - if (!arState.isSandboxOwned()) { - return; - } String id = arState.getSandboxId(); if (id != null && !id.isBlank()) { http.deleteSandbox(id); diff --git a/agentscope-extensions/agentscope-extensions-sandbox/agentscope-extensions-sandbox-agentrun/src/test/java/io/agentscope/extensions/sandbox/agentrun/AgentRunSandboxShutdownTest.java b/agentscope-extensions/agentscope-extensions-sandbox/agentscope-extensions-sandbox-agentrun/src/test/java/io/agentscope/extensions/sandbox/agentrun/AgentRunSandboxShutdownTest.java new file mode 100644 index 0000000000..c7292619f3 --- /dev/null +++ b/agentscope-extensions/agentscope-extensions-sandbox/agentscope-extensions-sandbox-agentrun/src/test/java/io/agentscope/extensions/sandbox/agentrun/AgentRunSandboxShutdownTest.java @@ -0,0 +1,71 @@ +/* + * 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.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import io.agentscope.harness.agent.sandbox.WorkspaceSpec; +import org.junit.jupiter.api.Test; + +class AgentRunSandboxShutdownTest { + + private static AgentRunSandboxState state(String id, boolean owned) { + AgentRunSandboxState state = new AgentRunSandboxState(); + state.setSandboxId(id); + state.setSandboxOwned(owned); + state.setWorkspaceRoot(AgentRunSandboxState.DEFAULT_WORKSPACE_ROOT); + WorkspaceSpec ws = new WorkspaceSpec(); + ws.setRoot(AgentRunSandboxState.DEFAULT_WORKSPACE_ROOT); + state.setWorkspaceSpec(ws); + return state; + } + + private static AgentRunSandboxClientOptions options() { + AgentRunSandboxClientOptions opt = new AgentRunSandboxClientOptions(); + opt.setApiKey("test-key"); + opt.setTemplateName("agentscope-default"); + return opt; + } + + @Test + void shutdown_whenNotOwned_keepsMcpOpenAndSkipsDelete() throws Exception { + AgentRunMcpChannel mcp = mock(AgentRunMcpChannel.class); + AgentRunDataPlaneHttp http = mock(AgentRunDataPlaneHttp.class); + + AgentRunSandbox sandbox = + new AgentRunSandbox(state("sbx-shared", false), options(), http, mcp); + sandbox.shutdown(); + + verify(mcp, never()).close(); + verify(http, never()).deleteSandbox("sbx-shared"); + } + + @Test + void shutdown_whenOwned_closesMcpAndDeletesSandbox() throws Exception { + AgentRunMcpChannel mcp = mock(AgentRunMcpChannel.class); + AgentRunDataPlaneHttp http = mock(AgentRunDataPlaneHttp.class); + + AgentRunSandbox sandbox = + new AgentRunSandbox(state("sbx-owned", true), options(), http, mcp); + sandbox.shutdown(); + + verify(mcp, times(1)).close(); + verify(http, times(1)).deleteSandbox("sbx-owned"); + } +}