UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

497 lines (496 loc) • 20.1 kB
import { t as pruneMapToMaxSize } from "./map-size-BlrH_CnE.mjs"; import { l as normalizeToolPolicyName } from "./tool-policy-shared-DWHChhWM.mjs"; import "./tool-policy-BKvvmcQR.mjs"; import { N as getAgentRunContextOwnerStatus, S as releaseAgentRunContext, l as getAgentRunContext, r as claimAgentRunContext, u as getAgentRunContextOwnership } from "./agent-run-registry-BaxMDLZF.mjs"; import { a as emitAgentEventForOwner, l as getAgentEventLifecycleGeneration, s as emitAgentEventIfCurrent } from "./agent-events-GvNTkbe3.mjs"; import { s as isDefinitiveRunLifecycle } from "./agent-run-terminal-outcome-Ccvy5spR.mjs"; import "./agent-run-terminal-outcome-BTCs8JFs.mjs"; import { a as captureWorkerReplyMedia, s as captureWorkerTurnFinishing } from "./placement-turn-claim-events-BCSz3C5d.mjs"; import { r as projectAgentToolActivity } from "./agent-activity-events-CYq4KNEY.mjs"; import { d as sanitizeToolResult, n as capLiveExecResult, u as sanitizeToolArgs } from "./embedded-agent-tool-results-C0yTOgai.mjs"; import { t as createTrajectoryRuntimeRecorder } from "./runtime-Cffvdo3D.mjs"; import { t as captureWorkerTurnLiveEventOwner } from "./worker-turn-run-owner-Bw3dxPqh.mjs"; 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) { const target = params.source.sessionTarget; return createTrajectoryRuntimeRecorder({ runId: params.runId, sessionId: target.sessionId, sessionKey: target.sessionKey, sessionTarget: target, assertCommitAllowed: params.source.receiptAuthority }); } 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" || event.payload.phase === "finishing") recorder.recordEvent(`model.${event.payload.phase}`, 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, ...event.payload.phase === "error" ? { promptError: event.payload.error } : {} }); recorder.recordEvent("session.ended", { ...data, status: interrupted ? "interrupted" : failed ? "error" : "success" }); } else return; } else return; return recorder.flush().catch(() => void 0); } //#endregion //#region src/gateway/worker-environments/live-event-window.ts function releaseWorkerLiveRun(window, runId) { const owned = window.activeRuns.get(runId); if (!owned) return; window.activeRuns.delete(runId); releaseAgentRunContext(runId, owned.claimId); } function fenceReleasedWorkerLiveRun(window, runId) { if (!window.terminalRuns.has(runId)) window.terminalRuns.set(runId, window.ackedSeq); releaseWorkerLiveRun(window, runId); } function hasReachableBufferedTerminal(window, admittedRunId, countedRunIds, windowSize) { 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; } function rotateWorkerLiveEventCredential(window, rotation) { if (!rotation.credentialHash || !rotation.environmentId || !rotation.previousCredentialHash || !rotation.sessionId || !Number.isSafeInteger(rotation.runEpoch) || rotation.runEpoch < 0) return false; 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; } //#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 fencedEnvironmentEpochs = /* @__PURE__ */ new Map(); const windows = /* @__PURE__ */ new Map(); 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)); const rotateCredential = (rotation) => rotateWorkerLiveEventCredential(windows.get(rotation.sessionId), rotation); const clearWindow = (window) => { windows.delete(window.sessionId); for (const runId of window.activeRuns.keys()) releaseWorkerLiveRun(window, runId); window.pending.clear(); window.pendingBytes = 0; window.terminalRuns.clear(); }; 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) => { let window = windows.get(sessionId); if (window && (window.environmentId !== params.identity.environmentId || window.runEpoch !== params.request.runEpoch)) { if (params.request.runEpoch <= window.runEpoch) return { ok: false, details: { reason: "epoch-mismatch" } }; clearWindow(window); window = void 0; } if (window) { if (params.identity.credentialHash !== window.credentialHash) return { ok: false, details: { reason: "epoch-mismatch" } }; } else { const ackedSeq = params.readAckedSeq(); if (params.request.lastAckedSeq > ackedSeq) return resyncRequired(ackedSeq); if (windows.size >= maxSessions) { let evicted = false; for (const candidate of windows.values()) if (!(candidate.activeApplications > 0 || [...candidate.activeRuns.entries()].some(([runId, owned]) => getAgentRunContextOwnerStatus(runId, owned.claimId, owned.lifecycleGeneration) === "active"))) { clearWindow(candidate); evicted = true; break; } if (!evicted) return capacityExceeded(); } window = { activeApplications: 0, activeRuns: /* @__PURE__ */ new Map(), ackedSeq, credentialHash: params.identity.credentialHash, environmentId: params.identity.environmentId, pending: /* @__PURE__ */ new Map(), pendingBytes: 0, trajectoryWrites: /* @__PURE__ */ new Set(), runEpoch: params.request.runEpoch, sessionId, source: params.source, terminalRuns: /* @__PURE__ */ new Map() }; windows.set(sessionId, window); } if (window.source !== params.source) { if (window.activeRuns.size > 0 || window.pending.size > 0 || window.activeApplications > 0) return invalidEvent(); window.source = params.source; } 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") fenceReleasedWorkerLiveRun(window, runId); } }; 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.source.sessionTarget.sessionKey || context.agentId !== window.source.sessionTarget.agentId || context.lifecycleGeneration !== owned.lifecycleGeneration || context.isControlUiVisible !== owned.controlUiVisible) { fenceReleasedWorkerLiveRun(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, windowSize))) return capacityExceeded(); const lifecycleGeneration = getAgentEventLifecycleGeneration(); const existingContext = getAgentRunContext(runId); const controlUiVisible = existingContext?.isControlUiVisible ?? false; if (existingContext && (existingContext.sessionId !== window.sessionId || existingContext.sessionKey !== window.source.sessionTarget.sessionKey || existingContext.agentId !== void 0 && existingContext.agentId !== window.source.sessionTarget.agentId || existingContext.lifecycleGeneration !== lifecycleGeneration)) return invalidEvent(); const hasExistingTrackedOwner = getAgentRunContextOwnership(runId)?.lifecycleGeneration === lifecycleGeneration; const emissionMode = existingContext ? "shared" : "exclusive"; const claimId = claimAgentRunContext(runId, { ...window.source.sessionTarget.agentId ? { agentId: window.source.sessionTarget.agentId } : {}, isControlUiVisible: controlUiVisible, lifecycleGeneration, projectSessionActive: true, sessionId: window.sessionId, sessionKey: window.source.sessionTarget.sessionKey }, { exclusive: existingContext === void 0, onClearRequested: (clearedClaimId) => { if (window.activeRuns.get(runId)?.claimId === clearedClaimId) fenceReleasedWorkerLiveRun(window, runId); }, ownsContext: !hasExistingTrackedOwner, trackOwner: true }); if (!claimId) return invalidEvent(); const claimed = { claimId, controlUiVisible, emissionMode, lifecycleGeneration, trajectoryRecorder: createWorkerLiveTrajectoryRecorder({ runId, source: window.source }), toolArgsByCallId: /* @__PURE__ */ new Map() }; window.activeRuns.set(runId, claimed); return claimed; }; const publish = async (window, publication, allowBufferedTerminalCapacity) => { const { request, recordApplied, runOwner, source, prepareReplyMedia } = publication; if (runOwner?.isCancelled()) { if (!runOwner.isCancelledFinishing(request)) return invalidEvent(); window.terminalRuns.set(request.runId, request.seq); releaseWorkerLiveRun(window, request.runId); return; } try { source.receiptAuthority(); } catch { return invalidEvent(); } 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 (request.event.kind === "assistant" && request.event.payload.mediaUrls?.length && prepareReplyMedia) { const payload = await prepareReplyMedia(request.event.payload); event.data = { ...event.data, ...payload }; if (payload.text !== request.event.payload.text) { event.data.replace = true; event.data.delta = payload.text ?? ""; } } let activity; if (request.event.kind === "tool") { const tool = request.event.payload; if (tool.phase === "start") owned.toolArgsByCallId.set(tool.toolCallId, tool.args); activity = projectAgentToolActivity({ ...tool, name: normalizeToolPolicyName(tool.name), args: owned.toolArgsByCallId.get(tool.toolCallId) }); if (tool.phase === "result") owned.toolArgsByCallId.delete(tool.toolCallId); } const itemEvent = activity ? { runId: request.runId, stream: "item", data: activity } : void 0; const emissions = itemEvent ? activity?.phase === "start" ? [itemEvent, event] : [event, itemEvent] : [event]; const ownsPublication = () => { try { source.receiptAuthority(); } catch { return false; } return window.activeRuns.get(request.runId) === owned && getAgentRunContextOwnerStatus(request.runId, owned.claimId, owned.lifecycleGeneration) === "active"; }; for (const emission of emissions) { if (!ownsPublication()) return invalidEvent(); if (owned.emissionMode === "shared") { if (!emitAgentEventIfCurrent(emission)) { if (definitiveTerminal) window.terminalRuns.delete(request.runId); return invalidEvent(); } } else emitAgentEventForOwner(emission, owned.claimId); } if (activity && !ownsPublication()) return invalidEvent(); const write = recordWorkerLiveTrajectoryEvent(owned.trajectoryRecorder, request.event); if (write) { window.trajectoryWrites.add(write); write.then(() => window.trajectoryWrites.delete(write)); } recordApplied?.(request.event); }; const drain = async (window, first, firstPending) => { let buffered = firstPending; let publishedPrefix = false; while (true) { const publication = buffered ?? first; const { request } = publication; const failed = await publish(window, publication, buffered !== void 0); if (failed?.details.reason === "capacity-exceeded" && buffered) return { ok: true, result: { ackedSeq: window.ackedSeq } }; if (buffered && window.pending.delete(request.seq)) window.pendingBytes -= buffered.sizeBytes; if (failed) { if (failed.details.reason === "capacity-exceeded" && !publishedPrefix) { clearWindow(window); return failed; } return publishedPrefix ? { ok: true, result: { ackedSeq: window.ackedSeq } } : failed; } 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); buffered = window.pending.get(window.ackedSeq + 1); if (!buffered) break; } return { ok: true, result: { ackedSeq: window.ackedSeq } }; }; const applyToWindow = async (window, params, runOwner) => { if (params.request.seq <= window.ackedSeq) return { ok: true, result: { ackedSeq: window.ackedSeq } }; if (params.request.lastAckedSeq > window.ackedSeq) return resyncWindow(window); const recordFinishing = captureWorkerTurnFinishing(params.identity, params.request); const recordApplied = (event) => { runOwner?.record(event); recordFinishing?.(); }; const publication = { request: params.request, recordApplied, runOwner, source: params.source, prepareReplyMedia: captureWorkerReplyMedia(params.identity) }; const { seq } = params.request; const expectedSeq = window.ackedSeq + 1; if (seq > window.ackedSeq + windowSize) return resyncWindow(window); if (seq === expectedSeq) return drain(window, publication, window.pending.get(seq)); 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, { ...publication, sizeBytes }); window.pendingBytes += sizeBytes; return { ok: true, result: { ackedSeq: window.ackedSeq } }; }; const apply = async (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 runOwner = captureWorkerTurnLiveEventOwner(params.identity); window.activeApplications += 1; try { let result; try { result = await applyToWindow(window, params, runOwner); } finally { await Promise.all(window.trajectoryWrites); } if (result.ok) { if (!runOwner?.isCancelledFinishing(params.request)) try { params.source.receiptAuthority(); } catch { return invalidEvent(); } if (windows.get(sessionId) !== window) return { ok: false, details: { reason: "session-not-attached" } }; if (window.credentialHash !== params.identity.credentialHash) return { ok: false, details: { reason: "epoch-mismatch" } }; } return result; } finally { window.activeApplications -= 1; } }; const clearEnvironment = (environmentId, ownerEpoch) => { let fencedEpoch = Math.max(fencedEnvironmentEpochs.get(environmentId) ?? -1, ownerEpoch); 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 = () => { for (const window of windows.values()) clearWindow(window); fencedEnvironmentEpochs.clear(); }; return { apply, clear, clearEnvironment, rotateCredential }; } //#endregion export { createWorkerLiveEventReceiver };