feat: add --dev/--profile CLI profiles
This commit is contained in:
@@ -19,6 +19,24 @@ describe("browser config", () => {
|
||||
expect(profile?.cdpIsLoopback).toBe(true);
|
||||
});
|
||||
|
||||
it("derives default ports from CLAWDBOT_GATEWAY_PORT when unset", () => {
|
||||
const prev = process.env.CLAWDBOT_GATEWAY_PORT;
|
||||
process.env.CLAWDBOT_GATEWAY_PORT = "19001";
|
||||
try {
|
||||
const resolved = resolveBrowserConfig(undefined);
|
||||
expect(resolved.controlPort).toBe(19003);
|
||||
const profile = resolveProfile(resolved, resolved.defaultProfile);
|
||||
expect(profile?.cdpPort).toBe(19012);
|
||||
expect(profile?.cdpUrl).toBe("http://127.0.0.1:19012");
|
||||
} finally {
|
||||
if (prev === undefined) {
|
||||
delete process.env.CLAWDBOT_GATEWAY_PORT;
|
||||
} else {
|
||||
process.env.CLAWDBOT_GATEWAY_PORT = prev;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it("normalizes hex colors", () => {
|
||||
const resolved = resolveBrowserConfig({
|
||||
controlUrl: "http://localhost:18791",
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
import type { BrowserConfig, BrowserProfileConfig } from "../config/config.js";
|
||||
import {
|
||||
deriveDefaultBrowserCdpPortRange,
|
||||
deriveDefaultBrowserControlPort,
|
||||
} from "../config/port-defaults.js";
|
||||
import {
|
||||
DEFAULT_CLAWD_BROWSER_COLOR,
|
||||
DEFAULT_CLAWD_BROWSER_CONTROL_URL,
|
||||
@@ -89,11 +93,12 @@ function ensureDefaultProfile(
|
||||
profiles: Record<string, BrowserProfileConfig> | undefined,
|
||||
defaultColor: string,
|
||||
legacyCdpPort?: number,
|
||||
derivedDefaultCdpPort?: number,
|
||||
): Record<string, BrowserProfileConfig> {
|
||||
const result = { ...profiles };
|
||||
if (!result[DEFAULT_CLAWD_BROWSER_PROFILE_NAME]) {
|
||||
result[DEFAULT_CLAWD_BROWSER_PROFILE_NAME] = {
|
||||
cdpPort: legacyCdpPort ?? CDP_PORT_RANGE_START,
|
||||
cdpPort: legacyCdpPort ?? derivedDefaultCdpPort ?? CDP_PORT_RANGE_START,
|
||||
color: defaultColor,
|
||||
};
|
||||
}
|
||||
@@ -103,13 +108,30 @@ export function resolveBrowserConfig(
|
||||
cfg: BrowserConfig | undefined,
|
||||
): ResolvedBrowserConfig {
|
||||
const enabled = cfg?.enabled ?? DEFAULT_CLAWD_BROWSER_ENABLED;
|
||||
const envControlUrl = process.env.CLAWDBOT_BROWSER_CONTROL_URL?.trim();
|
||||
const derivedControlPort = (() => {
|
||||
const raw = process.env.CLAWDBOT_GATEWAY_PORT?.trim();
|
||||
if (!raw) return null;
|
||||
const gatewayPort = Number.parseInt(raw, 10);
|
||||
if (!Number.isFinite(gatewayPort) || gatewayPort <= 0) return null;
|
||||
return deriveDefaultBrowserControlPort(gatewayPort);
|
||||
})();
|
||||
const derivedControlUrl = derivedControlPort
|
||||
? `http://127.0.0.1:${derivedControlPort}`
|
||||
: null;
|
||||
|
||||
const controlInfo = parseHttpUrl(
|
||||
cfg?.controlUrl ?? DEFAULT_CLAWD_BROWSER_CONTROL_URL,
|
||||
cfg?.controlUrl ??
|
||||
envControlUrl ??
|
||||
derivedControlUrl ??
|
||||
DEFAULT_CLAWD_BROWSER_CONTROL_URL,
|
||||
"browser.controlUrl",
|
||||
);
|
||||
const controlPort = controlInfo.port;
|
||||
const defaultColor = normalizeHexColor(cfg?.color);
|
||||
|
||||
const derivedCdpRange = deriveDefaultBrowserCdpPortRange(controlPort);
|
||||
|
||||
const rawCdpUrl = (cfg?.cdpUrl ?? "").trim();
|
||||
let cdpInfo:
|
||||
| {
|
||||
@@ -149,6 +171,7 @@ export function resolveBrowserConfig(
|
||||
cfg?.profiles,
|
||||
defaultColor,
|
||||
legacyCdpPort,
|
||||
derivedCdpRange.start,
|
||||
);
|
||||
const cdpProtocol = cdpInfo.parsed.protocol === "https:" ? "https" : "http";
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ import path from "node:path";
|
||||
|
||||
import type { BrowserProfileConfig, ClawdbotConfig } from "../config/config.js";
|
||||
import { loadConfig, writeConfigFile } from "../config/config.js";
|
||||
import { deriveDefaultBrowserCdpPortRange } from "../config/port-defaults.js";
|
||||
import { resolveClawdUserDataDir } from "./chrome.js";
|
||||
import { parseHttpUrl, resolveProfile } from "./config.js";
|
||||
import {
|
||||
@@ -79,7 +80,10 @@ export function createBrowserProfilesService(ctx: BrowserRouteContext) {
|
||||
profileConfig = { cdpUrl: parsed.normalized, color: profileColor };
|
||||
} else {
|
||||
const usedPorts = getUsedPorts(resolvedProfiles);
|
||||
const cdpPort = allocateCdpPort(usedPorts);
|
||||
const range = deriveDefaultBrowserCdpPortRange(
|
||||
state.resolved.controlPort,
|
||||
);
|
||||
const cdpPort = allocateCdpPort(usedPorts, range);
|
||||
if (cdpPort === null) {
|
||||
throw new Error("no available CDP ports in range");
|
||||
}
|
||||
|
||||
@@ -64,6 +64,17 @@ describe("port allocation", () => {
|
||||
expect(allocateCdpPort(usedPorts)).toBe(CDP_PORT_RANGE_START);
|
||||
});
|
||||
|
||||
it("allocates within an explicit range", () => {
|
||||
const usedPorts = new Set<number>();
|
||||
expect(allocateCdpPort(usedPorts, { start: 20000, end: 20002 })).toBe(
|
||||
20000,
|
||||
);
|
||||
usedPorts.add(20000);
|
||||
expect(allocateCdpPort(usedPorts, { start: 20000, end: 20002 })).toBe(
|
||||
20001,
|
||||
);
|
||||
});
|
||||
|
||||
it("skips used ports and returns next available", () => {
|
||||
const usedPorts = new Set([CDP_PORT_RANGE_START, CDP_PORT_RANGE_START + 1]);
|
||||
expect(allocateCdpPort(usedPorts)).toBe(CDP_PORT_RANGE_START + 2);
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
/**
|
||||
* CDP port allocation for browser profiles.
|
||||
*
|
||||
* Port range: 18800-18899 (100 profiles max)
|
||||
* Default port range: 18800-18899 (100 profiles max)
|
||||
* Ports are allocated once at profile creation and persisted in config.
|
||||
* Multi-instance: callers may pass an explicit range to avoid collisions.
|
||||
*
|
||||
* Reserved ports (do not use for CDP):
|
||||
* 18789 - Gateway WebSocket
|
||||
@@ -21,8 +22,22 @@ export function isValidProfileName(name: string): boolean {
|
||||
return PROFILE_NAME_REGEX.test(name);
|
||||
}
|
||||
|
||||
export function allocateCdpPort(usedPorts: Set<number>): number | null {
|
||||
for (let port = CDP_PORT_RANGE_START; port <= CDP_PORT_RANGE_END; port++) {
|
||||
export function allocateCdpPort(
|
||||
usedPorts: Set<number>,
|
||||
range?: { start: number; end: number },
|
||||
): number | null {
|
||||
const start = range?.start ?? CDP_PORT_RANGE_START;
|
||||
const end = range?.end ?? CDP_PORT_RANGE_END;
|
||||
if (
|
||||
!Number.isFinite(start) ||
|
||||
!Number.isFinite(end) ||
|
||||
start <= 0 ||
|
||||
end <= 0
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
if (start > end) return null;
|
||||
for (let port = start; port <= end; port++) {
|
||||
if (!usedPorts.has(port)) return port;
|
||||
}
|
||||
return null;
|
||||
|
||||
Reference in New Issue
Block a user