UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

367 lines (366 loc) • 18.7 kB
import { a as resolveGlobalSingleton } from "./global-singleton-0m-JziJW.mjs"; import { c as isRecord } from "./record-coerce-TIsquoVR.mjs"; import { t as ensureSqliteLibrarySelected } from "./bun-sqlite-library-CmVami_Y.mjs"; import { a as createSqliteLifecycleAggregateError } from "./sqlite-lifecycle-errors-DuOygjcn.mjs"; import { _ as registerOpenClawStateDatabaseAsyncResource, v as registerOpenClawStateDatabaseLifecycleListener } from "./openclaw-state-db-cache-BEArcjbO.mjs"; import { t as SQLITE_IDLE_HANDLE_TTL_MS } from "./sqlite-handle-lifecycle-dWd9h3ii.mjs"; import { t as resolveRuntimeProcessEntrypointUrl } from "./runtime-process-url-DpjnU3O9.mjs"; import { n as hydrateOpenClawStateWorkerError, r as retainOpenClawStateWorkerErrorPayload } from "./openclaw-state-worker-error-PbHiriY7.mjs"; import { a as DEFAULT_WORKER_PENDING_BYTES, n as createOwnedWorkerTaskPool, r as WorkerTaskError } from "./worker-task-pool-Dx19K932.mjs"; //#region src/channels/message/ingress-queue-read-contract.ts function isChannelIngressReadCommand(value) { if (!isRecord(value)) return false; if (value.type === "channelIngress.failedHealth") return value.input === void 0; if (value.type === "channelIngress.accounts") return isRecord(value.input) && typeof value.input.channelId === "string"; return value.type === "channelIngress.pressureHealth" && isRecord(value.input) && typeof value.input.now === "number"; } //#endregion //#region src/state/openclaw-state-read-worker.ts function readPool() { const state = resolveGlobalSingleton(Symbol.for("openclaw.stateReadWorkers"), () => { const owned = {}; registerOpenClawStateDatabaseAsyncResource({ phase: "after-resources", async close(identity) { if (!owned.pool) return; const pool = owned.pool; if (identity) { await pool.closeResources(identity.key); return; } await (owned.closing ??= Promise.resolve().then(() => pool.closeResources()).then(() => pool.close()).then(() => { owned.pool = void 0; }).finally(() => { owned.closing = void 0; })); } }); registerOpenClawStateDatabaseLifecycleListener((event) => { if (event.kind !== "opened" && event.identity) owned.pool?.closeResources(event.identity.key).catch((error) => { process.emitWarning(`Shared-state reader invalidation failed: ${String(error)}`); }); }); return owned; }); if (state.closing) throw new WorkerTaskError("Shared-state readers are closing", "unavailable"); if (!state.pool) { ensureSqliteLibrarySelected(); state.pool = createOwnedWorkerTaskPool({ workerUrl: resolveRuntimeProcessEntrypointUrl("stateRead"), workerOptions: { resourceLimits: { maxOldGenerationSizeMb: 512 } }, maxWorkers: 2, idleTimeoutMs: SQLITE_IDLE_HANDLE_TTL_MS, maxPendingTasks: 128, maxPendingBytes: DEFAULT_WORKER_PENDING_BYTES }); } return state.pool; } function captureCommand(command) { if (command.type === "tui.lastSession.retiredPointers") return { ...command, retiredSessionKeys: [...command.retiredSessionKeys] }; if (command.type === "userProfiles.avatar.read") return { ...command, expected: { ...command.expected } }; if (command.type === "channelIngress.failedHealth") return { type: command.type }; if (command.type === "channelIngress.pressureHealth") return { type: command.type, input: { now: command.input.now } }; if (command.type === "channelIngress.accounts") return { type: command.type, input: { channelId: command.input.channelId } }; if (command.type === "cron.jobNames") return { ...command, jobIds: [...command.jobIds] }; if (command.type === "sessionRepositoryWorkspaces.find") return { type: command.type, owners: command.owners.map(({ agentId, sessionKey }) => ({ agentId, sessionKey })) }; if (command.type === "userProfiles.channelIdentity.resolve") return { type: command.type, identity: structuredClone(command.identity) }; if (command.type === "userProfiles.githubAttribution.resolve" || command.type === "userPreferences.values") return { ...command, profileIds: [...command.profileIds] }; if (command.type === "subagents.runs") return { ...command, scope: command.scope.kind === "ids" ? { kind: "ids", runIds: [...command.scope.runIds] } : { ...command.scope } }; if (command.type === "mcpOAuth.statuses") return { type: command.type, input: [...command.input] }; if (command.type === "sessionGroups.members") return { ...command, cfg: structuredClone(command.cfg) }; if (command.type === "conversationBindings.inspect") { const { channel, accountId, conversationId, parentConversationId } = command.conversation; return { type: command.type, conversation: { channel, accountId, conversationId, ...parentConversationId !== void 0 ? { parentConversationId } : {} } }; } if (command.type === "cron.observeRunRecovery") return { type: command.type, storeKey: command.storeKey, proposals: command.proposals.map(({ jobId, queuedAtMs, runningAtMs }) => ({ jobId, ...queuedAtMs === void 0 ? {} : { queuedAtMs }, ...runningAtMs === void 0 ? {} : { runningAtMs } })) }; if (command.type === "devicePairing.bootstrapContext") return { ...command, input: { ...command.input } }; if (command.type === "operatorApprovals.history") return { ...command, input: { ...command.input } }; if (command.type === "acpSessions.metadata" || command.type === "githubPublication.knownPullRequestUrls" || command.type === "githubRepository.knownPullRequestUrls" || command.type === "workers.placementProjection") return structuredClone(command); if (command.type === "pluginBlob.lookup") { const { pluginId, namespace, key } = command.input; return { type: command.type, input: { pluginId, namespace, key } }; } if (command.type === "pluginBlob.entries") { const { pluginId, namespace } = command.input; return { type: command.type, input: { pluginId, namespace } }; } if (command.type === "updateRuns.list") return { ...command, input: { ...command.input } }; if (command.type === "updateRuns.reconciliationCandidates") return { ...command, input: { ...command.input, ...command.input.runIds ? { runIds: [...command.input.runIds] } : {} } }; if (command.type === "skills.library.descriptions" || command.type === "skills.library.manifests") return { type: command.type, input: command.input.map(({ skillId, revision }) => ({ skillId, revision })) }; if (command.type === "audit.run.inspect") { const input = command.input; const common = { now: input.now, decisionCursor: input.decisionCursor, decisionLimit: input.decisionLimit }; return { type: command.type, input: "executionId" in input ? { ...common, executionId: input.executionId } : { ...common, runId: input.runId, executionOffset: input.executionOffset, executionLimit: input.executionLimit } }; } if (command.type === "workerEnvironments.pruneCandidates") return { type: command.type, input: { ...command.input, cursor: command.input.cursor ? { ...command.input.cursor } : void 0 } }; if (command.type === "workerEnvironments.snapshot") return { type: command.type, ...command.ids ? { ids: [...command.ids] } : {} }; return { ...command }; } function commandBytes(command) { let bytes = Buffer.byteLength(command.type, "utf8"); if (command.type === "tui.lastSession.read") return bytes + Buffer.byteLength(command.stateKey, "utf8"); if (command.type === "tui.lastSession.retiredPointers") return command.retiredSessionKeys.reduce((total, key) => total + Buffer.byteLength(key, "utf8"), bytes); if (isChannelIngressReadCommand(command)) return bytes + Buffer.byteLength(JSON.stringify(command.input ?? null), "utf8"); if (command.type === "capture.readOnlyEvents") return bytes + Buffer.byteLength(command.sessionId, "utf8") + 8; if (command.type === "capture.readOnlyBlob") return bytes + Buffer.byteLength(command.blobId, "utf8"); if (command.type === "cron.jobNames") return command.jobIds.reduce((sum, id) => sum + Buffer.byteLength(id, "utf8"), bytes + Buffer.byteLength(command.storePath ?? "", "utf8")); if (command.type === "sessionRepositoryWorkspaces.find") return command.owners.reduce((total, owner) => total + Buffer.byteLength(owner.agentId, "utf8") + Buffer.byteLength(owner.sessionKey, "utf8"), bytes); if (command.type === "agentDatabaseDeletion.snapshot") return bytes + Buffer.byteLength(command.purpose, "utf8"); if (command.type === "agentDeletionJournal.status") return bytes + Buffer.byteLength(command.agentId, "utf8"); if (command.type === "subagents.runs") return bytes + (command.scope.kind === "session" ? Buffer.byteLength(command.scope.sessionKey, "utf8") : command.scope.runIds.reduce((total, runId) => total + Buffer.byteLength(runId, "utf8"), 0)); if (command.type === "mcpOAuth.statuses") return command.input.reduce((total, key) => total + Buffer.byteLength(key, "utf8"), bytes); if (command.type === "mcpOAuth.readOnly" || command.type === "mcpOAuth.keys" || command.type === "mcpOAuth.pending" || command.type === "mcpOAuth.countPrincipals") return bytes + Buffer.byteLength(command.input, "utf8"); if (command.type === "sessionGroups.members") return bytes + Buffer.byteLength(JSON.stringify(command.cfg), "utf8"); if (command.type === "conversationBindings.inspect") return bytes + Object.values(command.conversation).reduce((sum, value) => sum + Buffer.byteLength(value ?? "", "utf8"), 0); if (command.type === "cron.observeRunRecovery") return command.proposals.reduce((total, proposal) => total + Buffer.byteLength(proposal.jobId, "utf8") + (proposal.queuedAtMs === void 0 ? 0 : 8) + (proposal.runningAtMs === void 0 ? 0 : 8), bytes + Buffer.byteLength(command.storeKey, "utf8")); if (command.type === "devicePairing.bootstrapContext") return bytes + Buffer.byteLength(command.input.token) + Buffer.byteLength(command.input.deviceId) + Buffer.byteLength(command.input.publicKey) + 8; if (command.type === "devicePairing.lookup") return bytes + Buffer.byteLength(command.deviceId); if (command.type === "devicePairing.pending") return bytes + Buffer.byteLength(command.requestId) + 8; if (command.type === "devicePairing.list") return bytes + 8 + Buffer.byteLength(command.publishedRevision ?? ""); if (command.type === "operatorApprovals.history") return bytes + Buffer.byteLength(command.input.cursor ?? "", "utf8") + Buffer.byteLength(command.input.kind ?? "", "utf8") + 16; if (command.type === "deliveryQueue.outbound") return bytes + Buffer.byteLength(command.id ?? "", "utf8"); if (command.type === "githubPublication.request" || command.type === "githubRepository.request" || command.type === "githubPublication.lifecycle") return bytes + Buffer.byteLength(command.requestId, "utf8") + 8; if (command.type === "githubPublication.knownPullRequestUrls" || command.type === "githubRepository.knownPullRequestUrls") return Object.values(command.input).reduce((total, value) => total + (typeof value === "string" ? Buffer.byteLength(value, "utf8") : 8), bytes); if (command.type === "pluginBlob.lookup" || command.type === "pluginBlob.entries") return bytes + Buffer.byteLength(command.input.pluginId, "utf8") + Buffer.byteLength(command.input.namespace, "utf8") + (command.type === "pluginBlob.lookup" ? Buffer.byteLength(command.input.key, "utf8") : 0); if (command.type === "subagents.forChildSession") return bytes + Buffer.byteLength(command.childSessionKey, "utf8"); if (command.type === "sandboxRegistry.get") return bytes + Buffer.byteLength(command.containerName, "utf8"); if (command.type === "sandboxRegistry.runtimeIds") return bytes + Buffer.byteLength(command.backendId, "utf8") + Buffer.byteLength(command.scopeKey, "utf8"); if (command.type === "updateRuns.get" || command.type === "updateRuns.reconciliationCandidate") return bytes + Buffer.byteLength(command.runId, "utf8"); if (command.type === "updateRuns.reconciliationCandidates") return (command.input.runIds ?? []).reduce((total, runId) => total + Buffer.byteLength(runId, "utf8"), bytes + 3 + (command.input.repairHistorySinceMs === void 0 ? 0 : 8)); if (command.type === "updateRuns.list") return bytes + Buffer.byteLength(command.input.reason ?? "", "utf8") + Buffer.byteLength(command.input.includeRunId ?? "", "utf8") + (command.input.limit === void 0 ? 0 : 8) + (command.input.active === void 0 ? 0 : 1); if (command.type === "skills.library.descriptions" || command.type === "skills.library.manifests") return command.input.reduce((total, pin) => total + Buffer.byteLength(pin.skillId, "utf8") + Buffer.byteLength(pin.revision, "utf8"), bytes); if (command.type === "fleet.get") return bytes + Buffer.byteLength(command.tenantId, "utf8"); if (command.type === "onboardingRecommendations.read") return bytes + Buffer.byteLength(command.configKey, "utf8"); if (command.type === "userProfiles.reconcile" || command.type === "userProfiles.avatar.inspect" || command.type === "userProfiles.channelIdentity.list" || command.type === "userProfiles.authority.resolve") return bytes + Buffer.byteLength(command.profileId, "utf8"); if (command.type === "userProfiles.avatar.read") return bytes + Buffer.byteLength(command.profileId, "utf8") + Object.values(command.expected).reduce((total, value) => total + Buffer.byteLength(value, "utf8"), 0); if (command.type === "userProfiles.githubIdentity.cached") return bytes + Buffer.byteLength(command.email, "utf8") + 8; if (command.type === "userProfiles.githubAttribution.resolve" || command.type === "userPreferences.values") return command.profileIds.reduce((total, profileId) => total + Buffer.byteLength(profileId, "utf8"), bytes + (command.type === "userPreferences.values" ? Buffer.byteLength(command.key) : 0)); if (command.type === "userProfiles.channelIdentity.resolve") return bytes + Buffer.byteLength(JSON.stringify(command.identity), "utf8"); if (command.type === "userProfiles.email.resolve") return bytes + Buffer.byteLength(command.email, "utf8"); if (command.type === "workspace.snapshot") return bytes + Buffer.byteLength(command.workspaceDir, "utf8"); if (command.type === "audit.run.inspect") { const input = command.input; bytes += Buffer.byteLength(input.decisionCursor ?? "", "utf8") + 8; if (input.decisionLimit !== void 0) bytes += 8; if ("executionId" in input) return bytes + Buffer.byteLength(input.executionId, "utf8"); return bytes + Buffer.byteLength(input.runId, "utf8") + (input.executionOffset === void 0 ? 0 : 8) + (input.executionLimit === void 0 ? 0 : 8); } if (command.type === "workers.placementProjection") return Buffer.byteLength(JSON.stringify(command), "utf8"); if (command.type === "workerEnvironments.pruneCandidates") return bytes + 8 + (command.input.limit === void 0 ? 0 : 8) + (command.input.cursor ? 8 + Buffer.byteLength(command.input.cursor.environmentId, "utf8") : 0); if (command.type === "workerEnvironments.snapshot") return bytes + (command.ids?.reduce((total, id) => total + Buffer.byteLength(id, "utf8"), 0) ?? 0); return bytes; } function requestBytes(request) { return [ ...Object.entries(request.context.environment).flatMap(([key, value]) => [key, value]), request.context.existingSchemaPath, request.databasePath, request.location, request.expectedIdentity, request.snapshotRoot ].reduce((bytes, value) => bytes + (value === void 0 ? 0 : Buffer.byteLength(value, "utf8")), commandBytes(request.command)); } function decodeTaskReply(reply) { if (reply.ok) return { value: reply }; const error = new Error(reply.message); retainOpenClawStateWorkerErrorPayload(error, reply.error); return { error: hydrateOpenClawStateWorkerError(error, { includeOrdinary: true }), sourceAdmitted: reply.sourceAdmitted === true }; } function createOpenClawStateReadTransport(command) { const capturedCommand = captureCommand(command); const tasks = /* @__PURE__ */ new Map(); let closed = false; let closing; const closeTask = async (task) => { const cleanup = tasks.get(task); try { await task.close(cleanup?.retire ? { retire: true } : void 0); } catch (error) { if (cleanup?.error) throw createSqliteLifecycleAggregateError([cleanup.error, error], "Shared-state reader cleanup and worker retirement failed", cleanup.error); throw error; } tasks.delete(task); }; const run = async (context, location, checkFreshAdmission, operation, authority, expectedIdentity, snapshotRoot) => { if (closed) throw new WorkerTaskError("Shared-state read transport is closed", "unavailable"); const request = { context: { environment: { ...context.environment }, existingSchemaPath: context.existingSchemaPath }, databasePath: context.admission.databasePath, location, checkFreshAdmission, expectedIdentity, snapshotRoot, command: { ...operation } }; const task = readPool().runTask(() => { authority.assertCurrent(); return request; }, { signal: authority.signal, inputBytes: requestBytes(request) }); const cleanup = { retire: true }; tasks.set(task, cleanup); let outcome; try { const reply = await task.result; if (reply.nativeCleanupFailure) { const error = /* @__PURE__ */ new Error("Quarantine reader native cleanup was not confirmed"); retainOpenClawStateWorkerErrorPayload(error, reply.nativeCleanupFailure.error); cleanup.error = hydrateOpenClawStateWorkerError(error, { includeOrdinary: true }); } outcome = decodeTaskReply(reply); } catch (error) { outcome = { error }; } cleanup.retire = Boolean(process.versions.bun) || "error" in outcome || cleanup.error !== void 0; return { task, outcome }; }; return { async validateFresh(context, authority) { const { task, outcome } = await run(context, context.admission.databasePath, true, { type: "admit" }, authority); if ("error" in outcome) throw outcome.error; authority.assertCurrent(); await closeTask(task); }, async read(source, authority) { const { outcome } = await run(source.context, source.location, source.checkFreshAdmission, capturedCommand, authority, source.expectedIdentity, source.snapshotRoot); return outcome; }, close() { closed = true; return closing ??= Promise.allSettled([...tasks.keys()].map(closeTask)).then((results) => { const errors = results.flatMap((result) => result.status === "rejected" ? [result.reason] : []); if (errors.length === 1) throw errors[0]; if (errors.length > 1) throw new AggregateError(errors, "Shared-state reader task cleanup failed"); }).finally(() => { closing = void 0; }); } }; } //#endregion export { createOpenClawStateReadTransport as t };