UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

172 lines (171 loc) 7.55 kB
import { t as isTruthyEnvValue } from "./env-Di49gJwo.js"; import { b as parseStrictPositiveInteger } from "./number-coercion-CJQ8TR--.js"; import "./parse-finite-number-Z7n6tXLk.js"; import { n as parseTcpPort } from "./tcp-port-DPgvEEt3.js"; import { i as runExec } from "./exec-DubsSJS2.js"; import { o as getTailnetHostname } from "./tailscale-BOFw04AY.js"; import { n as pickPrimaryTailnetIPv4, r as pickPrimaryTailnetIPv6 } from "./tailnet-C1gjmZpy.js"; import { a as writeWideAreaGatewayZone, i as resolveWideAreaDiscoveryDomain } from "./widearea-dns-DLzurlsT.js"; import fs from "node:fs"; import path from "node:path"; //#region src/gateway/server-discovery.ts /** Formats the Bonjour instance name while preserving user-provided OpenClaw names. */ function formatBonjourInstanceName(displayName) { const trimmed = displayName.trim(); if (!trimmed) return "OpenClaw"; if (/openclaw/i.test(trimmed)) return trimmed; return `${trimmed} (OpenClaw)`; } /** Resolves the CLI path advertised to Bonjour clients, preferring explicit env config. */ function resolveBonjourCliPath(opts = {}) { const envPath = (opts.env ?? process.env).OPENCLAW_CLI_PATH?.trim(); if (envPath) return envPath; const statSync = opts.statSync ?? fs.statSync; const isFile = (candidate) => { try { return statSync(candidate).isFile(); } catch { return false; } }; const execPath = opts.execPath ?? process.execPath; const execDir = path.dirname(execPath); const siblingCli = path.join(execDir, "openclaw"); if (isFile(siblingCli)) return siblingCli; const argvPath = (opts.argv ?? process.argv)[1]; if (argvPath && isFile(argvPath)) return argvPath; const cwd = opts.cwd ?? process.cwd(); const distCli = path.join(cwd, "dist", "index.js"); if (isFile(distCli)) return distCli; const binCli = path.join(cwd, "bin", "openclaw"); if (isFile(binCli)) return binCli; } /** Resolves a Tailnet DNS hint from env or the local tailscale CLI when enabled. */ async function resolveTailnetDnsHint(opts) { const envRaw = (opts?.env ?? process.env).OPENCLAW_TAILNET_DNS?.trim(); const envValue = envRaw && envRaw.length > 0 ? envRaw.replace(/\.$/, "") : ""; if (envValue) return envValue; if (opts?.enabled === false) return; const exec = opts?.exec ?? ((command, args) => runExec(command, args, { timeoutMs: 1500, maxBuffer: 2e5 })); try { return await getTailnetHostname(exec); } catch { return; } } //#endregion //#region src/gateway/server-discovery-runtime.ts const DEFAULT_DISCOVERY_ADVERTISE_TIMEOUT_MS = 5e3; function resolveDiscoveryAdvertiseTimeoutMs(env) { const raw = env.OPENCLAW_GATEWAY_DISCOVERY_ADVERTISE_TIMEOUT_MS?.trim(); if (!raw) return DEFAULT_DISCOVERY_ADVERTISE_TIMEOUT_MS; const parsed = parseStrictPositiveInteger(raw); if (parsed === void 0) return DEFAULT_DISCOVERY_ADVERTISE_TIMEOUT_MS; return parsed; } /** Start configured Gateway discovery publishers and return their shutdown hook. */ async function startGatewayDiscovery(params) { let bonjourStop = null; const mdnsMode = params.mdnsMode ?? "minimal"; const localDiscoveryEnabled = mdnsMode !== "off" && !isTruthyEnvValue(process.env.OPENCLAW_DISABLE_BONJOUR) && !process.env.VITEST; const mdnsMinimal = mdnsMode !== "full"; const tailscaleEnabled = params.tailscaleMode !== "off"; const needsTailnetDns = localDiscoveryEnabled || params.wideAreaDiscoveryEnabled; const advertiseTimeoutMs = resolveDiscoveryAdvertiseTimeoutMs(process.env); const tailnetDns = needsTailnetDns ? await resolveTailnetDnsHint({ enabled: tailscaleEnabled }) : void 0; const sshPort = mdnsMinimal ? void 0 : parseTcpPort(process.env.OPENCLAW_SSH_PORT) ?? void 0; const cliPath = mdnsMinimal ? void 0 : resolveBonjourCliPath(); if (localDiscoveryEnabled) { const stops = []; let attemptedLocalDiscovery = false; let stoppedLocalDiscovery = false; for (const entry of params.gatewayDiscoveryServices ?? []) { attemptedLocalDiscovery = true; try { let timer; let timedOut = false; const context = { machineDisplayName: params.machineDisplayName, gatewayPort: params.port, gatewayTlsEnabled: params.gatewayTls?.enabled ?? false, gatewayTlsFingerprintSha256: params.gatewayTls?.fingerprintSha256, gatewayDirectReachable: params.gatewayDirectReachable === true, canvasPort: params.canvasPort, sshPort, tailnetDns, cliPath, minimal: mdnsMinimal }; const advertisePromise = Promise.resolve().then(() => entry.service.advertise(context)).then(async (started) => { if (timedOut) { if (started?.stop) if (stoppedLocalDiscovery) try { await started.stop(); } catch (err) { params.logDiscovery.warn(`gateway discovery stop failed: ${String(err)}`); } else stops.push(started.stop); params.logDiscovery.warn(`gateway discovery service completed after startup timeout (${entry.service.id}, plugin=${entry.pluginId})`); } return started; }, (err) => { params.logDiscovery.warn(`gateway discovery service failed${timedOut ? " after startup timeout" : ""} (${entry.service.id}, plugin=${entry.pluginId}): ${String(err)}`); }); const timeoutPromise = new Promise((resolve) => { timer = setTimeout(() => { timedOut = true; params.logDiscovery.warn(`gateway discovery service timed out after ${advertiseTimeoutMs}ms (${entry.service.id}, plugin=${entry.pluginId}); continuing startup`); resolve(void 0); }, advertiseTimeoutMs); timer.unref?.(); }); const started = await Promise.race([advertisePromise, timeoutPromise]); if (timer) clearTimeout(timer); if (started?.stop) stops.push(started.stop); } catch (err) { params.logDiscovery.warn(`gateway discovery service failed (${entry.service.id}, plugin=${entry.pluginId}): ${String(err)}`); } } if (attemptedLocalDiscovery) bonjourStop = async () => { stoppedLocalDiscovery = true; for (const stop of stops.toReversed()) try { await stop(); } catch (err) { params.logDiscovery.warn(`gateway discovery stop failed: ${String(err)}`); } }; } if (params.wideAreaDiscoveryEnabled) { const wideAreaDomain = resolveWideAreaDiscoveryDomain({ configDomain: params.wideAreaDiscoveryDomain ?? void 0 }); if (!wideAreaDomain) { params.logDiscovery.warn("discovery.wideArea.enabled is true, but no domain was configured; set discovery.wideArea.domain to enable unicast DNS-SD"); return { bonjourStop }; } const tailnetIPv4 = pickPrimaryTailnetIPv4(); if (!tailnetIPv4) params.logDiscovery.warn("discovery.wideArea.enabled is true, but no Tailscale IPv4 address was found; skipping unicast DNS-SD zone update"); else try { const tailnetIPv6 = pickPrimaryTailnetIPv6(); const result = await writeWideAreaGatewayZone({ domain: wideAreaDomain, gatewayPort: params.port, displayName: formatBonjourInstanceName(params.machineDisplayName), tailnetIPv4, tailnetIPv6: tailnetIPv6 ?? void 0, gatewayTlsEnabled: params.gatewayTls?.enabled ?? false, gatewayTlsFingerprintSha256: params.gatewayTls?.fingerprintSha256, gatewayDirectReachable: params.gatewayDirectReachable === true, tailnetDns, sshPort, cliPath }); params.logDiscovery.info(`wide-area DNS-SD ${result.changed ? "updated" : "unchanged"} (${wideAreaDomain}${result.zonePath})`); } catch (err) { params.logDiscovery.warn(`wide-area discovery update failed: ${String(err)}`); } } return { bonjourStop }; } //#endregion export { startGatewayDiscovery };