Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
4 changes: 3 additions & 1 deletion src/lib/actions/sandbox/snapshot-restore-clone-ports.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ vi.mock("../../onboard/dashboard-port", () => ({
beforeEach(f.resetSnapshotRestoreMocks);
afterEach(f.cleanupSnapshotRestoreMocks);
describe("runSandboxSnapshot restore: clone dashboard port identity", () => {
it("allocates the auto-created clone its own dashboard port instead of inheriting the source's (#6746)", async () => {
it("registers a legacy source clone with complete dashboard and gateway ports (#7227)", async () => {
let registeredClone: f.SandboxRecord | null = null;
f.registerSandboxMock.mockImplementation(
(entry) => (registeredClone = entry as f.SandboxRecord),
Expand Down Expand Up @@ -75,6 +75,8 @@ describe("runSandboxSnapshot restore: clone dashboard port identity", () => {
expect.objectContaining({
name: "beta",
dashboardPort: 18901,
gatewayName: "nemoclaw",
gatewayPort: 8080,
}),
);
});
Expand Down
1 change: 1 addition & 0 deletions src/lib/actions/sandbox/snapshot-restore-test-fixture.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ export type SandboxRecord = {
}>;
fromDockerfile?: string | null;
gatewayName?: string | null;
gatewayPort?: number | null;
imageTag?: string | null;
workload?: SandboxWorkloadReceipt;
openshellDriver?: string | null;
Expand Down
17 changes: 16 additions & 1 deletion src/lib/actions/sandbox/snapshot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,10 @@ import {
withDashboardPortReservationLock,
} from "../../onboard/dashboard-port";
import { isValidForwardPort } from "../../onboard/dashboard-runtime";
import { resolveSandboxGatewayName } from "../../onboard/gateway-binding";
import {
resolveGatewayPortFromName,
resolveSandboxGatewayName,
} from "../../onboard/gateway-binding";
import { resolveHermesDashboardOnboardState } from "../../onboard/hermes-dashboard";
import {
isDcodeAgent,
Expand Down Expand Up @@ -366,6 +369,13 @@ async function autoCreateSandboxFromSource(
const openshellBin = getOpenshellBinary();
const sourceObservabilityEnabled =
(srcEntry as { observabilityEnabled?: boolean }).observabilityEnabled === true;
const sourceGatewayName = resolveSandboxGatewayName(
srcEntry as { gatewayName?: string | null; gatewayPort?: number | null },
);
const sourceGatewayPort = resolveGatewayPortFromName(sourceGatewayName);
if (sourceGatewayPort === null) {
throw new Error(`Cannot resolve gateway port for snapshot source '${srcName}'.`);
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
const startupCommand = [
"env",
`NEMOCLAW_OBSERVABILITY=${sourceObservabilityEnabled ? "1" : "0"}`,
Expand Down Expand Up @@ -453,6 +463,11 @@ async function autoCreateSandboxFromSource(
(srcEntry as SandboxEntry).hermesDashboardEnabled === true
? dstDashboardPort
: (srcEntry as SandboxEntry).hermesDashboardPort,
// A legacy source may have only a gateway name (or neither binding
// field). Register the new clone with the complete canonical binding so
// stop/start, recovery, and later snapshots can address its gateway.
gatewayName: sourceGatewayName,
gatewayPort: sourceGatewayPort,
});

const sourceAgent = (srcEntry as SandboxEntry).agent || "openclaw";
Expand Down
Loading