UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

508 lines (505 loc) • 25.3 kB
import { s as normalizeNullableString } from "./string-coerce-1VuiLaaR.mjs"; import "./src-BirUqOZp.mjs"; import { t as stableStringify } from "./stable-stringify-Djn3uxNo.mjs"; import { a as executeSqliteQueryTakeFirstSync, i as executeSqliteQuerySync, o as getNodeSqliteKysely } from "./kysely-sync-DUWzQvCU.mjs"; import { i as tableExists } from "./openclaw-state-db-schema-helpers-DKz5xGgh.mjs"; import { r as runSqliteDeferredTransactionSync } from "./sqlite-transaction-DBRLvCDM.mjs"; import { l as runOpenClawStateWriteTransaction } from "./openclaw-state-db-DJZ7EKoI.mjs"; import { f as resolveCronJobGrantDefinitionGenerationFloor, p as resolveCronJobGrantDefinitionRevision, s as loadedCronStoreFromRows } from "./row-codec-Fr_vfXdw.mjs"; import { i as mintMcpToolGrantLocked } from "./exec-approvals-sqlite-CbRxT0B7.mjs"; import { r as buildSystemRunApprovalEnvBinding } from "./system-run-approval-binding-Cl9jeqJB.mjs"; import { t as resolveCronJobConfigRevision } from "./config-revision-CA1eZYCq.mjs"; import { r as readCronRunReceiptCurrentFactsInDatabase } from "./run-receipt-read-CcxJup9X.mjs"; import { S as operatorApprovalTerminalFields, _ as requireDecodedRecord, a as clampAuditTimestamp, c as denyCorruptPendingRow, g as requireApprovalId, l as expirePendingRow, m as matchesExpectedApprovalOwner, p as isValidTimestamp, s as decodeOperatorApprovalRow, v as requireString, y as selectOperatorApprovalRow } from "./operator-approval-store.rows-CK66kG3A.mjs"; import { isDeepStrictEqual } from "node:util"; import { randomUUID } from "node:crypto"; //#region src/gateway/operator-approval-standing-grants.ts const STANDING_GRANT_TABLE = "operator_approval_standing_grants"; const STANDING_GRANT_GENERATION_TABLE = "operator_approval_standing_grant_generations"; const STANDING_GRANT_SCHEMA_SQL = ` CREATE TABLE IF NOT EXISTS operator_approval_standing_grants ( grant_id TEXT NOT NULL PRIMARY KEY CHECK (length(grant_id) > 0), minted_by_approval_id TEXT NOT NULL REFERENCES operator_approvals(approval_id) ON DELETE CASCADE, agent_id TEXT NOT NULL CHECK (length(agent_id) > 0), cron_job_id TEXT NOT NULL CHECK (length(cron_job_id) > 0), job_config_revision TEXT NOT NULL CHECK (length(job_config_revision) > 0), operation_binding TEXT NOT NULL CHECK (length(operation_binding) > 0), created_at_ms INTEGER NOT NULL, expires_at_ms INTEGER CHECK (expires_at_ms IS NULL OR expires_at_ms >= created_at_ms), revoked_at_ms INTEGER, revoked_by TEXT, last_used_at_ms INTEGER, use_count INTEGER NOT NULL DEFAULT 0 ) STRICT; CREATE INDEX IF NOT EXISTS idx_operator_approval_standing_grants_binding ON operator_approval_standing_grants(agent_id, cron_job_id, operation_binding, created_at_ms DESC); CREATE TABLE IF NOT EXISTS operator_approval_standing_grant_generations ( grant_id TEXT NOT NULL PRIMARY KEY REFERENCES operator_approval_standing_grants(grant_id) ON DELETE CASCADE, job_definition_generation INTEGER NOT NULL CHECK (job_definition_generation >= 1) ) STRICT; `; function projectCronStandingGrant(row) { return { grantId: row.grant_id, mintedByApprovalId: row.minted_by_approval_id, agentId: row.agent_id, cronJobId: row.cron_job_id, jobConfigRevision: row.job_config_revision, operationBinding: row.operation_binding, createdAtMs: row.created_at_ms, expiresAtMs: row.expires_at_ms, lastUsedAtMs: row.last_used_at_ms, useCount: row.use_count }; } /** * Exact gateway-exec operation binding: trimmed command text, cwd, and the * env-override hash. Both mint (approval creation) and use (allowlist * evaluation) derive it from the same raw inputs, so matching is byte-exact — * the same semantics as systemRunBinding digest matching. */ function buildCronExecOperationBinding(params) { return stableStringify({ v: 1, command: params.command.trim(), cwd: params.cwd?.trim() || null, envHash: buildSystemRunApprovalEnvBinding(params.env).envHash }); } /** Parses a stored operation binding back into its display facts. */ function parseCronExecOperationBinding(binding) { try { const parsed = JSON.parse(binding); if (parsed.v !== 1 || typeof parsed.command !== "string") return null; return { command: parsed.command, cwd: typeof parsed.cwd === "string" ? parsed.cwd : null }; } catch { return null; } } function ensureStandingGrantSchema(db) { if (tableExists(db, STANDING_GRANT_TABLE)) { if (db.prepare(`PRAGMA table_info(${STANDING_GRANT_TABLE})`).all().some((column) => column.name === "expires_at_ms" && column.notnull === 1)) db.exec(`DROP TABLE ${STANDING_GRANT_TABLE};`); } db.exec(STANDING_GRANT_SCHEMA_SQL); } function decodeDefinitionGeneration(value) { return typeof value === "number" && Number.isSafeInteger(value) && value >= 1 ? value : null; } /** * Mints one standing grant inside the caller's open write transaction — the * same transaction that resolves the minting approval to allow-always. A * re-mint for the same (agent, job, binding) replaces prior grants. */ function mintCronStandingGrantLocked(database, params) { ensureStandingGrantSchema(database.db); const stateDb = getNodeSqliteKysely(database.db); executeSqliteQuerySync(database.db, stateDb.deleteFrom(STANDING_GRANT_TABLE).where("expires_at_ms", "is not", null).where("expires_at_ms", "<=", params.nowMs)); const jobRows = executeSqliteQuerySync(database.db, stateDb.selectFrom("cron_jobs").selectAll().where("job_id", "=", params.cronJobId).limit(2)).rows; if (jobRows.length !== 1) return; const jobRow = jobRows[0]; const job = loadedCronStoreFromRows(jobRows).store.jobs.find((entry) => entry.id === params.cronJobId); if (!job || resolveCronJobConfigRevision(job) !== params.jobConfigRevision) return; const grantDefinitionRevision = resolveCronJobGrantDefinitionRevision(job); let jobDefinitionGeneration = decodeDefinitionGeneration(jobRow.grant_definition_generation); if (jobRow.grant_definition_revision !== grantDefinitionRevision || jobRow.grant_definition_updated_at !== jobRow.updated_at || jobDefinitionGeneration === null) { const retainedGenerationFloor = resolveCronJobGrantDefinitionGenerationFloor(database.db, params.cronJobId); jobDefinitionGeneration = decodeDefinitionGeneration(Math.max((jobDefinitionGeneration ?? 0) + 1, retainedGenerationFloor)); if (jobDefinitionGeneration === null) return; executeSqliteQuerySync(database.db, stateDb.updateTable("cron_jobs").set({ grant_definition_revision: grantDefinitionRevision, grant_definition_generation: jobDefinitionGeneration, grant_definition_updated_at: jobRow.updated_at }).where("store_key", "=", jobRow.store_key).where("job_id", "=", jobRow.job_id)); } executeSqliteQuerySync(database.db, stateDb.deleteFrom(STANDING_GRANT_TABLE).where("agent_id", "=", params.agentId).where("cron_job_id", "=", params.cronJobId).where("operation_binding", "=", params.operationBinding)); const grantId = randomUUID(); executeSqliteQuerySync(database.db, stateDb.insertInto(STANDING_GRANT_TABLE).values({ grant_id: grantId, minted_by_approval_id: params.approvalId, agent_id: params.agentId, cron_job_id: params.cronJobId, job_config_revision: params.jobConfigRevision, operation_binding: params.operationBinding, created_at_ms: params.nowMs, expires_at_ms: params.expiresAtMs, revoked_at_ms: null, revoked_by: null, last_used_at_ms: null, use_count: 0 })); executeSqliteQuerySync(database.db, stateDb.insertInto(STANDING_GRANT_GENERATION_TABLE).values({ grant_id: grantId, job_definition_generation: jobDefinitionGeneration })); } /** Worker snapshot lookup; only committed consumption authorizes a launch. */ function lookupCronStandingGrantInDatabase(db, params, recordUse) { return runSqliteDeferredTransactionSync(db, () => { if (!tableExists(db, STANDING_GRANT_TABLE)) return { outcome: "no-grant" }; const nowMs = params.nowMs ?? Date.now(); const stateDb = getNodeSqliteKysely(db); const grant = executeSqliteQueryTakeFirstSync(db, stateDb.selectFrom(STANDING_GRANT_TABLE).selectAll().where("agent_id", "=", params.agentId).where("cron_job_id", "=", params.cronJobId).where("operation_binding", "=", params.operationBinding).orderBy("created_at_ms", "desc").orderBy("grant_id", "desc").limit(1)); if (!grant || params.expectedGrant && (grant.grant_id !== params.expectedGrant.grantId || grant.minted_by_approval_id !== params.expectedGrant.mintedByApprovalId)) return { outcome: "no-grant" }; if (grant.revoked_at_ms !== null) return { outcome: "revoked" }; if (grant.expires_at_ms !== null && grant.expires_at_ms <= nowMs) return { outcome: "expired" }; if (grant.job_config_revision !== params.jobConfigRevision) return { outcome: "job-revision-changed" }; const jobRows = executeSqliteQuerySync(db, stateDb.selectFrom("cron_jobs").selectAll().where("job_id", "=", params.cronJobId).limit(2)).rows; if (jobRows.length !== 1) return { outcome: "job-missing" }; const job = loadedCronStoreFromRows(jobRows).store.jobs.find((entry) => entry.id === params.cronJobId); if (!job) return { outcome: "job-missing" }; if (resolveCronJobConfigRevision(job) !== grant.job_config_revision) return { outcome: "job-revision-changed" }; const jobRow = jobRows[0]; if (jobRow.grant_definition_revision !== resolveCronJobGrantDefinitionRevision(job)) return { outcome: "job-revision-changed" }; if (jobRow.grant_definition_updated_at !== jobRow.updated_at) return { outcome: "job-revision-changed" }; const grantGeneration = decodeDefinitionGeneration((tableExists(db, STANDING_GRANT_GENERATION_TABLE) ? executeSqliteQueryTakeFirstSync(db, stateDb.selectFrom(STANDING_GRANT_GENERATION_TABLE).select("job_definition_generation").where("grant_id", "=", grant.grant_id)) : void 0)?.job_definition_generation); const jobGeneration = decodeDefinitionGeneration(jobRow.grant_definition_generation); if (grantGeneration === null || jobGeneration === null || grantGeneration !== jobGeneration) return { outcome: "job-revision-changed" }; const { handle } = params; const facts = readCronRunReceiptCurrentFactsInDatabase(db, { type: "cron.currentReceipt", handle, includeJob: false, includeAvailability: true }); if (!isDeepStrictEqual(facts.receipt, handle) || facts.deletionBlocked || handle.agentId !== params.agentId || handle.jobId !== params.cronJobId || handle.storeKey !== jobRow.store_key || handle.configRevision !== params.jobConfigRevision || job.agentId !== void 0 && job.agentId !== handle.agentId) return { outcome: "receipt-not-current" }; const approvalRow = executeSqliteQueryTakeFirstSync(db, stateDb.selectFrom("operator_approvals").select(["status", "decision"]).where("approval_id", "=", grant.minted_by_approval_id)); if (!approvalRow) return { outcome: "approval-missing" }; if (approvalRow.status !== "allowed" || approvalRow.decision !== "allow-always") return { outcome: "approval-not-allow-always" }; if (!recordUse) return { outcome: "consumed", grant: projectCronStandingGrant(grant) }; const nextUseCount = grant.use_count + 1; if (executeSqliteQuerySync(db, stateDb.updateTable(STANDING_GRANT_TABLE).set({ last_used_at_ms: nowMs, use_count: nextUseCount }).where("grant_id", "=", grant.grant_id).where("revoked_at_ms", "is", null).where((eb) => eb.or([eb("expires_at_ms", "is", null), eb("expires_at_ms", ">", nowMs)]))).numAffectedRows !== 1n) return { outcome: "no-grant" }; return { outcome: "consumed", grant: { ...projectCronStandingGrant(grant), lastUsedAtMs: nowMs, useCount: nextUseCount } }; }); } /** Called only by the existing shared-state writer transaction. */ function consumeCronStandingGrantInDatabase(params) { return runOpenClawStateWriteTransaction(({ db }) => lookupCronStandingGrantInDatabase(db, params, params.recordUse), params.databaseOptions); } /** Repeated revocation preserves the original actor and timestamp. */ function revokeCronStandingGrantInDatabase(params) { return runOpenClawStateWriteTransaction((database) => { if (!tableExists(database.db, STANDING_GRANT_TABLE)) return { outcome: "not-found" }; const nowMs = params.nowMs ?? Date.now(); const stateDb = getNodeSqliteKysely(database.db); const grant = executeSqliteQueryTakeFirstSync(database.db, stateDb.selectFrom(STANDING_GRANT_TABLE).selectAll().where("grant_id", "=", params.grantId)); if (!grant) return { outcome: "not-found" }; if (grant.revoked_at_ms !== null) return { outcome: "already-revoked" }; executeSqliteQuerySync(database.db, stateDb.updateTable(STANDING_GRANT_TABLE).set({ revoked_at_ms: nowMs, revoked_by: params.revokedBy }).where("grant_id", "=", params.grantId).where("revoked_at_ms", "is", null)); return { outcome: "revoked", grant: { ...projectCronStandingGrant(grant), cronJobName: null, revokedAtMs: nowMs, revokedBy: params.revokedBy } }; }, params.databaseOptions); } //#endregion //#region src/gateway/operator-approval-store.transitions.ts function resolveOperatorApprovalInDatabase(params) { const id = requireApprovalId(params.id); const resolverId = normalizeNullableString(params.resolver.id); const runtimeEpoch = params.runtimeEpoch === void 0 ? void 0 : requireString(params.runtimeEpoch, "operator approval runtime epoch"); return runOpenClawStateWriteTransaction((database) => { const nowMs = params.nowMs ?? Date.now(); let row = selectOperatorApprovalRow(database, id); if (!row) return { outcome: "not-found" }; if (!matchesExpectedApprovalOwner({ row, expectedKind: params.expectedKind, runtimeEpoch })) return { outcome: "not-found" }; let record = decodeOperatorApprovalRow(row); if (!record) { denyCorruptPendingRow({ database, id, nowMs, createdAtMs: row.created_at_ms }); return { outcome: "corrupt" }; } if (record.status !== "pending") return { outcome: "already-resolved", retry: record.decision === params.decision ? "same" : "conflict", record }; if (record.expiresAtMs <= nowMs) { row = expirePendingRow({ database, id, nowMs, createdAtMs: row.created_at_ms }); if (!row) return { outcome: "not-found" }; record = requireDecodedRecord(row); return { outcome: "expired", record }; } if (!Array.prototype.includes.call(record.presentation.allowedDecisions, params.decision)) return { outcome: "decision-not-allowed", record }; const auditTimestampMs = clampAuditTimestamp(nowMs, record.createdAtMs); let resolveQuery = getNodeSqliteKysely(database.db).updateTable("operator_approvals").set(operatorApprovalTerminalFields(params.decision === "deny" ? "denied" : "allowed", "user", auditTimestampMs, params.decision, { kind: params.resolver.kind, id: resolverId })).where("approval_id", "=", id).where("status", "=", "pending").where("expires_at_ms", ">", nowMs); if (params.expectedKind !== void 0) resolveQuery = resolveQuery.where("kind", "=", params.expectedKind); if (runtimeEpoch !== void 0) resolveQuery = resolveQuery.where("runtime_epoch", "=", runtimeEpoch); const result = executeSqliteQuerySync(database.db, resolveQuery); row = selectOperatorApprovalRow(database, id); if (!row) return { outcome: "not-found" }; record = requireDecodedRecord(row); if (result.numAffectedRows === 1n) { if (params.decision === "allow-always" && params.mcpToolGrant && record.kind === "plugin" && record.source.agentId === params.mcpToolGrant.agentId) mintMcpToolGrantLocked(database.db, params.mcpToolGrant, auditTimestampMs); if (params.decision === "allow-always" && params.standingGrant) mintCronStandingGrantLocked(database, { ...params.standingGrant, approvalId: id, nowMs: auditTimestampMs }); return { outcome: "resolved", record }; } if (record.status === "pending" && record.expiresAtMs <= nowMs) { const expiredRow = expirePendingRow({ database, id, nowMs, createdAtMs: record.createdAtMs }); if (!expiredRow) return { outcome: "not-found" }; return { outcome: "expired", record: requireDecodedRecord(expiredRow) }; } return { outcome: "already-resolved", retry: record.decision === params.decision ? "same" : "conflict", record }; }, params.databaseOptions); } function forceDenyOperatorApprovalInDatabase(params) { const id = requireApprovalId(params.id); const runtimeEpoch = params.runtimeEpoch === void 0 ? void 0 : requireString(params.runtimeEpoch, "operator approval runtime epoch"); return runOpenClawStateWriteTransaction((database) => { const nowMs = params.nowMs ?? Date.now(); const row = selectOperatorApprovalRow(database, id); if (!row) return { outcome: "not-found" }; if (!matchesExpectedApprovalOwner({ row, expectedKind: params.expectedKind, runtimeEpoch })) return { outcome: "not-found" }; if (row.status === "pending" && row.expires_at_ms <= nowMs) { const expiredRow = expirePendingRow({ database, id, nowMs, createdAtMs: row.created_at_ms }); if (!expiredRow) return { outcome: "not-found" }; const expiredRecord = decodeOperatorApprovalRow(expiredRow); return expiredRecord ? { outcome: "expired", record: expiredRecord } : { outcome: "corrupt" }; } const record = decodeOperatorApprovalRow(row); if (!record) { denyCorruptPendingRow({ database, id, nowMs, createdAtMs: row.created_at_ms }); return { outcome: "corrupt" }; } if (record.status !== "pending") return { outcome: "already-terminal", record }; if (params.status === "expired" && params.requireDue === true && record.expiresAtMs > nowMs) return { outcome: "not-due", record }; const auditTimestampMs = clampAuditTimestamp(nowMs, record.createdAtMs); let denyQuery = getNodeSqliteKysely(database.db).updateTable("operator_approvals").set(operatorApprovalTerminalFields(params.status ?? "denied", params.reason, auditTimestampMs, "deny", { kind: params.resolver.kind, id: normalizeNullableString(params.resolver.id) })).where("approval_id", "=", id).where("status", "=", "pending"); if (params.expectedKind !== void 0) denyQuery = denyQuery.where("kind", "=", params.expectedKind); if (runtimeEpoch !== void 0) denyQuery = denyQuery.where("runtime_epoch", "=", runtimeEpoch); executeSqliteQuerySync(database.db, denyQuery); const terminalRow = selectOperatorApprovalRow(database, id); if (!terminalRow) return { outcome: "not-found" }; return { outcome: "denied", record: requireDecodedRecord(terminalRow) }; }, params.databaseOptions); } function expireDueOperatorApprovalsInDatabase(params) { return runOpenClawStateWriteTransaction((database) => { const nowMs = params.nowMs ?? Date.now(); const stateDb = getNodeSqliteKysely(database.db); const dueRows = executeSqliteQuerySync(database.db, stateDb.selectFrom("operator_approvals").selectAll().where("status", "=", "pending").where("expires_at_ms", "<=", nowMs).orderBy("expires_at_ms", "asc").orderBy("approval_id", "asc")).rows; if (dueRows.length === 0) return { affected: 0, records: [] }; const terminalFields = operatorApprovalTerminalFields("expired", "timeout", nowMs); const result = executeSqliteQuerySync(database.db, stateDb.updateTable("operator_approvals").set(terminalFields).where("status", "=", "pending").where("expires_at_ms", "<=", nowMs)); return { affected: Number(result.numAffectedRows ?? 0n), records: dueRows.map((row) => decodeOperatorApprovalRow({ ...row, ...terminalFields })).filter((record) => record !== null) }; }, params.databaseOptions); } function closeOrphanedOperatorApprovals(params) { const runtimeEpoch = requireString(params.runtimeEpoch, "operator approval runtime epoch"); return runOpenClawStateWriteTransaction((database) => { const nowMs = params.nowMs ?? Date.now(); const stateDb = getNodeSqliteKysely(database.db); const orphanRows = executeSqliteQuerySync(database.db, stateDb.selectFrom("operator_approvals").selectAll().where("status", "=", "pending").where("runtime_epoch", "!=", runtimeEpoch).orderBy("created_at_ms", "asc").orderBy("approval_id", "asc")).rows; if (orphanRows.length === 0) return { affected: 0, records: [] }; let affected = 0; const terminalRows = []; for (const row of orphanRows) { const auditTimestampMs = clampAuditTimestamp(nowMs, row.created_at_ms); const terminalFields = operatorApprovalTerminalFields("cancelled", "gateway-restart", auditTimestampMs); const result = executeSqliteQuerySync(database.db, stateDb.updateTable("operator_approvals").set(terminalFields).where("approval_id", "=", row.approval_id).where("status", "=", "pending")); const rowAffected = Number(result.numAffectedRows ?? 0n); affected += rowAffected; if (rowAffected === 1) terminalRows.push({ ...row, ...terminalFields }); } return { affected, records: terminalRows.map((row) => decodeOperatorApprovalRow(row)).filter((record) => record !== null) }; }, params.databaseOptions); } function consumeOperatorApprovalAllowOnceInDatabase(params) { const id = requireApprovalId(params.id); const consumerId = requireString(params.consumerId, "operator approval consumer id"); const runtimeEpoch = params.runtimeEpoch === void 0 ? void 0 : requireString(params.runtimeEpoch, "operator approval runtime epoch"); if (params.redemptionWindowMs !== void 0 && !isValidTimestamp(params.redemptionWindowMs)) throw new Error("operator approval redemption window must be a non-negative safe integer"); return runOpenClawStateWriteTransaction((database) => { const nowMs = params.nowMs ?? Date.now(); const redemptionThresholdMs = params.redemptionWindowMs === void 0 ? void 0 : nowMs - params.redemptionWindowMs; let row = selectOperatorApprovalRow(database, id); if (!row) return { outcome: "not-found" }; if (!matchesExpectedApprovalOwner({ row, expectedKind: params.expectedKind, runtimeEpoch })) return { outcome: "not-found" }; if (row.status === "pending" && row.expires_at_ms <= nowMs) { row = expirePendingRow({ database, id, nowMs, createdAtMs: row.created_at_ms }); if (!row) return { outcome: "not-found" }; } let record = decodeOperatorApprovalRow(row); if (!record) { denyCorruptPendingRow({ database, id, nowMs, createdAtMs: row.created_at_ms }); return { outcome: "corrupt" }; } if (record.status !== "allowed" || record.decision !== "allow-once") return { outcome: "not-allow-once", record }; if (record.consumedAtMs !== null) return { outcome: "already-consumed", record }; if (record.resolvedAtMs === null) return { outcome: "corrupt" }; if (redemptionThresholdMs !== void 0 && record.resolvedAtMs <= redemptionThresholdMs) return { outcome: "redemption-expired", record }; const auditTimestampMs = clampAuditTimestamp(nowMs, record.createdAtMs, record.resolvedAtMs, record.updatedAtMs); let consumeQuery = getNodeSqliteKysely(database.db).updateTable("operator_approvals").set({ consumed_at_ms: auditTimestampMs, consumed_by: consumerId, updated_at_ms: auditTimestampMs }).where("approval_id", "=", id).where("status", "=", "allowed").where("decision", "=", "allow-once").where("consumed_at_ms", "is", null); if (redemptionThresholdMs !== void 0) consumeQuery = consumeQuery.where("resolved_at_ms", ">", redemptionThresholdMs); if (params.expectedKind !== void 0) consumeQuery = consumeQuery.where("kind", "=", params.expectedKind); if (runtimeEpoch !== void 0) consumeQuery = consumeQuery.where("runtime_epoch", "=", runtimeEpoch); const result = executeSqliteQuerySync(database.db, consumeQuery); row = selectOperatorApprovalRow(database, id); if (!row) return { outcome: "not-found" }; record = requireDecodedRecord(row); if (result.numAffectedRows === 1n) return { outcome: "consumed", record }; if (redemptionThresholdMs !== void 0 && record.resolvedAtMs !== null && record.resolvedAtMs <= redemptionThresholdMs) return { outcome: "redemption-expired", record }; return { outcome: "already-consumed", record }; }, params.databaseOptions); } function pruneTerminalOperatorApprovals(params) { const retentionMs = params.retentionMs ?? 2592e6; if (!Number.isSafeInteger(retentionMs) || retentionMs < 0) throw new Error("operator approval retention must be a non-negative safe integer"); return runOpenClawStateWriteTransaction((database) => { const cutoffMs = (params.nowMs ?? Date.now()) - retentionMs; const stateDb = getNodeSqliteKysely(database.db); const result = executeSqliteQuerySync(database.db, stateDb.deleteFrom("operator_approvals").where("status", "!=", "pending").where("resolved_at_ms", "is not", null).where("resolved_at_ms", "<=", cutoffMs)); return Number(result.numAffectedRows ?? 0n); }, params.databaseOptions); } //#endregion export { pruneTerminalOperatorApprovals as a, consumeCronStandingGrantInDatabase as c, forceDenyOperatorApprovalInDatabase as i, parseCronExecOperationBinding as l, consumeOperatorApprovalAllowOnceInDatabase as n, resolveOperatorApprovalInDatabase as o, expireDueOperatorApprovalsInDatabase as r, buildCronExecOperationBinding as s, closeOrphanedOperatorApprovals as t, revokeCronStandingGrantInDatabase as u };