openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
211 lines (210 loc) • 8.05 kB
JavaScript
import "./utils-P__uGsPB.js";
import { t as sleep } from "./sleep-D7nua6TP.js";
import { i as GATEWAY_CLIENT_NAMES, n as GATEWAY_CLIENT_IDS, r as GATEWAY_CLIENT_MODES } from "./client-info-B1bPgeKr.js";
import { n as resolveGatewayCredentialsWithSecretInputs } from "./credentials-secret-inputs-BlthacCl.js";
import { o as callGateway } from "./call-BWR5pPgw.js";
import { t as resolveAdvertisedControlUiLinks } from "./control-ui-links-BY4F5DYV.js";
import { n as t } from "./i18n-hynzGFbD.js";
import { n as openUrl, t as detectBrowserOpenSupport } from "./browser-open-m1tKJTvZ.js";
import { i as formatControlUiSshHint } from "./onboard-helpers-BDY-6Z-t.js";
import { i as waitForControlUiDocument, n as issueControlUiBrowserHandoff, r as resolveControlUiHandoffTarget, t as hasVerifiedControlUiLoopbackAlias } from "./control-ui-handoff-CipgXwfB.js";
//#region src/commands/onboard-browser-handoff.ts
const GUI_HANDOFF_TIMEOUT_MS = 6e4;
const HEADLESS_HANDOFF_TIMEOUT_MS = 3e5;
const HANDOFF_POLL_INTERVAL_MS = 1e3;
const HANDOFF_PROBE_TIMEOUT_MS = 5e3;
async function resolveBrowserHatchTarget(config, env) {
const shared = await resolveControlUiHandoffTarget({
config,
env
});
const credentials = await resolveGatewayCredentialsWithSecretInputs({
config,
env,
modeOverride: "local"
});
const authMode = !config.gateway?.auth?.mode && credentials.password ? "password" : shared.authMode;
const token = authMode === "token" ? credentials.token : void 0;
const setupAuthValue = authMode === "password" ? credentials.password : void 0;
const target = {
config,
dashboardUrl: shared.links.httpUrl,
documentUrl: shared.documentUrl,
port: shared.port,
...shared.loopbackAliasHost ? { loopbackAliasHost: shared.loopbackAliasHost } : {},
...shared.tlsConfig ? { tlsConfig: shared.tlsConfig } : {},
...shared.bind === "loopback" || shared.tlsConfig?.enabled !== true ? { sshHint: formatControlUiSshHint({
port: shared.port,
...shared.basePath ? { basePath: shared.basePath } : {},
tlsEnabled: shared.tlsConfig?.enabled === true
}) } : {},
...token ? { token } : {}
};
if (setupAuthValue) target["password"] = setupAuthValue;
return target;
}
function isConnectedControlUi(entry) {
return entry.host === GATEWAY_CLIENT_IDS.CONTROL_UI && entry.mode === GATEWAY_CLIENT_MODES.WEBCHAT && entry.reason !== "disconnect";
}
function retargetBrowserHandoffUrl(browserUrl, visibleBaseUrl) {
const issued = new URL(browserUrl);
const visible = new URL(visibleBaseUrl);
visible.hash = issued.hash;
return visible.toString();
}
function resolveConnectedControlUiPresenceKeys(entries) {
return entries.filter(isConnectedControlUi).map((entry) => [
entry.deviceId,
entry.instanceId,
entry.host,
entry.mode
].join("\0"));
}
async function probeDashboardPresence(target, timeoutMs) {
try {
return {
reachable: true,
clientKeys: resolveConnectedControlUiPresenceKeys(await callGateway({
config: target.config,
method: "system-presence",
timeoutMs,
clientName: GATEWAY_CLIENT_NAMES.CLI,
mode: GATEWAY_CLIENT_MODES.CLI,
...target.token ? { token: target.token } : {},
...target.password ? { password: target.password } : {},
expectFinal: false,
ignoreEnvUrlOverride: true
}) ?? [])
};
} catch (error) {
return {
reachable: false,
reason: error instanceof Error ? error.message : String(error)
};
}
}
async function waitForDashboardClient(params) {
const now = params.now ?? Date.now;
const sleepFor = params.sleep ?? sleep;
const deadline = now() + params.timeoutMs;
while (true) {
const beforeProbeMs = deadline - now();
if (beforeProbeMs <= 0) return {
connected: false,
reason: "timeout"
};
const result = await params.probe(params.target, Math.min(HANDOFF_PROBE_TIMEOUT_MS, beforeProbeMs));
if (!result.reachable) return {
connected: false,
reason: "gateway-unreachable"
};
if (result.clientKeys.some((key) => !params.baselineClientKeys.has(key))) return { connected: true };
const remainingMs = deadline - now();
if (remainingMs <= 0) return {
connected: false,
reason: "timeout"
};
await sleepFor(Math.min(HANDOFF_POLL_INTERVAL_MS, remainingMs));
}
}
/** Opens or prints the dashboard and waits for its Control UI client connection. */
async function runBrowserHatchHandoff(params, deps = {}) {
const env = deps.env ?? process.env;
if (params.suppressTokenOutput === true || params.config.gateway?.controlUi?.enabled === false) return {
handedOff: false,
reason: "target-unavailable"
};
const canOpenBrowser = (await detectBrowserOpenSupport(deps)).ok;
let target;
try {
target = await (deps.resolveTarget ?? resolveBrowserHatchTarget)(params.config, env);
} catch {
return {
handedOff: false,
reason: "target-unavailable"
};
}
if (!await (deps.verifyLoopbackAlias ?? hasVerifiedControlUiLoopbackAlias)(target)) return {
handedOff: false,
reason: "target-unavailable"
};
let progress;
try {
if (!(await (deps.waitForDocument ?? waitForControlUiDocument)({
url: target.documentUrl,
tlsConfig: target.tlsConfig,
onPending: () => {
progress = params.prompter.progress(t("wizard.guided.controlUiPreparing"));
}
})).ready) return {
handedOff: false,
reason: "target-unavailable"
};
} catch {
return {
handedOff: false,
reason: "target-unavailable"
};
} finally {
progress?.stop();
}
const probePresence = deps.probePresence ?? probeDashboardPresence;
const baseline = await probePresence(target, HANDOFF_PROBE_TIMEOUT_MS);
if (!baseline.reachable) return {
handedOff: false,
reason: "gateway-unreachable"
};
let browserUrl;
try {
browserUrl = (await (deps.issueBrowserHandoff ?? issueControlUiBrowserHandoff)(target.dashboardUrl)).browserUrl;
} catch {
return {
handedOff: false,
reason: "target-unavailable"
};
}
let opened = false;
if (canOpenBrowser) try {
opened = await (deps.openBrowser ?? openUrl)(browserUrl);
} catch {
opened = false;
}
if (opened) await params.prompter.note(t("wizard.guided.browserHandoffOpening"), t("wizard.guided.browserHandoffTitle"));
else {
const bind = target.config.gateway?.bind;
const remoteBind = bind === "lan" || bind === "tailnet" || bind === "custom";
const remoteSession = Boolean(env.SSH_CLIENT || env.SSH_TTY || env.SSH_CONNECTION || env.REMOTE_CONTAINERS || env.CODESPACES);
const directRemoteDisplay = remoteBind && target.tlsConfig?.enabled === true;
const tunnelHint = !directRemoteDisplay && (!canOpenBrowser || remoteSession) ? target.sshHint ?? (remoteBind ? formatControlUiSshHint({
port: target.port,
...target.config.gateway?.controlUi?.basePath ? { basePath: target.config.gateway.controlUi.basePath } : {},
tlsEnabled: target.tlsConfig?.enabled === true
}) : void 0) : void 0;
const sshHint = tunnelHint ? `\n\n${tunnelHint}` : "";
const visibleBaseUrl = directRemoteDisplay ? (await resolveAdvertisedControlUiLinks({
bind,
port: target.port,
customBindHost: target.config.gateway?.customBindHost,
basePath: target.config.gateway?.controlUi?.basePath,
tlsEnabled: target.tlsConfig?.enabled === true
})).httpUrl : target.dashboardUrl;
const visibleUrl = retargetBrowserHandoffUrl(browserUrl, visibleBaseUrl);
await params.prompter.note(`${t("wizard.guided.browserHandoffCopy", { url: visibleUrl })}${sshHint}`, t("wizard.guided.browserHandoffTitle"));
}
const wait = await (deps.pollForClient ?? waitForDashboardClient)({
target,
baselineClientKeys: new Set(baseline.clientKeys),
timeoutMs: opened ? GUI_HANDOFF_TIMEOUT_MS : HEADLESS_HANDOFF_TIMEOUT_MS,
probe: probePresence,
...deps.now ? { now: deps.now } : {},
...deps.sleep ? { sleep: deps.sleep } : {}
});
if (!wait.connected) return {
handedOff: false,
reason: wait.reason
};
await params.prompter.note(t("wizard.guided.browserHandoffContinuing"), t("wizard.guided.browserHandoffTitle"));
return { handedOff: true };
}
//#endregion
export { runBrowserHatchHandoff as n, resolveConnectedControlUiPresenceKeys as t };