diff --git a/src/lib/ports.ts b/src/lib/ports.ts index 15c741c455..04ac579316 100644 --- a/src/lib/ports.ts +++ b/src/lib/ports.ts @@ -37,7 +37,7 @@ export const GATEWAY_PORT = parsePort("NEMOCLAW_GATEWAY_PORT", 8080); * configured via NEMOCLAW_DASHBOARD_PORT at onboard time. This constant represents * the hardcoded default when no override is set. */ -const SANDBOX_DASHBOARD_PORT = 18789; +export const SANDBOX_DASHBOARD_PORT = 18789; /** Dashboard UI port (default SANDBOX_DASHBOARD_PORT, override via NEMOCLAW_DASHBOARD_PORT). This is the host-side port. */ export const DASHBOARD_PORT = parsePort("NEMOCLAW_DASHBOARD_PORT", SANDBOX_DASHBOARD_PORT); /** Start of the auto-allocation range for dashboard ports (inclusive). */ diff --git a/test/onboard.test.ts b/test/onboard.test.ts index c4b82e4838..31f625a254 100644 --- a/test/onboard.test.ts +++ b/test/onboard.test.ts @@ -1368,6 +1368,30 @@ const { loadAgent } = require(${agentDefsPath}); ); }); + it("regression #2007: cleans up stale sandbox-owned dashboard forwards on other ports", () => { + const source = fs.readFileSync( + path.join(import.meta.dirname, "..", "src", "lib", "onboard.ts"), + "utf-8", + ); + + // ensureDashboardForward must iterate occupied forwards and stop any + // owned by this sandbox on ports other than the one we're about to bind. + // This subsumes the original default-18789 cleanup from #2073. + assert.match( + source, + /const occupied = getOccupiedPorts\(existingForwards\);[\s\S]*for \(const \[port, owner\] of occupied\.entries\(\)\)[\s\S]*owner === sandboxName[\s\S]*Number\(port\) !== actualPort[\s\S]*runOpenshell\(\["forward", "stop", port\]/, + ); + // Guard against regressions to the hardcoded literal. + assert.doesNotMatch(source, /const DEFAULT_SANDBOX_PORT = "18789"/); + + // The shared port constant must remain exported (used by sandbox image build). + const portsSource = fs.readFileSync( + path.join(import.meta.dirname, "..", "src", "lib", "ports.ts"), + "utf-8", + ); + assert.match(portsSource, /export const SANDBOX_DASHBOARD_PORT = 18789/); + }); + it("classifies gateway reuse states conservatively", () => { expect( getGatewayReuseState(