openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
271 lines (270 loc) • 10.5 kB
JavaScript
import { d as resolveGatewayPort } from "./paths-mvMm5bYV.js";
import { t as createLazyImportLoader } from "./lazy-promise-BONnzNfb.js";
import { n as defaultRuntime } from "./runtime-B4lgFmsS.js";
import { t as resolveGatewayAuthToken } from "./auth-token-resolution-AiDzF8d9.js";
import { n as promptYesNo } from "./prompt-DN3iQ5gF.js";
import { r as runCommandWithTimeout } from "./exec-DubsSJS2.js";
import { u as readConfigFileSnapshot } from "./io-Gi7-pyU-.js";
import "./config-C9RxTsn1.js";
import { n as isWSL2Sync } from "./wsl-Bb_g7JpO.js";
import { t as resolveControlUiLinks } from "./control-ui-links-HaNDPHaQ.js";
import { n as openUrl, t as detectBrowserOpenSupport } from "./browser-open-dLnrbzcu.js";
import { i as formatControlUiSshHint } from "./onboard-helpers-DXyUiHQ-.js";
import { a as gatewayProbeResultSawGateway } from "./gateway-health-auth-diagnostic-Bd2f-neB.js";
//#region src/infra/clipboard.ts
const WSL_CLIPBOARD_ARGV = [
"/bin/sh",
"-c",
"exec /mnt/c/Windows/System32/clip.exe"
];
async function copyToClipboard(value) {
const attempts = [
...isWSL2Sync() ? [{ argv: WSL_CLIPBOARD_ARGV }] : [],
{ argv: ["pbcopy"] },
{ argv: [
"xclip",
"-selection",
"clipboard"
] },
{ argv: ["wl-copy"] },
{ argv: ["clip.exe"] },
{ argv: [
"powershell",
"-NoProfile",
"-Command",
"Set-Clipboard"
] }
];
for (const attempt of attempts) try {
const result = await runCommandWithTimeout(attempt.argv, {
timeoutMs: 3e3,
input: value
});
if (result.code === 0 && !result.killed) return true;
} catch {}
return false;
}
//#endregion
//#region src/commands/gateway-readiness.ts
const daemonStatusModuleLoader = createLazyImportLoader(() => import("./status.gather-DIr8hvwr.js"));
const daemonInstallModuleLoader = createLazyImportLoader(() => import("./install.runtime-CiYS7IcU.js"));
const daemonLifecycleModuleLoader = createLazyImportLoader(() => import("./lifecycle-DN9r7mpZ.js"));
async function defaultGatherStatus(params) {
const { gatherDaemonStatus } = await daemonStatusModuleLoader.load();
return gatherDaemonStatus({
rpc: params.probeUrl ? { url: params.probeUrl } : {},
probe: true,
requireRpc: params.requireRpc,
deep: false
});
}
function activeProbePortStatus(status) {
const probeUrl = status.rpc?.url ?? status.gateway?.probeUrl;
const probePort = probeUrl ? (() => {
try {
return Number(new URL(probeUrl).port);
} catch {
return NaN;
}
})() : NaN;
if (Number.isFinite(probePort) && status.portCli?.port === probePort) return status.portCli;
return status.port;
}
function gatewayIsRunning(status) {
return status.rpc?.ok === true;
}
function gatewayProbeSawGateway(status) {
return Boolean(status.rpc && gatewayProbeResultSawGateway(status.rpc));
}
function gatewayLooksReachable(status) {
if (gatewayIsRunning(status)) return true;
if (activeProbePortStatus(status)?.status !== "busy") return false;
return gatewayProbeSawGateway(status);
}
function gatewayIsReady(status, options) {
return gatewayIsRunning(status) || options.readyWhenReachable === true && gatewayLooksReachable(status);
}
function gatewayLooksStopped(status) {
if (status.rpc?.ok === true) return false;
if (activeProbePortStatus(status)?.status === "free") return true;
if (status.service.runtime?.status === "stopped") return true;
const error = status.rpc?.error ?? "";
return /\bECONNREFUSED\b|couldn't connect|connection refused/i.test(error);
}
function gatewayServiceIsInstalled(status) {
return Boolean(status.service.command || status.service.loaded);
}
function readinessFailureReason(status) {
if (gatewayLooksStopped(status)) return "Gateway is not running.";
return status.rpc?.error ? `Gateway probe failed: ${status.rpc.error}` : "Gateway is not healthy.";
}
function printGatewayNotReadyHints(runtime, reason) {
runtime.log(reason);
runtime.log("Run `openclaw gateway status --deep` for details.");
runtime.log("Run `openclaw gateway start` to start a managed gateway.");
runtime.log("Run `openclaw gateway run` for a foreground gateway.");
}
async function confirmRecovery(params) {
if (params.yes) return true;
if (!(params.interactive ?? process.stdin.isTTY)) return false;
return params.confirm(params.message, true);
}
async function waitForGatewayReady(params) {
const attempts = params.attempts ?? 20;
const delayMs = params.delayMs ?? 500;
let latest = await params.gatherStatus();
for (let attempt = 1; attempt < attempts && !gatewayIsReady(latest, { readyWhenReachable: params.readyWhenReachable }); attempt += 1) {
await new Promise((resolve) => {
setTimeout(resolve, delayMs);
});
latest = await params.gatherStatus();
}
return latest;
}
/** Checks readiness and, when approved, recovers by installing or starting the gateway. */
async function ensureGatewayReadyForOperation(options) {
const requireRpc = options.requireRpc ?? false;
const gatherStatus = options.deps?.gatherStatus ?? (() => defaultGatherStatus({
requireRpc,
probeUrl: options.probeUrl
}));
const confirm = options.deps?.confirm ?? promptYesNo;
const installGateway = options.deps?.installGateway ?? (async () => {
const { runDaemonInstall } = await daemonInstallModuleLoader.load();
await runDaemonInstall({ json: false });
});
const startGateway = options.deps?.startGateway ?? (async () => {
const { runDaemonStart } = await daemonLifecycleModuleLoader.load();
await runDaemonStart({ json: false });
});
const initialStatus = await gatherStatus();
if (gatewayIsReady(initialStatus, { readyWhenReachable: options.readyWhenReachable })) return {
ready: true,
status: initialStatus,
recovered: false
};
const reason = readinessFailureReason(initialStatus);
if (!gatewayLooksStopped(initialStatus)) {
printGatewayNotReadyHints(options.runtime, reason);
return {
ready: false,
status: initialStatus,
reason,
recoverable: false
};
}
const shouldInstall = !gatewayServiceIsInstalled(initialStatus);
if (shouldInstall && options.allowInstall === false) {
printGatewayNotReadyHints(options.runtime, reason);
return {
ready: false,
status: initialStatus,
reason,
recoverable: false
};
}
if (!await confirmRecovery({
message: shouldInstall ? `Gateway is not installed. Install and start it now so OpenClaw can ${options.operation}?` : `Gateway is not running. Start it now so OpenClaw can ${options.operation}?`,
yes: options.yes,
interactive: options.interactive,
confirm
})) {
printGatewayNotReadyHints(options.runtime, reason);
return {
ready: false,
status: initialStatus,
reason,
recoverable: true
};
}
if (shouldInstall) await installGateway();
else await startGateway();
const recoveredStatus = await waitForGatewayReady({
gatherStatus,
readyWhenReachable: options.readyWhenReachable
});
if (gatewayIsReady(recoveredStatus, { readyWhenReachable: options.readyWhenReachable })) return {
ready: true,
status: recoveredStatus,
recovered: true
};
const recoveredReason = readinessFailureReason(recoveredStatus);
printGatewayNotReadyHints(options.runtime, recoveredReason);
return {
ready: false,
status: recoveredStatus,
reason: recoveredReason,
recoverable: true
};
}
//#endregion
//#region src/commands/dashboard.ts
async function resolveDashboardTarget() {
const snapshot = await readConfigFileSnapshot();
const cfg = snapshot.valid ? snapshot.sourceConfig ?? snapshot.config : {};
const port = resolveGatewayPort(cfg);
const bind = cfg.gateway?.bind ?? "loopback";
const basePath = cfg.gateway?.controlUi?.basePath;
const customBindHost = cfg.gateway?.customBindHost;
const resolvedToken = await resolveGatewayAuthToken({
cfg,
env: process.env,
envFallback: "always"
});
const token = resolvedToken.token ?? "";
const links = resolveControlUiLinks({
port,
bind: bind === "lan" ? "loopback" : bind,
customBindHost,
basePath,
tlsEnabled: cfg.gateway?.tls?.enabled === true
});
const includeTokenInUrl = token.length > 0 && !resolvedToken.secretRefConfigured;
return {
port,
basePath,
links,
resolvedToken,
token,
includeTokenInUrl,
dashboardUrl: includeTokenInUrl ? `${links.httpUrl}#token=${encodeURIComponent(token)}` : links.httpUrl
};
}
/** Open or print the Control UI dashboard URL after ensuring the Gateway is reachable. */
async function dashboardCommand(runtime = defaultRuntime, options = {}) {
const initialTarget = await resolveDashboardTarget();
const readiness = await ensureGatewayReadyForOperation({
runtime,
operation: "open the dashboard",
yes: options.yes,
probeUrl: initialTarget.links.wsUrl,
readyWhenReachable: true
});
if (!readiness.ready) return;
const { port, basePath, links, resolvedToken, token, includeTokenInUrl, dashboardUrl } = readiness.recovered ? await resolveDashboardTarget() : initialTarget;
runtime.log(`Dashboard URL: ${links.httpUrl}`);
if (includeTokenInUrl) runtime.log("Token auto-auth included in browser/clipboard URL.");
if (resolvedToken.secretRefConfigured && token) runtime.log("Token auto-auth is disabled for SecretRef-managed gateway.auth.token; use your external token source if prompted.");
if (resolvedToken.unresolvedRefReason) {
runtime.log(`Token auto-auth unavailable: ${resolvedToken.unresolvedRefReason}`);
runtime.log("Set OPENCLAW_GATEWAY_TOKEN in this shell or resolve your secret provider, then rerun `openclaw dashboard`.");
}
const copied = await copyToClipboard(dashboardUrl).catch(() => false);
runtime.log(copied ? "Copied to clipboard." : "Copy to clipboard unavailable.");
let opened = false;
let hint;
if (!options.noOpen) {
if ((await detectBrowserOpenSupport()).ok) opened = await openUrl(dashboardUrl);
if (!opened) hint = formatControlUiSshHint({
port,
basePath
});
} else hint = copied && includeTokenInUrl ? "Browser launch disabled (--no-open). Token-authenticated URL copied to clipboard." : "Browser launch disabled (--no-open). Use the URL above.";
const fallbackToManualAuth = !copied && !opened && includeTokenInUrl;
const suppressNoOpenHint = options.noOpen === true && fallbackToManualAuth;
if (opened) runtime.log("Opened in your browser. Keep that tab to control OpenClaw.");
else if (hint && !suppressNoOpenHint) runtime.log(hint);
if (fallbackToManualAuth) runtime.log("Token auto-auth not delivered. Append your gateway token (from OPENCLAW_GATEWAY_TOKEN or gateway.auth.token) as a URL fragment with key `token` to authenticate.");
}
//#endregion
export { dashboardCommand };