openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
165 lines (164 loc) • 7.64 kB
JavaScript
import { t as createDeferredCore } from "./deferred-D0La5CRk.mjs";
import { o as redactSensitiveUrlLikeString } from "./redact-sensitive-url-BnHakqqk.mjs";
import { t as resolveIdentityPathViaExistingAncestorSync } from "./boundary-path-CAv_plX7.mjs";
import { r as resolveStateDir } from "./state-dir-BHJORNly.mjs";
import { m as resolveConfigPath } from "./paths-DGIwWbpu.mjs";
import { t as formatErrorMessage } from "./errors-O33D01XW.mjs";
import { o as registerSignalExitGate } from "./signal-exit-barrier-BFniU76K.mjs";
import { i as GATEWAY_CLIENT_NAMES, r as GATEWAY_CLIENT_MODES } from "./client-info-C8RatpKO.mjs";
import { t as GATEWAY_SERVER_CAPS } from "./server-capabilities-BTjjxElt.mjs";
import { statSync } from "node:fs";
import path from "node:path";
//#region src/cli/local-state-owner.ts
var LocalStateOwnerError = class extends Error {
constructor(code, message, cause) {
super(message, { cause });
this.code = code;
this.name = "LocalStateOwnerError";
}
};
/** Select one owner before domain admission and retain offline custody through resource settlement. */
async function runWithLocalStateOwner(params) {
const selectedEnv = { ...process.env };
const selectedStateDir = resolveStateDir(selectedEnv);
const stateDir = resolveIdentityPathViaExistingAncestorSync(selectedStateDir);
const rootIdentity = statSync(stateDir, {
bigint: true,
throwIfNoEntry: false
});
const env = {
...selectedEnv,
OPENCLAW_STATE_DIR: stateDir,
OPENCLAW_CONFIG_PATH: resolveConfigPath(selectedEnv, selectedStateDir)
};
const input = structuredClone(params.params);
const [{ acquireGatewayLock, isGatewayLifecycleContentionError, isSameGatewayLockIdentity, readActiveGatewayLockIdentity, readLockPayloadSync, resolveGatewayLockPaths }, { captureGatewayStateOwner }, { createOpenClawDatabaseMaintenanceScope }] = await Promise.all([
import("./gateway-lock-C0JnIOLH.mjs"),
import("./gateway-state-owner-58tyvYs2.mjs"),
import("./openclaw-state-db-async-lifecycle-B0GRf5rO.mjs")
]);
const paths = resolveGatewayLockPaths(env);
const databasePath = path.join(paths.stateDir, "state", "openclaw.sqlite");
const controller = new AbortController();
const finished = createDeferredCore();
const releaseExitGate = registerSignalExitGate(finished.promise, () => controller.abort());
const assertTargetCurrent = () => {
controller.signal.throwIfAborted();
const ambientPaths = resolveGatewayLockPaths(process.env);
const currentRoot = rootIdentity ? statSync(stateDir, {
bigint: true,
throwIfNoEntry: false
}) : void 0;
if (paths.stateDir !== stateDir || ambientPaths.ownerLockPath !== paths.ownerLockPath || ambientPaths.configPath !== paths.configPath || resolveIdentityPathViaExistingAncestorSync(selectedStateDir) !== stateDir || rootIdentity && (currentRoot?.dev !== rootIdentity.dev || currentRoot?.ino !== rootIdentity.ino) || resolveGatewayLockPaths(selectedEnv).ownerLockPath !== paths.ownerLockPath) throw new LocalStateOwnerError("OWNER_UNAVAILABLE", "Selected state root changed; rerun the command.");
params.assertTargetCurrent?.();
};
const guidance = `Update the Gateway or fix authentication and retry. To run offline, stop the Gateway through its service owner, wait for ownership to release, then rerun this exact command.`;
const refuse = (cause) => {
throw new LocalStateOwnerError("OWNER_UNAVAILABLE", `Cannot admit ${params.method} for ${params.target} in state root ${paths.stateDir}: ${redactSensitiveUrlLikeString(formatErrorMessage(cause))}. No local mutation was attempted. Inspect openclaw gateway status. ${guidance}`, cause);
};
const discover = async () => {
try {
assertTargetCurrent();
return await readActiveGatewayLockIdentity({
env,
requireInspection: true,
signal: controller.signal
});
} catch (error) {
return refuse(error);
}
};
const runLocal = async (assertOwnerCurrent) => {
const assertCurrent = () => {
assertTargetCurrent();
assertOwnerCurrent();
};
assertCurrent();
const { getRuntimeConfig } = await import("./config/config.js");
assertCurrent();
const config = getRuntimeConfig();
assertCurrent();
return await params.runLocal({
env,
config,
signal: controller.signal,
assertCurrent
});
};
const route = async (owner) => {
if (params.onForeignOwner === "refuse") return refuse(/* @__PURE__ */ new Error("This operation requires exclusive offline state ownership"));
if (!owner.ownerId) return refuse(/* @__PURE__ */ new Error("Gateway lacks the expected-owner contract; update the Gateway."));
const { callGateway, isGatewayClientRequestError } = await import("./call-DLG6_hH-.mjs");
let dispatched = false;
try {
assertTargetCurrent();
return await callGateway({
method: params.method,
configPath: paths.configPath,
params: {
...input,
expectedOwnerId: owner.ownerId
},
localPortOverride: owner.port,
ignoreEnvUrlOverride: true,
requiredMethods: [params.method],
requiredCapabilities: [GATEWAY_SERVER_CAPS.LOCAL_STATE_OWNER_ROUTING, ...params.requiredCapabilities ?? []],
timeoutMs: 6e5,
signal: controller.signal,
scopes: ["operator.admin"],
clientName: GATEWAY_CLIENT_NAMES.CLI,
mode: GATEWAY_CLIENT_MODES.CLI,
prepareDispatchCurrent: async () => {
const current = await discover();
if (!current || !isSameGatewayLockIdentity(owner, current)) refuse(/* @__PURE__ */ new Error("Gateway owner changed before dispatch"));
},
assertDispatchCurrent: () => {
assertTargetCurrent();
const current = readLockPayloadSync(paths.ownerLockPath, true);
if (!current || current.ownerId !== owner.ownerId || current.pid !== owner.pid) refuse(/* @__PURE__ */ new Error("Gateway owner changed before dispatch"));
dispatched = true;
}
});
} catch (error) {
const refused = isGatewayClientRequestError(error) && typeof error.details === "object" && error.details !== null && "mutationAccepted" in error.details && error.details.mutationAccepted === false;
throw new LocalStateOwnerError(dispatched && !refused ? "OUTCOME_UNKNOWN" : "OWNER_REFUSED", `Gateway owning ${paths.stateDir} on local port ${owner.port} could not complete ${params.method} for ${params.target}: ${redactSensitiveUrlLikeString(formatErrorMessage(error))}. ` + (dispatched && !refused ? `The outcome may be partial; inspect ${params.recoveryCommand ?? "the operation's status"} and the target before retrying. No local fallback was attempted.` : `No local mutation was attempted. ${guidance}`), error);
}
};
try {
assertTargetCurrent();
const hosted = captureGatewayStateOwner(databasePath);
if (hosted) return await runLocal(hosted.assertCurrent);
const owner = await discover();
if (owner) return await route(owner);
let lock;
try {
lock = await acquireGatewayLock({
env,
role: "agent-embedded",
allowInTests: true
});
} catch (error) {
if (isGatewayLifecycleContentionError(error)) {
const winner = await discover();
if (winner) return await route(winner);
}
return refuse(error);
}
if (!lock) return refuse(/* @__PURE__ */ new Error("Offline state ownership was not acquired"));
const resources = createOpenClawDatabaseMaintenanceScope({
assertOwnerCurrent: () => lock.assertCurrent(),
assertDatabaseAccess: lock.assertDatabaseAccess
});
try {
return await resources.run(() => runLocal(lock.assertCurrent));
} finally {
await resources.close();
await lock.release();
}
} finally {
finished.resolve();
releaseExitGate();
}
}
//#endregion
export { runWithLocalStateOwner as t };