openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
324 lines (323 loc) • 13 kB
JavaScript
import { o as normalizeLowercaseStringOrEmpty } from "./string-coerce-CIXf7egm.js";
import { n as parseTcpPort, r as parseTcpPortFromArgs } from "./tcp-port-C3gLZtJi.js";
import { r as assertGatewayServiceMutationAllowed } from "./gateway-supervision-D7p37rG2.js";
import { h as resolveFutureConfigActionBlock, m as formatFutureConfigActionBlock } from "./config-env-vars-DUfQlcAk.js";
import { r as readSystemdServiceExecStart } from "./systemd-service-files-BCcumv4T.js";
import { t as resolveServiceEntrypoint } from "./service-layout-bTjykwbW.js";
import { c as restartLaunchAgent, d as stageLaunchAgent, f as uninstallLaunchAgent, l as startLaunchAgent, o as stopLaunchAgent, u as installLaunchAgent } from "./launchd-BH9PtJsR.js";
import { n as mergeGatewayServiceEnv } from "./gateway-service-probe-hosts-CH6J1p0Q.js";
import { d as readLaunchAgentProgramArguments, f as readLaunchAgentRuntime, i as isLaunchAgentLoaded, r as isLaunchAgentEnabled } from "./launchd-runtime-D4UiKWFz.js";
import { _ as readScheduledTaskCommand, d as isScheduledTaskInstalled, f as readScheduledTaskRuntime, i as restartScheduledTask, n as stageScheduledTask, o as startScheduledTask, r as uninstallScheduledTask, s as stopScheduledTask, t as installScheduledTask } from "./schtasks-CgL6lOw1.js";
import { t as createServiceRuntimeInspectionFailure } from "./service-runtime-C1Las5eF.js";
import { n as findInstalledSystemdGatewayScope, o as isSystemdServiceAbsent } from "./systemd-scope-MYrAg51P.js";
import { a as restartSystemdService, f as stageSystemdService, m as readSystemdDefinitionMutationCapability, n as readSystemdServiceRuntime, o as startSystemdService, p as uninstallSystemdService, s as stopSystemdService, t as isSystemdServiceEnabled, u as installSystemdService } from "./systemd-C1W5NZTY.js";
import fs from "node:fs";
import path from "node:path";
import os from "node:os";
//#region src/daemon/future-config-guard.ts
/** Prevents daemon write actions when the config belongs to a newer OpenClaw. */
async function readFutureConfigActionBlock(action) {
const { readConfigFileSnapshot } = await import("./io.runtime.js");
try {
const snapshot = await readConfigFileSnapshot();
return resolveFutureConfigActionBlock({
action,
snapshot
});
} catch {
return null;
}
}
async function assertFutureConfigActionAllowed(action) {
const block = await readFutureConfigActionBlock(action);
if (block) throw new Error(formatFutureConfigActionBlock(block));
}
//#endregion
//#region src/daemon/service.ts
/** Platform service registry and shared gateway service start/repair logic. */
function ignoreServiceWriteResult(write) {
return async (args) => {
await write(args);
};
}
const TEMP_PROGRAM_ROOTS = [
os.tmpdir(),
"/tmp",
"/private/tmp",
"/var/tmp"
].map((entry) => path.resolve(entry));
function pathIsSameOrChild(candidate, parent) {
return candidate === parent || candidate.startsWith(`${parent}${path.sep}`);
}
function isTemporaryProgramPath(value) {
if (!value || !path.isAbsolute(value)) return false;
const resolved = path.resolve(value);
return TEMP_PROGRAM_ROOTS.some((root) => pathIsSameOrChild(resolved, root));
}
function isMissingProgramPath(value) {
if (!value || !path.isAbsolute(value)) return false;
return !fs.existsSync(value);
}
function collectGatewayServiceStartRepairIssues(state, expectedPort) {
const command = state.command;
if (state.loadState.status !== "loaded" || !command) return [];
const issues = [];
const servicePort = parseTcpPortFromArgs(command.programArguments) ?? parseTcpPort(command.environment?.OPENCLAW_GATEWAY_PORT ?? "");
if (expectedPort !== void 0 && servicePort !== null && servicePort !== expectedPort) issues.push({
code: "port-mismatch",
message: `service port ${servicePort} does not match current gateway config port ${expectedPort}`
});
for (const candidate of /* @__PURE__ */ new Set([command.programArguments[0], resolveServiceEntrypoint(command)])) {
if (isTemporaryProgramPath(candidate)) {
issues.push({
code: "temporary-program",
message: `service command points at a temporary path: ${candidate}`
});
continue;
}
if (isMissingProgramPath(candidate)) issues.push({
code: "missing-program",
message: `service command points at a missing path: ${candidate}`
});
}
return issues;
}
/** Reads the installed service and reports definition drift that must be repaired before launch. */
async function inspectGatewayServiceStartRepair(service, args, expectedPort) {
const state = await readGatewayServiceState(service, args);
return {
state,
issues: collectGatewayServiceStartRepairIssues(state, expectedPort)
};
}
function formatGatewayServiceStartRepairIssues(issues) {
return issues.map((issue) => issue.message).join("; ");
}
async function readGatewayServiceLoadState(service, args = {}) {
try {
return { status: await service.isLoaded(args) ? "loaded" : "not-loaded" };
} catch (error) {
return {
status: "unknown",
detail: String(error)
};
}
}
async function readGatewayServiceState(service, args = {}) {
const baseEnv = args.env ?? process.env;
const { timeoutMs } = args;
if (await service.isAbsent?.({
env: baseEnv,
timeoutMs
}).catch(() => false)) {
args.validateEnvBeforeStatusRead?.(baseEnv);
return {
installed: false,
loadState: { status: "not-loaded" },
running: false,
env: baseEnv,
command: null,
runtime: {
status: "stopped",
missingUnit: true
}
};
}
const command = args.requireEffective ? await service.readCommand(baseEnv, {
timeoutMs,
requireEffective: true
}) : await service.readCommand(baseEnv, { timeoutMs }).catch(() => null);
const env = mergeGatewayServiceEnv(baseEnv, command);
args.validateEnvBeforeStatusRead?.(env);
const [installed, loadState, runtime, definitionMutationCapability] = await Promise.all([
command !== null ? true : service.hasInstalledDefinition?.({
env,
timeoutMs
}).catch(() => false) ?? false,
readGatewayServiceLoadState(service, {
env,
timeoutMs
}),
service.readRuntime(env, { timeoutMs }).catch((error) => createServiceRuntimeInspectionFailure(error)),
args.requireEffective ? service.readDefinitionMutationCapability?.({
env: baseEnv,
environment: env,
timeoutMs
}).catch(() => ({
kind: "unknown",
reason: "inspection-failed"
})) : void 0
]);
return {
installed,
loadState,
running: runtime?.status === "running",
env,
command,
...definitionMutationCapability ? { definitionMutationCapability } : {},
runtime
};
}
async function startGatewayService(service, args, expectedPort) {
const { state, issues: repairIssues } = await inspectGatewayServiceStartRepair(service, { env: args.env }, expectedPort);
if (state.loadState.status === "unknown") throw new Error(`Service status inspection failed: ${state.loadState.detail}`);
if (state.loadState.status === "not-loaded" && !state.installed) return {
outcome: "missing-install",
state
};
if (state.loadState.status === "loaded" && state.running) return {
outcome: "already-running",
state,
issues: repairIssues
};
if (repairIssues.length > 0) return {
outcome: "repair-required",
state,
issues: repairIssues
};
let nextState;
try {
await service.start({
...args,
env: state.env
});
nextState = await readGatewayServiceState(service, { env: state.env });
} catch (err) {
const recoveryState = await readGatewayServiceState(service, { env: state.env });
if (!recoveryState.installed) return {
outcome: "missing-install",
state: recoveryState
};
throw err;
}
if (nextState.loadState.status === "unknown") throw new Error(`Service status inspection failed after start: ${nextState.loadState.detail}`);
const runtime = nextState.runtime;
const failedState = normalizeLowercaseStringOrEmpty(runtime?.state) === "failed";
const newFailedExit = runtime?.status === "stopped" && typeof runtime.lastExitStatus === "number" && runtime.lastExitStatus !== 0 && runtime.lastExitStatus !== state.runtime?.lastExitStatus;
if (failedState || newFailedExit) {
const failure = failedState ? "state failed" : `exit ${runtime?.lastExitStatus}`;
throw new Error(`Service failed to start (${failure}). Check the service logs and retry.`);
}
return {
outcome: "started",
state: nextState
};
}
function describeGatewayServiceRestart(serviceNoun, result) {
if (result.outcome === "scheduled") return {
scheduled: true,
daemonActionResult: "scheduled",
message: `restart scheduled, ${normalizeLowercaseStringOrEmpty(serviceNoun)} will restart momentarily`,
progressMessage: `${serviceNoun} service restart scheduled.`
};
return {
scheduled: false,
daemonActionResult: "restarted",
message: `${serviceNoun} service restarted.`,
progressMessage: `${serviceNoun} service restarted.`
};
}
function createUnsupportedGatewayServiceError() {
return /* @__PURE__ */ new Error(`Gateway service install not supported on ${process.platform}`);
}
async function rejectUnsupportedGatewayService() {
throw createUnsupportedGatewayServiceError();
}
function createUnsupportedGatewayService() {
return {
label: "Gateway service",
loadedText: "available",
notLoadedText: "not installed",
stage: rejectUnsupportedGatewayService,
install: rejectUnsupportedGatewayService,
uninstall: rejectUnsupportedGatewayService,
start: rejectUnsupportedGatewayService,
stop: rejectUnsupportedGatewayService,
restart: rejectUnsupportedGatewayService,
isLoaded: rejectUnsupportedGatewayService,
readCommand: async () => null,
readRuntime: async () => ({
status: "unknown",
detail: createUnsupportedGatewayServiceError().message
})
};
}
const GATEWAY_SERVICE_REGISTRY = {
darwin: {
label: "LaunchAgent",
loadedText: "loaded",
notLoadedText: "not loaded",
stage: ignoreServiceWriteResult(stageLaunchAgent),
install: ignoreServiceWriteResult(installLaunchAgent),
uninstall: uninstallLaunchAgent,
start: startLaunchAgent,
stop: stopLaunchAgent,
restart: restartLaunchAgent,
isLoaded: isLaunchAgentLoaded,
isEnabled: isLaunchAgentEnabled,
readCommand: readLaunchAgentProgramArguments,
readRuntime: readLaunchAgentRuntime
},
linux: {
label: "systemd user",
loadedText: "enabled",
notLoadedText: "disabled",
stage: ignoreServiceWriteResult(stageSystemdService),
install: ignoreServiceWriteResult(installSystemdService),
uninstall: uninstallSystemdService,
start: startSystemdService,
stop: stopSystemdService,
restart: restartSystemdService,
isLoaded: isSystemdServiceEnabled,
isAbsent: ({ env }) => isSystemdServiceAbsent(env ?? process.env),
hasInstalledDefinition: async ({ env }) => await findInstalledSystemdGatewayScope(env ?? process.env) !== null,
readDefinitionMutationCapability: ({ env, environment, timeoutMs }) => readSystemdDefinitionMutationCapability(env ?? process.env, {
environment,
timeoutMs
}),
readCommand: readSystemdServiceExecStart,
readRuntime: readSystemdServiceRuntime
},
win32: {
label: "Scheduled Task",
loadedText: "registered",
notLoadedText: "missing",
stage: ignoreServiceWriteResult(stageScheduledTask),
install: ignoreServiceWriteResult(installScheduledTask),
uninstall: uninstallScheduledTask,
start: startScheduledTask,
stop: stopScheduledTask,
restart: restartScheduledTask,
isLoaded: isScheduledTaskInstalled,
readCommand: readScheduledTaskCommand,
readRuntime: readScheduledTaskRuntime
}
};
function guardGatewayServiceMutation(action, mutate) {
return async (args) => {
assertGatewayServiceMutationAllowed(action, process.env);
if (args.env && args.env !== process.env) assertGatewayServiceMutationAllowed(action, args.env);
await assertFutureConfigActionAllowed(action);
return await mutate(args);
};
}
function withGatewayServiceMutationGuards(service) {
return {
...service,
stage: guardGatewayServiceMutation("rewrite the gateway service", service.stage),
install: guardGatewayServiceMutation("install or rewrite the gateway service", service.install),
uninstall: guardGatewayServiceMutation("uninstall the gateway service", service.uninstall),
start: guardGatewayServiceMutation("start the gateway service", service.start),
stop: guardGatewayServiceMutation("stop the gateway service", service.stop),
restart: guardGatewayServiceMutation("restart the gateway service", service.restart)
};
}
function isSupportedGatewayServicePlatform(platform) {
return Object.hasOwn(GATEWAY_SERVICE_REGISTRY, platform);
}
function resolveGatewayService() {
if (isSupportedGatewayServicePlatform(process.platform)) return withGatewayServiceMutationGuards(GATEWAY_SERVICE_REGISTRY[process.platform]);
return createUnsupportedGatewayService();
}
//#endregion
export { readGatewayServiceState as a, readGatewayServiceLoadState as i, formatGatewayServiceStartRepairIssues as n, resolveGatewayService as o, inspectGatewayServiceStartRepair as r, startGatewayService as s, describeGatewayServiceRestart as t };