openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
145 lines (144 loc) • 6.22 kB
JavaScript
import { m as isFutureDateTimestampMs } from "./number-coercion-CJQ8TR--.js";
import { f as sweepStaleRunContexts } from "./agent-events-C1B8VhOg.js";
import { r as cleanOldMedia } from "./store-C9M_0A1p.js";
import { r as abortTrackedChatRunById } from "./chat-abort-Cu_ehtms.js";
import { r as HEALTH_REFRESH_INTERVAL_MS, s as TICK_INTERVAL_MS, t as DEDUPE_MAX } from "./server-constants-BGwLM6XN.js";
import { n as pruneStaleControlPlaneBuckets } from "./control-plane-rate-limit-DcNqNwry.js";
import { t as formatError } from "./server-utils-Co-s4JxP.js";
import { s as setBroadcastHealthUpdate } from "./health-state-Cha0Tz7F.js";
//#region src/gateway/server-maintenance.ts
function startGatewayMaintenanceTimers(params) {
setBroadcastHealthUpdate((snap) => {
params.broadcast("health", snap, { stateVersion: {
presence: params.getPresenceVersion(),
health: params.getHealthVersion()
} });
params.nodeSendToAllSubscribed("health", snap);
});
const tickInterval = setInterval(() => {
const payload = { ts: Date.now() };
params.broadcast("tick", payload);
params.nodeSendToAllSubscribed("tick", payload);
}, TICK_INTERVAL_MS);
const healthInterval = setInterval(() => {
params.refreshGatewayHealthSnapshot({ probe: false }).catch((err) => params.logHealth.error(`refresh failed: ${formatError(err)}`));
}, HEALTH_REFRESH_INTERVAL_MS);
params.refreshGatewayHealthSnapshot({ probe: false }).catch((err) => params.logHealth.error(`initial refresh failed: ${formatError(err)}`));
const dedupeCleanup = setInterval(() => {
const AGENT_RUN_SEQ_MAX = 1e4;
const now = Date.now();
const resolveDedupeRunId = (key, entry) => {
if (!key.startsWith("agent:") && !key.startsWith("chat:")) return;
const keyRunId = key.slice(key.indexOf(":") + 1);
if (keyRunId) {
if (params.chatAbortControllers.get(keyRunId)) return keyRunId;
}
const payload = entry.payload;
return payload && typeof payload === "object" && !Array.isArray(payload) ? typeof payload.runId === "string" ? payload.runId.trim() || void 0 : void 0 : void 0;
};
const isPendingAcceptedAgentDedupeKey = (key, dedupeEntry) => {
if (!key.startsWith("agent:")) return false;
const payload = dedupeEntry.payload;
if (!payload || typeof payload !== "object" || Array.isArray(payload)) return false;
if (payload.status !== "accepted") return false;
const expiresAtMs = payload.expiresAtMs;
return isFutureDateTimestampMs(expiresAtMs, { nowMs: now });
};
const isActiveRunDedupeKey = (key, dedupeEntry) => {
if (!key.startsWith("agent:") && !key.startsWith("chat:")) return false;
const runId = resolveDedupeRunId(key, dedupeEntry);
const entry = runId ? params.chatAbortControllers.get(runId) : void 0;
if (!entry) return false;
return key.startsWith("agent:") ? entry.kind === "agent" : entry.kind !== "agent";
};
for (const [k, v] of params.dedupe) {
if (isActiveRunDedupeKey(k, v) || isPendingAcceptedAgentDedupeKey(k, v)) continue;
if (now - v.ts > 3e5) params.dedupe.delete(k);
}
if (params.dedupe.size > 1e3) {
const excess = params.dedupe.size - DEDUPE_MAX;
const oldestKeys = [...params.dedupe.entries()].filter(([key, entry]) => !isActiveRunDedupeKey(key, entry) && !isPendingAcceptedAgentDedupeKey(key, entry)).toSorted(([, left], [, right]) => left.ts - right.ts).slice(0, excess).map(([key]) => key);
for (const key of oldestKeys) params.dedupe.delete(key);
}
if (params.agentRunSeq.size > AGENT_RUN_SEQ_MAX) {
const excess = params.agentRunSeq.size - AGENT_RUN_SEQ_MAX;
let removed = 0;
for (const runId of params.agentRunSeq.keys()) {
params.agentRunSeq.delete(runId);
removed += 1;
if (removed >= excess) break;
}
}
const resolveAgentThrottleRunId = (key) => {
if (key.endsWith(":assistant")) return key.slice(0, -10);
if (key.endsWith(":thinking")) return key.slice(0, -9);
return key;
};
for (const [runId, entry] of params.chatAbortControllers) {
if (isFutureDateTimestampMs(entry.expiresAtMs, { nowMs: now })) continue;
abortTrackedChatRunById(params, {
runId,
sessionKey: entry.sessionKey,
stopReason: "timeout"
});
}
const ABORTED_RUN_TTL_MS = 60 * 6e4;
for (const [runId, abortedAt] of params.chatRunState.abortedRuns) {
if (now - abortedAt <= ABORTED_RUN_TTL_MS) continue;
params.chatRunState.abortedRuns.delete(runId);
params.chatRunState.clearRun(runId);
}
pruneStaleControlPlaneBuckets(now);
for (const [runId, lastSentAt] of params.chatDeltaSentAt) {
if (params.chatRunState.abortedRuns.has(runId)) continue;
if (params.chatAbortControllers.has(runId)) continue;
if (now - lastSentAt <= ABORTED_RUN_TTL_MS) continue;
params.chatRunState.clearRun(runId);
}
for (const [runId, lastUpdatedAt] of params.chatRunState.bufferUpdatedAt) {
if (params.chatRunState.abortedRuns.has(runId)) continue;
if (params.chatAbortControllers.has(runId)) continue;
if (now - lastUpdatedAt <= ABORTED_RUN_TTL_MS) continue;
params.chatRunState.clearRun(runId);
}
for (const [key, lastSentAt] of params.chatRunState.agentDeltaSentAt) {
const runId = resolveAgentThrottleRunId(key);
if (params.chatRunState.abortedRuns.has(runId)) continue;
if (params.chatAbortControllers.has(runId)) continue;
if (now - lastSentAt <= ABORTED_RUN_TTL_MS) continue;
params.chatRunState.clearRun(runId);
}
sweepStaleRunContexts();
}, 6e4);
if (typeof params.mediaCleanupTtlMs !== "number") return {
tickInterval,
healthInterval,
dedupeCleanup,
mediaCleanup: null
};
let mediaCleanupInFlight = null;
const runMediaCleanup = () => {
if (mediaCleanupInFlight) return mediaCleanupInFlight;
mediaCleanupInFlight = cleanOldMedia(params.mediaCleanupTtlMs, {
recursive: true,
pruneEmptyDirs: true
}).catch((err) => {
params.logHealth.error(`media cleanup failed: ${formatError(err)}`);
}).finally(() => {
mediaCleanupInFlight = null;
});
return mediaCleanupInFlight;
};
const mediaCleanup = setInterval(() => {
runMediaCleanup();
}, 60 * 6e4);
runMediaCleanup();
return {
tickInterval,
healthInterval,
dedupeCleanup,
mediaCleanup
};
}
//#endregion
export { startGatewayMaintenanceTimers };