Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
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
3 changes: 0 additions & 3 deletions scripts/checks/legacy-sandbox-transports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,7 @@ const REVIEWED_SITE_TUPLES = [
["src/lib/actions/sandbox/host-aliases.ts", "docker-exec-command", 1],
["src/lib/actions/sandbox/sessions/passthrough.ts", "grpc-cli-read-only-fallback", 1],
["src/lib/actions/sandbox/process-recovery.ts", "privileged-sandbox-exec", 2],
["src/lib/adapters/docker/container.ts", "docker-exec-command", 1],
["src/lib/diagnostics/debug.ts", "grpc-cli-read-only-fallback", 1],
["src/lib/onboard.ts", "docker-exec-builder", 1],
["src/lib/resources-cmd.ts", "docker-exec-command", 1],
["src/lib/sandbox/config.ts", "privileged-sandbox-exec", 2],
["src/lib/sandbox/version.ts", "grpc-cli-read-only-fallback", 1],
Expand All @@ -53,7 +51,6 @@ const REVIEWED_SITE_TUPLES = [
["src/lib/state/openclaw-plugin-restore.ts", "grpc-cli-read-only-fallback", 1],
["src/lib/state/sandbox.ts", "grpc-cli-read-only-fallback", 1],
["src/lib/state/user-managed-files-probe.ts", "grpc-cli-read-only-fallback", 1],
["src/lib/tunnel/sandbox-gateway-stop.ts", "docker-exec-command", 2],
] as const satisfies readonly ReviewedSiteTuple[];

export const REVIEWED_LEGACY_SANDBOX_TRANSPORT_SITES: readonly LegacySandboxTransportSite[] =
Expand Down
6 changes: 3 additions & 3 deletions src/commands/sandbox/stop.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ const rootDir = process.cwd();

describe("SandboxStopCommand", () => {
beforeEach(() => {
stopSandbox.mockClear();
stopSandbox.mockReturnValue({ exitCode: 0 });
stopSandbox.mockReset();
stopSandbox.mockResolvedValue({ exitCode: 0 });
});

afterEach(() => {
Expand All @@ -30,7 +30,7 @@ describe("SandboxStopCommand", () => {

it("propagates the action's failure exit code (#6026)", async () => {
const error = vi.spyOn(console, "error").mockImplementation(() => {});
stopSandbox.mockReturnValue({ exitCode: 1, message: " boom" });
stopSandbox.mockResolvedValue({ exitCode: 1, message: " boom" });

await SandboxStopCommand.run(["alpha"], rootDir);

Expand Down
2 changes: 1 addition & 1 deletion src/commands/sandbox/stop.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,6 @@ export default class SandboxStopCommand extends NemoClawCommand {

public async run(): Promise<void> {
const { args } = await this.parse(SandboxStopCommand);
this.applyExitResult(stopSandbox(args.sandboxName));
this.applyExitResult(await stopSandbox(args.sandboxName));
}
}
2 changes: 1 addition & 1 deletion src/commands/stop.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,6 @@ export default class DeprecatedStopCommand extends NemoClawCommand {

public async run(): Promise<void> {
await this.parse(DeprecatedStopCommand);
runStopCommand({ ...serviceDeps(), stopAll, releaseGatewayPort: true });
await runStopCommand({ ...serviceDeps(), stopAll, releaseGatewayPort: true });
}
}
7 changes: 3 additions & 4 deletions src/commands/tunnel/stop.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,9 @@
// SPDX-License-Identifier: Apache-2.0

import { NemoClawCommand } from "../../lib/cli/nemoclaw-oclif-command";

import { stopAll } from "../../lib/tunnel/services";
import { runStopCommand } from "../../lib/tunnel/service-command";
import { serviceDeps } from "../../lib/tunnel/command-support";
import { runStopCommand } from "../../lib/tunnel/service-command";
import { stopAll } from "../../lib/tunnel/services";

export default class TunnelStopCommand extends NemoClawCommand {
static id = "tunnel:stop";
Expand All @@ -18,6 +17,6 @@ export default class TunnelStopCommand extends NemoClawCommand {

public async run(): Promise<void> {
await this.parse(TunnelStopCommand);
runStopCommand({ ...serviceDeps(), stopAll });
await runStopCommand({ ...serviceDeps(), stopAll });
}
}
2 changes: 1 addition & 1 deletion src/lib/actions/sandbox/destroy-flow.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ describe("destroySandbox flow", () => {
});

it("selects the sandbox gateway, deletes live resources, cleans host state, and removes registry state", async () => {
const harness = createDestroyHarness();
const harness = createDestroyHarness({ registeredSandboxCount: 1 });

await expect(
harness.destroySandbox("alpha", { yes: true, cleanupGateway: true }),
Expand Down
18 changes: 9 additions & 9 deletions src/lib/actions/sandbox/destroy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ import { resolveNemoclawStateDir } from "../../state/paths";
import * as registry from "../../state/registry";
import { confirmSandboxDestroy } from "./destroy-confirmation";
import { executeSandboxDestroy } from "./destroy-execution";
import { shouldCleanupGatewayAfterConfirmedFinalDestroy } from "./destroy-gateway-cleanup";
import { cleanupGatewayAfterLastSandbox } from "./destroy-gateway";
import { shouldCleanupGatewayAfterConfirmedFinalDestroy } from "./destroy-gateway-cleanup";
import { prepareSandboxDestroy } from "./destroy-preflight";
import { type WipeSandboxStateDeps, wipeSandboxState } from "./wipe-state";

Expand All @@ -57,7 +57,7 @@ type RunOpenshell = (args: string[], opts?: Record<string, unknown>) => { status

export type CleanupSandboxServicesDeps = {
getSandbox?: typeof registry.getSandbox;
stopAll?: (opts: { sandboxName: string }) => void;
stopAll?: (opts: { sandboxName: string; skipSandboxStop: true }) => Promise<void>;
unloadOllamaModels?: () => void;
runOpenshell?: RunOpenshell;
rmSync?: typeof fs.rmSync;
Expand Down Expand Up @@ -99,11 +99,11 @@ async function resolveCleanupGatewayDecision(options: DestroySandboxOptions): Pr
return trimmed === "y" || trimmed === "yes";
}

export function cleanupSandboxServices(
export async function cleanupSandboxServices(
sandboxName: string,
{ stopHostServices = false }: { stopHostServices?: boolean } = {},
deps: CleanupSandboxServicesDeps = {},
): void {
): Promise<void> {
// Source boundary: this exported helper can be called independently of CLI
// dispatch, including from forced local recovery. Validate once before every
// host and provider cleanup side effect, then derive the PID path from that
Expand All @@ -114,11 +114,11 @@ export function cleanupSandboxServices(
const getSandbox = deps.getSandbox ?? registry.getSandbox;
const stopAll =
deps.stopAll ??
((opts: { sandboxName: string }) => {
((opts: { sandboxName: string; skipSandboxStop: true }) => {
const services = require("../../tunnel/services") as {
stopAll: (opts: { sandboxName: string }) => void;
stopAll: (opts: { sandboxName: string; skipSandboxStop: true }) => Promise<void>;
};
services.stopAll(opts);
return services.stopAll(opts);
});
const unloadOllamaModels =
deps.unloadOllamaModels ??
Expand All @@ -141,7 +141,7 @@ export function cleanupSandboxServices(
if (stopHostServices) {
// `stopAll()` already runs `unloadOllamaModels()` unconditionally —
// see src/lib/tunnel/services.ts. Don't double-call here.
stopAll({ sandboxName: validatedSandboxName });
await stopAll({ sandboxName: validatedSandboxName, skipSandboxStop: true });
} else {
// No global stop, so `stopAll()` did not run; explicitly free Ollama
// models for this sandbox if its provider used Ollama. Without this
Expand Down Expand Up @@ -380,7 +380,7 @@ async function destroySandboxUnlocked(
sandboxStillRegistered: !!registry.getSandbox(sandboxName),
});

cleanupSandboxServices(sandboxName, {
await cleanupSandboxServices(sandboxName, {
stopHostServices: shouldStopHostServices,
});
// The sandbox's gateway was captured before the registry entry is removed —
Expand Down
Loading
Loading