openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
300 lines (299 loc) • 11.3 kB
JavaScript
import { F as resolveTimerTimeoutMs } from "./number-coercion-CuVhdCBs.mjs";
import { h as sleep } from "./utils-DgZn2dLu.mjs";
import { t as formatErrorMessage } from "./errors-O33D01XW.mjs";
import { n as awaitWithinDeadline, t as ABSOLUTE_DEADLINE_EXPIRED } from "./absolute-deadline-DoGHpXtD.mjs";
import { t as ErrorCodes } from "./gateway-error-details-DIefe7Zy.mjs";
import { f as errorShape } from "./error-codes-Dh56LniQ.mjs";
import { n as clearApnsRegistrationIfCurrent, o as loadApnsRegistration } from "./push-apns-store-BNQPyh4x.mjs";
import { n as sendApnsBackgroundWake, s as shouldClearStoredApnsRegistration, t as sendApnsAlert } from "./push-apns-DWV7vQst.mjs";
import { c as runNodeWakeAttempt, l as runNodeWakeNudgeAttempt, n as NODE_WAKE_RECONNECT_WAIT_MS, o as isNodeWakeLifecycleCurrent, r as captureNodeWakeLifecycle, s as releaseNodeWakeLifecycle, t as NODE_WAKE_RECONNECT_RETRY_WAIT_MS } from "./node-wake-state-CWVR-GCk.mjs";
import { i as isNodePairingGenerationCurrent } from "./device-pairing-node-state-BWkfmloU.mjs";
import { t as resolveNodePushTransport } from "./node-push-transport-njtux2u1.mjs";
import { t as nodeInvokePolicy } from "./nodes-policy-fOzIq_sI.mjs";
//#region src/gateway/server-methods/nodes.shared.ts
function respondPairingChanged(respond) {
respond(false, void 0, errorShape(ErrorCodes.UNAVAILABLE, "node pairing changed while invocation was active", {
retryable: true,
details: { code: "PAIRING_CHANGED" }
}));
}
async function isNodePairingWorkCurrent(params) {
if (!isNodeWakeLifecycleCurrent(params.nodeId, params.lifecycle, params.generation.key)) return false;
if (!await isNodePairingGenerationCurrent(params.generation)) return false;
return isNodeWakeLifecycleCurrent(params.nodeId, params.lifecycle, params.generation.key);
}
async function isNodePushAttemptCurrent(params) {
return params.generation ? isNodePairingWorkCurrent({
nodeId: params.nodeId,
generation: params.generation,
lifecycle: params.lifecycle
}) : isNodeWakeLifecycleCurrent(params.nodeId, params.lifecycle);
}
function resolveDispatchableNodeSession(session) {
return session?.client?.invalidated === true ? void 0 : session;
}
//#endregion
//#region src/gateway/server-methods/nodes.wake.ts
async function clearStaleApnsRegistrationIfNeeded(registration, nodeId, params) {
if (!shouldClearStoredApnsRegistration({
registration,
result: params
})) return;
await clearApnsRegistrationIfCurrent({
nodeId,
registration
});
}
async function maybeWakeNodeWithApns(nodeId, opts) {
const lifecycleProvided = opts?.lifecycle !== void 0;
const pairingGeneration = opts?.generation?.key;
const lifecycle = opts?.lifecycle ?? captureNodeWakeLifecycle(nodeId, pairingGeneration);
const isAttemptCurrent = () => isNodePushAttemptCurrent({
nodeId,
lifecycle,
generation: opts?.generation
});
try {
if (!await isAttemptCurrent()) return {
available: false,
throttled: false,
path: "invalidated",
durationMs: 0
};
const result = await runNodeWakeAttempt({
nodeId,
pairingGeneration,
force: opts?.force === true,
throttleMs: nodeInvokePolicy.wakeThrottleMs,
attempt: async (markAttempted) => {
const startedAtMs = performance.now();
let attempted = false;
const withDuration = (attempt) => ({
...attempt,
durationMs: Math.round(performance.now() - startedAtMs)
});
const markWakeAttempted = () => {
attempted = true;
markAttempted();
};
try {
if (!await isAttemptCurrent()) return withDuration({
available: false,
throttled: false,
path: "invalidated"
});
const registration = await loadApnsRegistration(nodeId);
if (!await isAttemptCurrent()) return withDuration({
available: false,
throttled: false,
path: "invalidated"
});
if (!registration) return withDuration({
available: false,
throttled: false,
path: "no-registration"
});
const transport = await resolveNodePushTransport(registration, opts?.cfg);
if (!transport.ok) return withDuration({
available: false,
throttled: false,
path: "no-auth",
apnsReason: transport.error
});
if (!await isAttemptCurrent()) return withDuration({
available: false,
throttled: false,
path: "invalidated"
});
markWakeAttempted();
const wakeResult = await sendApnsBackgroundWake({
...transport.transport,
nodeId,
wakeReason: opts?.wakeReason ?? "node.invoke",
signal: lifecycle,
isCurrent: isAttemptCurrent
});
if (!await isAttemptCurrent()) return withDuration({
available: false,
throttled: false,
path: "invalidated"
});
await clearStaleApnsRegistrationIfNeeded(registration, nodeId, wakeResult);
return withDuration({
available: true,
throttled: false,
path: wakeResult.ok ? "sent" : "send-error",
apnsStatus: wakeResult.status,
apnsReason: wakeResult.reason
});
} catch (err) {
if (!await isAttemptCurrent()) return withDuration({
available: false,
throttled: false,
path: "invalidated"
});
return withDuration({
available: attempted,
throttled: false,
path: "send-error",
apnsReason: formatErrorMessage(err)
});
}
}
});
return await isAttemptCurrent() ? result : {
available: false,
throttled: false,
path: "invalidated",
durationMs: 0
};
} finally {
if (!lifecycleProvided) releaseNodeWakeLifecycle(nodeId, lifecycle);
}
}
async function maybeSendNodeWakeNudge(nodeId, opts) {
const startedAtMs = performance.now();
const withDuration = (attempt) => ({
...attempt,
durationMs: Math.round(performance.now() - startedAtMs)
});
const lifecycleProvided = opts?.lifecycle !== void 0;
const pairingGeneration = opts?.generation?.key;
const lifecycle = opts?.lifecycle ?? captureNodeWakeLifecycle(nodeId, pairingGeneration);
const isAttemptCurrent = () => isNodePushAttemptCurrent({
nodeId,
lifecycle,
generation: opts?.generation
});
try {
if (!await isAttemptCurrent()) return withDuration({
sent: false,
throttled: false,
reason: "invalidated"
});
return await runNodeWakeNudgeAttempt({
nodeId,
pairingGeneration,
throttleMs: nodeInvokePolicy.wakeNudgeThrottleMs,
throttled: () => withDuration({
sent: false,
throttled: true,
reason: "throttled"
}),
attempt: async () => {
const registration = await loadApnsRegistration(nodeId);
if (!await isAttemptCurrent()) return withDuration({
sent: false,
throttled: false,
reason: "invalidated"
});
if (!registration) return withDuration({
sent: false,
throttled: false,
reason: "no-registration"
});
try {
const transport = await resolveNodePushTransport(registration, opts?.cfg);
if (!transport.ok) return withDuration({
sent: false,
throttled: false,
reason: "no-auth",
apnsReason: transport.error
});
if (!await isAttemptCurrent()) return withDuration({
sent: false,
throttled: false,
reason: "invalidated"
});
const result = await sendApnsAlert({
...transport.transport,
nodeId,
title: "OpenClaw needs a quick reopen",
body: "Tap to reopen OpenClaw and restore the node connection.",
signal: lifecycle,
isCurrent: isAttemptCurrent
});
if (!await isAttemptCurrent()) return withDuration({
sent: result.ok,
throttled: false,
reason: "invalidated"
});
await clearStaleApnsRegistrationIfNeeded(registration, nodeId, result);
if (!await isAttemptCurrent()) return withDuration({
sent: result.ok,
throttled: false,
reason: "invalidated"
});
return withDuration({
sent: result.ok,
throttled: false,
reason: result.ok ? "sent" : "apns-not-ok",
apnsStatus: result.status,
apnsReason: result.reason
});
} catch (err) {
if (!await isAttemptCurrent()) return withDuration({
sent: false,
throttled: false,
reason: "invalidated"
});
return withDuration({
sent: false,
throttled: false,
reason: "send-error",
apnsReason: formatErrorMessage(err)
});
}
}
});
} finally {
if (!lifecycleProvided) releaseNodeWakeLifecycle(nodeId, lifecycle);
}
}
async function waitForNodeReconnect(params) {
const timeoutMs = resolveTimerTimeoutMs(params.timeoutMs, NODE_WAKE_RECONNECT_WAIT_MS, 1);
const pollMs = resolveTimerTimeoutMs(params.pollMs, 150, 50);
const deadline = performance.now() + timeoutMs;
for (;;) {
const beforeDeadline = performance.now() < deadline;
if (params.lifecycle && !isNodeWakeLifecycleCurrent(params.nodeId, params.lifecycle, params.pairingGeneration)) return false;
if (resolveDispatchableNodeSession(params.pairingGeneration ? params.context.nodeRegistry.getForPairingGeneration(params.nodeId, params.pairingGeneration) : params.context.nodeRegistry.get(params.nodeId))) return true;
if (!beforeDeadline) return false;
await sleep(Math.min(pollMs, Math.max(0, deadline - performance.now())));
}
}
//#endregion
//#region src/gateway/server-methods/nodes.wake-reconnect.ts
/** One wake stage owns its push deadline and bounded reconnect wait; callers retain pairing custody. */
async function wakeNodeForReconnect(params) {
const { nodeId, context, cfg, generation, lifecycle, force, deadlineAtMs } = params;
const wake = await awaitWithinDeadline(() => maybeWakeNodeWithApns(nodeId, {
...force ? { force: true } : {},
...params.source === "pending" ? { wakeReason: "node.pending" } : {},
cfg,
lifecycle,
generation
}), deadlineAtMs, () => performance.now());
if (wake === ABSOLUTE_DEADLINE_EXPIRED) return wake;
const prefix = params.source === "invoke" ? "node wake" : "node pending wake";
const stage = force ? 2 : 1;
context.logGateway.info(`${prefix} stage=wake${stage} node=${nodeId} req=${params.requestId}${force ? " force=true" : ""} available=${wake.available} throttled=${wake.throttled} path=${wake.path} durationMs=${wake.durationMs} apnsStatus=${wake.apnsStatus ?? -1} apnsReason=${wake.apnsReason ?? "-"}`);
if (deadlineAtMs !== void 0 && performance.now() >= deadlineAtMs) return ABSOLUTE_DEADLINE_EXPIRED;
if (wake.available) {
const waitStartedAtMs = Date.now();
const reconnectWaitMs = force ? NODE_WAKE_RECONNECT_RETRY_WAIT_MS : NODE_WAKE_RECONNECT_WAIT_MS;
const timeoutMs = deadlineAtMs === void 0 ? reconnectWaitMs : Math.min(reconnectWaitMs, Math.max(0, deadlineAtMs - performance.now()));
const reconnected = await waitForNodeReconnect({
nodeId,
context,
timeoutMs,
lifecycle,
pairingGeneration: generation.key
});
const duration = params.source === "invoke" ? ` durationMs=${Math.max(0, Date.now() - waitStartedAtMs)}` : "";
context.logGateway.info(`${prefix} stage=wait${stage} node=${nodeId} req=${params.requestId} reconnected=${reconnected} timeoutMs=${timeoutMs}${duration}`);
}
return wake;
}
//#endregion
export { isNodePairingWorkCurrent as a, waitForNodeReconnect as i, maybeSendNodeWakeNudge as n, resolveDispatchableNodeSession as o, maybeWakeNodeWithApns as r, respondPairingChanged as s, wakeNodeForReconnect as t };