openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
524 lines (523 loc) • 22.7 kB
JavaScript
import { t as pruneMapToMaxSize } from "./map-size-CNcWiFKu.js";
import { c as normalizeToolPolicyName } from "./tool-policy-shared-DIyS0iQC.js";
import "./tool-policy-Wi0C45cX.js";
import { a as emitAgentEventForOwner, c as getAgentEventLifecycleGeneration, o as emitAgentEventIfCurrent } from "./agent-events-CoxiItUi.js";
import { c as getAgentRunContext, h as registerAgentRunContext, l as getAgentRunContextOwnerStatus, r as claimAgentRunContext, u as getAgentRunContextOwnership, v as releaseAgentRunContext } from "./agent-run-registry-CKYKdfNd.js";
import "./session-accessor-YsytfDtG.js";
import { p as onSessionIdentityMutation } from "./session-history-eviction-C4srftLJ.js";
import { c as isDefinitiveRunLifecycle } from "./agent-run-terminal-outcome-CigeY75d.js";
import { c as sanitizeToolResult, n as capLiveExecResult, s as sanitizeToolArgs } from "./embedded-agent-tool-results-ZMp1V0-a.js";
import { t as createTrajectoryRuntimeRecorder } from "./runtime-DKseXJ77.js";
import { t as resolveWorkerSessionTarget } from "./session-target-BDNR3YFZ.js";
import { t as captureWorkerTurnDiagnosticRecorder } from "./worker-turn-run-owner-D4SKwZga.js";
import { Buffer } from "node:buffer";
//#region src/gateway/worker-environments/live-event-projection.ts
function prepareWorkerLiveEventData(event) {
const payload = structuredClone(event.payload);
if (event.kind !== "tool") return payload;
const toolName = normalizeToolPolicyName(event.payload.name);
payload.name = toolName;
if (event.payload.phase === "start") payload.args = sanitizeToolArgs(event.payload.args);
else if (event.payload.phase === "update") {
const partialResult = sanitizeToolResult(event.payload.partialResult);
payload.partialResult = toolName === "exec" ? capLiveExecResult(partialResult) : partialResult;
} else {
const result = sanitizeToolResult(event.payload.result);
payload.result = toolName === "exec" ? capLiveExecResult(result) : result;
}
return payload;
}
function isDefinitiveWorkerTerminalEvent(event) {
return event.kind === "lifecycle" && isDefinitiveRunLifecycle({
phase: event.payload.phase,
data: event.payload
});
}
function createWorkerLiveTrajectoryRecorder(params) {
return createTrajectoryRuntimeRecorder({
runId: params.runId,
sessionId: params.target.sessionId,
sessionKey: params.target.sessionKey,
sessionTarget: {
agentId: params.target.agentId ?? "main",
sessionId: params.target.sessionId,
sessionKey: params.target.sessionKey,
storePath: params.target.storePath
}
});
}
function recordWorkerLiveTrajectoryEvent(recorder, event) {
if (!recorder) return;
if (event.kind === "tool") {
if (event.payload.phase === "start") recorder.recordEvent("tool.call", prepareWorkerLiveEventData(event));
else if (event.payload.phase === "result") recorder.recordEvent("tool.result", {
...prepareWorkerLiveEventData(event),
success: !event.payload.isError
});
else return;
} else if (event.kind === "approval") recorder.recordEvent(`approval.${event.payload.phase}`, prepareWorkerLiveEventData(event));
else if (event.kind === "lifecycle") {
if (event.payload.phase === "start") recorder.recordEvent("session.started", {
...prepareWorkerLiveEventData(event),
backend: "cloud-worker"
});
else if (event.payload.phase === "fallback_step") recorder.recordEvent("model.fallback_step", prepareWorkerLiveEventData(event));
else if (event.payload.phase === "finishing") recorder.recordEvent("model.finishing", prepareWorkerLiveEventData(event));
else if ((event.payload.phase === "end" || event.payload.phase === "error") && isDefinitiveWorkerTerminalEvent(event)) {
const data = prepareWorkerLiveEventData(event);
const failed = event.payload.phase === "error";
const interrupted = event.payload.aborted === true;
recorder.recordEvent("model.completed", {
...data,
...failed ? { promptError: event.payload.error } : {}
});
recorder.recordEvent("session.ended", {
...data,
status: interrupted ? "interrupted" : failed ? "error" : "success"
});
} else return;
} else return;
recorder.flush().catch(() => void 0);
}
//#endregion
//#region src/gateway/worker-environments/live-event-session-binding.ts
function isValidLiveSessionBinding(binding) {
return binding.environmentId.length > 0 && binding.sessionId.length > 0 && Number.isSafeInteger(binding.runEpoch) && binding.runEpoch >= 0;
}
function resolveLiveEventTarget(config, sessionId) {
const target = resolveWorkerSessionTarget(config, sessionId);
if (!target) return;
return {
...target.agentId ? { agentId: target.agentId } : {},
sessionId: target.sessionId,
sessionKey: target.sessionKey,
storePath: target.storePath
};
}
function prepareBoundLiveSession(config, binding) {
if (!isValidLiveSessionBinding(binding)) return;
const target = resolveLiveEventTarget(config, binding.sessionId);
return target ? {
...binding,
target
} : void 0;
}
function prepareBoundLiveSessionSafely(config, binding) {
try {
return prepareBoundLiveSession(config, binding);
} catch {
return;
}
}
function matchesSessionIdentityMutation(binding, prepared, mutation) {
return ("current" in mutation ? [mutation.previous, mutation.current] : [mutation.previous]).some((target) => target.sessionId === binding.sessionId || (prepared ? target.sessionKeys.includes(prepared.target.sessionKey) : false));
}
//#endregion
//#region src/gateway/worker-environments/live-events.ts
const DEFAULT_WINDOW_SIZE = 128;
const DEFAULT_MAX_PENDING_BYTES = 524288;
const DEFAULT_MAX_SESSIONS = 128;
const DEFAULT_MAX_ACTIVE_RUNS = 32;
const MAX_FENCED_ENVIRONMENTS = 4096;
function invalidEvent() {
return {
ok: false,
details: { reason: "invalid-event" }
};
}
function capacityExceeded() {
return {
ok: false,
details: { reason: "capacity-exceeded" }
};
}
function createWorkerLiveEventReceiver(options) {
const boundSessions = /* @__PURE__ */ new Map();
const sessionBindings = /* @__PURE__ */ new Map();
const fencedEnvironmentEpochs = /* @__PURE__ */ new Map();
const staleSessions = /* @__PURE__ */ new Set();
const windows = /* @__PURE__ */ new Map();
const startupBindingOwners = new Map(options.startupBindings.filter(isValidLiveSessionBinding).map(({ environmentId, runEpoch }) => [environmentId, runEpoch]));
const startupOwners = new Map([...options.startupOwners].filter(([environmentId, ownerEpoch]) => environmentId.length > 0 && Number.isSafeInteger(ownerEpoch) && ownerEpoch >= 0 && startupBindingOwners.get(environmentId) === ownerEpoch));
const windowSize = Math.max(1, Math.floor(options.windowSize ?? DEFAULT_WINDOW_SIZE));
const maxActiveRuns = Math.max(1, Math.floor(options.maxActiveRuns ?? DEFAULT_MAX_ACTIVE_RUNS));
const maxPendingBytes = Math.max(1, Math.floor(options.maxPendingBytes ?? DEFAULT_MAX_PENDING_BYTES));
const maxSessions = Math.max(1, Math.floor(options.maxSessions ?? DEFAULT_MAX_SESSIONS));
let committedConfig = options.getConfig();
for (const binding of options.startupBindings) {
if (!isValidLiveSessionBinding(binding)) continue;
const existing = sessionBindings.get(binding.sessionId);
if (!existing || binding.runEpoch > existing.runEpoch || binding.runEpoch === existing.runEpoch && binding.environmentId === existing.environmentId) sessionBindings.set(binding.sessionId, { ...binding });
}
for (const binding of sessionBindings.values()) {
const prepared = prepareBoundLiveSessionSafely(committedConfig, binding);
if (prepared) boundSessions.set(binding.sessionId, prepared);
else staleSessions.add(binding.sessionId);
}
const rotateCredential = (rotation) => {
if (!rotation.credentialHash || !rotation.environmentId || !rotation.previousCredentialHash || !rotation.sessionId || !Number.isSafeInteger(rotation.runEpoch) || rotation.runEpoch < 0) return false;
const window = windows.get(rotation.sessionId);
if (window?.credentialHash === rotation.previousCredentialHash && window.environmentId === rotation.environmentId && window.runEpoch === rotation.runEpoch) {
if (rotation.newProcessTurn === true) {
if (!Number.isSafeInteger(rotation.ackedSeq) || rotation.ackedSeq < 0 || rotation.ackedSeq > window.ackedSeq) return false;
for (const [runId, owned] of window.activeRuns) releaseAgentRunContext(runId, owned.claimId);
window.activeRuns.clear();
window.ackedSeq = rotation.ackedSeq;
window.pending.clear();
window.pendingBytes = 0;
window.terminalRuns.clear();
}
window.credentialHash = rotation.credentialHash;
return true;
}
return false;
};
const releaseRun = (window, runId) => {
const owned = window.activeRuns.get(runId);
if (!owned) return;
window.activeRuns.delete(runId);
releaseAgentRunContext(runId, owned.claimId);
};
const fenceReleasedRun = (window, runId) => {
if (!window.terminalRuns.has(runId)) window.terminalRuns.set(runId, window.ackedSeq);
releaseRun(window, runId);
};
const clearWindow = (window) => {
windows.delete(window.sessionId);
for (const runId of window.activeRuns.keys()) releaseRun(window, runId);
window.pending.clear();
window.pendingBytes = 0;
window.terminalRuns.clear();
};
const bindSessionWithConfig = (binding, config) => {
if (!isValidLiveSessionBinding(binding)) return false;
const existing = sessionBindings.get(binding.sessionId);
if (existing && binding.runEpoch < existing.runEpoch || existing && binding.runEpoch === existing.runEpoch && binding.environmentId !== existing.environmentId) return false;
const prepared = prepareBoundLiveSessionSafely(config, binding);
if (!prepared) {
if (existing?.environmentId === binding.environmentId && existing.runEpoch === binding.runEpoch) staleSessions.add(binding.sessionId);
return false;
}
const window = windows.get(binding.sessionId);
if (window && (window.environmentId !== binding.environmentId || window.runEpoch !== binding.runEpoch)) clearWindow(window);
sessionBindings.set(binding.sessionId, { ...binding });
boundSessions.set(binding.sessionId, prepared);
staleSessions.delete(binding.sessionId);
const retainedWindow = windows.get(binding.sessionId);
if (retainedWindow) {
retainedWindow.target = prepared.target;
for (const [runId, owned] of retainedWindow.activeRuns) if (getAgentRunContextOwnerStatus(runId, owned.claimId, owned.lifecycleGeneration) === "active") registerAgentRunContext(runId, {
...prepared.target.agentId ? { agentId: prepared.target.agentId } : {},
isControlUiVisible: owned.controlUiVisible,
lifecycleGeneration: owned.lifecycleGeneration,
projectSessionActive: true,
sessionId: binding.sessionId,
sessionKey: prepared.target.sessionKey
}, owned.claimId);
}
return true;
};
const bindSession = (binding) => bindSessionWithConfig(binding, committedConfig);
const rebindAll = (config) => {
committedConfig = config;
for (const binding of sessionBindings.values()) bindSessionWithConfig(binding, committedConfig);
};
let unsubscribeSessionIdentityMutation;
const start = () => {
if (unsubscribeSessionIdentityMutation) return;
unsubscribeSessionIdentityMutation = onSessionIdentityMutation((mutation) => {
for (const binding of sessionBindings.values()) if (matchesSessionIdentityMutation(binding, boundSessions.get(binding.sessionId), mutation)) {
if (!bindSessionWithConfig(binding, committedConfig)) {
startupOwners.delete(binding.environmentId);
const window = windows.get(binding.sessionId);
if (window) clearWindow(window);
}
}
});
for (const binding of sessionBindings.values()) if (!bindSessionWithConfig(binding, committedConfig)) startupOwners.delete(binding.environmentId);
};
const resyncRequired = (ackedSeq) => ({
ok: false,
details: {
reason: "resync-required",
ackedSeq,
expectedSeq: ackedSeq + 1
}
});
const resyncWindow = (window) => {
window.pending.clear();
window.pendingBytes = 0;
return resyncRequired(window.ackedSeq);
};
const resolveOrCreateWindow = (sessionId, params) => {
const binding = boundSessions.get(sessionId);
if (!binding) return {
ok: false,
details: { reason: "session-not-attached" }
};
if (binding.environmentId !== params.identity.environmentId || binding.runEpoch !== params.request.runEpoch) return {
ok: false,
details: { reason: "epoch-mismatch" }
};
if (staleSessions.has(sessionId)) return {
ok: false,
details: { reason: "session-not-attached" }
};
let window = windows.get(sessionId);
if (window) {
if (params.request.runEpoch !== window.runEpoch || params.identity.credentialHash !== window.credentialHash || params.identity.environmentId !== window.environmentId) return {
ok: false,
details: { reason: "epoch-mismatch" }
};
} else {
if (startupOwners.get(params.identity.environmentId) !== params.request.runEpoch && params.request.lastAckedSeq !== 0) return resyncRequired(0);
if (windows.size >= maxSessions) {
let evicted = false;
for (const candidate of windows.values()) if (![...candidate.activeRuns.entries()].some(([runId, owned]) => getAgentRunContextOwnerStatus(runId, owned.claimId, owned.lifecycleGeneration) === "active")) {
clearWindow(candidate);
evicted = true;
break;
}
if (!evicted) return capacityExceeded();
}
window = {
activeRuns: /* @__PURE__ */ new Map(),
ackedSeq: params.request.lastAckedSeq,
credentialHash: params.identity.credentialHash,
environmentId: params.identity.environmentId,
pending: /* @__PURE__ */ new Map(),
pendingBytes: 0,
runEpoch: params.request.runEpoch,
sessionId,
target: binding.target,
terminalRuns: /* @__PURE__ */ new Map()
};
windows.set(sessionId, window);
startupOwners.delete(params.identity.environmentId);
}
if (params.request.seq <= window.ackedSeq) return {
ok: true,
result: { ackedSeq: window.ackedSeq }
};
if (params.request.lastAckedSeq > window.ackedSeq) return resyncWindow(window);
return window;
};
const pruneReleasedRuns = (window) => {
for (const [runId, owned] of window.activeRuns) {
const ownerStatus = getAgentRunContextOwnerStatus(runId, owned.claimId, owned.lifecycleGeneration);
if (ownerStatus === void 0) {
clearWindow(window);
return resyncRequired(0);
}
if (ownerStatus !== "active") fenceReleasedRun(window, runId);
}
};
const hasReachableBufferedTerminal = (window, admittedRunId, countedRunIds) => {
for (let seq = window.ackedSeq + 2; seq <= window.ackedSeq + windowSize; seq += 1) {
const pending = window.pending.get(seq);
if (!pending) return false;
const pendingRunId = pending.request.runId;
if (countedRunIds.has(pendingRunId)) {
if (isDefinitiveWorkerTerminalEvent(pending.request.event)) return true;
continue;
}
if (pendingRunId !== admittedRunId) return false;
}
return false;
};
const claimRun = (window, runId, allowBufferedTerminalCapacity) => {
if (window.terminalRuns.has(runId)) return invalidEvent();
const owned = window.activeRuns.get(runId);
if (owned) {
const context = getAgentRunContext(runId);
const ownerStatus = getAgentRunContextOwnerStatus(runId, owned.claimId, owned.lifecycleGeneration);
if (ownerStatus === void 0) {
clearWindow(window);
return resyncRequired(0);
}
if (ownerStatus !== "active" || context?.sessionId !== window.sessionId || context.sessionKey !== window.target.sessionKey || context.agentId !== window.target.agentId || context.lifecycleGeneration !== owned.lifecycleGeneration || context.isControlUiVisible !== owned.controlUiVisible) {
fenceReleasedRun(window, runId);
return invalidEvent();
}
return owned;
}
const pruneFailure = pruneReleasedRuns(window);
if (pruneFailure) return pruneFailure;
const countedRunIds = /* @__PURE__ */ new Set();
for (const activeRunId of window.activeRuns.keys()) if (!window.terminalRuns.has(activeRunId)) countedRunIds.add(activeRunId);
if (countedRunIds.size >= maxActiveRuns && !(allowBufferedTerminalCapacity && hasReachableBufferedTerminal(window, runId, countedRunIds))) return capacityExceeded();
const lifecycleGeneration = getAgentEventLifecycleGeneration();
const existingContext = getAgentRunContext(runId);
const controlUiVisible = existingContext?.isControlUiVisible ?? false;
if (existingContext && (existingContext.sessionId !== window.sessionId || existingContext.sessionKey !== window.target.sessionKey || existingContext.agentId !== void 0 && existingContext.agentId !== window.target.agentId || existingContext.lifecycleGeneration !== lifecycleGeneration)) return invalidEvent();
const hasExistingTrackedOwner = getAgentRunContextOwnership(runId)?.lifecycleGeneration === lifecycleGeneration;
const emissionMode = existingContext ? "shared" : "exclusive";
const claimId = claimAgentRunContext(runId, {
...window.target.agentId ? { agentId: window.target.agentId } : {},
isControlUiVisible: controlUiVisible,
lifecycleGeneration,
projectSessionActive: true,
sessionId: window.sessionId,
sessionKey: window.target.sessionKey
}, {
exclusive: existingContext === void 0,
onClearRequested: (clearedClaimId) => {
if (window.activeRuns.get(runId)?.claimId === clearedClaimId) fenceReleasedRun(window, runId);
},
ownsContext: !hasExistingTrackedOwner,
trackOwner: true
});
if (!claimId) return invalidEvent();
const claimed = {
claimId,
controlUiVisible,
emissionMode,
lifecycleGeneration,
trajectoryRecorder: createWorkerLiveTrajectoryRecorder({
runId,
target: window.target
})
};
window.activeRuns.set(runId, claimed);
return claimed;
};
const publish = (window, request, allowBufferedTerminalCapacity, recordDiagnostic) => {
const owned = claimRun(window, request.runId, allowBufferedTerminalCapacity);
if ("ok" in owned) return owned;
const definitiveTerminal = isDefinitiveWorkerTerminalEvent(request.event);
if (definitiveTerminal) window.terminalRuns.set(request.runId, request.seq);
const event = {
runId: request.runId,
stream: request.event.kind,
data: prepareWorkerLiveEventData(request.event)
};
if (owned.emissionMode === "shared") {
if (!emitAgentEventIfCurrent(event)) {
if (definitiveTerminal) window.terminalRuns.delete(request.runId);
return invalidEvent();
}
} else emitAgentEventForOwner(event, owned.claimId);
recordWorkerLiveTrajectoryEvent(owned.trajectoryRecorder, request.event);
recordDiagnostic?.(request.event);
};
const drain = (window, first, firstDiagnostic, firstPending) => {
let request = first;
let buffered = firstPending;
let recordDiagnostic = firstPending ? firstPending.recordDiagnostic : firstDiagnostic;
let publishedPrefix = false;
while (request) {
const failed = publish(window, request, buffered !== void 0, recordDiagnostic);
if (failed) {
if (failed.details.reason === "capacity-exceeded" && buffered) return {
ok: true,
result: { ackedSeq: window.ackedSeq }
};
if (buffered) {
if (window.pending.delete(request.seq)) window.pendingBytes -= buffered.sizeBytes;
}
if (failed.details.reason === "capacity-exceeded" && !publishedPrefix) {
clearWindow(window);
return failed;
}
return publishedPrefix ? {
ok: true,
result: { ackedSeq: window.ackedSeq }
} : failed;
}
if (buffered) {
if (window.pending.delete(request.seq)) window.pendingBytes -= buffered.sizeBytes;
}
window.ackedSeq = request.seq;
publishedPrefix = true;
const oldestRetainedSeq = window.ackedSeq - windowSize;
for (const [runId, terminalSeq] of window.terminalRuns) if (!window.activeRuns.has(runId) && terminalSeq <= oldestRetainedSeq) window.terminalRuns.delete(runId);
const next = window.pending.get(window.ackedSeq + 1);
if (!next) break;
request = next.request;
buffered = next;
recordDiagnostic = next.recordDiagnostic;
}
return {
ok: true,
result: { ackedSeq: window.ackedSeq }
};
};
const apply = (params) => {
if (!params.identity.sessionId) return {
ok: false,
details: { reason: "session-not-attached" }
};
if (params.request.runEpoch !== params.identity.ownerEpoch) return {
ok: false,
details: { reason: "epoch-mismatch" }
};
if (params.request.runEpoch <= (fencedEnvironmentEpochs.get(params.identity.environmentId) ?? -1)) return invalidEvent();
const sessionId = params.identity.sessionId;
const window = resolveOrCreateWindow(sessionId, params);
if ("ok" in window) return window;
const recordDiagnostic = captureWorkerTurnDiagnosticRecorder(params.identity);
const { seq } = params.request;
const expectedSeq = window.ackedSeq + 1;
if (seq > window.ackedSeq + windowSize) return resyncWindow(window);
if (seq === expectedSeq) {
const pending = window.pending.get(seq);
return drain(window, pending?.request ?? params.request, recordDiagnostic, pending);
}
if (window.pending.has(seq)) return {
ok: true,
result: { ackedSeq: window.ackedSeq }
};
const sizeBytes = Buffer.byteLength(JSON.stringify(params.request.event), "utf8");
if (window.pendingBytes + sizeBytes > maxPendingBytes) return resyncWindow(window);
window.pending.set(seq, {
request: params.request,
sizeBytes,
recordDiagnostic
});
window.pendingBytes += sizeBytes;
return {
ok: true,
result: { ackedSeq: window.ackedSeq }
};
};
const clearEnvironment = (environmentId) => {
startupOwners.delete(environmentId);
let fencedEpoch = fencedEnvironmentEpochs.get(environmentId) ?? -1;
for (const [sessionId, binding] of sessionBindings) if (binding.environmentId === environmentId) {
fencedEpoch = Math.max(fencedEpoch, binding.runEpoch);
sessionBindings.delete(sessionId);
boundSessions.delete(sessionId);
staleSessions.delete(sessionId);
}
for (const window of windows.values()) if (window.environmentId === environmentId) {
fencedEpoch = Math.max(fencedEpoch, window.runEpoch);
clearWindow(window);
}
if (fencedEpoch >= 0) {
fencedEnvironmentEpochs.delete(environmentId);
fencedEnvironmentEpochs.set(environmentId, fencedEpoch);
pruneMapToMaxSize(fencedEnvironmentEpochs, MAX_FENCED_ENVIRONMENTS);
}
};
const clear = () => {
unsubscribeSessionIdentityMutation?.();
unsubscribeSessionIdentityMutation = void 0;
for (const window of windows.values()) clearWindow(window);
boundSessions.clear();
sessionBindings.clear();
fencedEnvironmentEpochs.clear();
staleSessions.clear();
startupOwners.clear();
};
return {
apply,
bindSession,
clear,
clearEnvironment,
rebindAll,
rotateCredential,
start
};
}
//#endregion
export { createWorkerLiveEventReceiver };