openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
361 lines (360 loc) • 13.9 kB
JavaScript
import { c as isRecord } from "./record-coerce-DItp3I4t.js";
import { l as normalizeOptionalString } from "./string-coerce-CIXf7egm.js";
import { t as ErrorCodes } from "./gateway-error-details-w0nAGBBp.js";
import { t as normalizeControlUiBasePath } from "./control-ui-shared-BiO6QP54.js";
import { g as validateApprovalResolveParams, m as validateApprovalHistoryParams, p as validateApprovalGetParams } from "./src-BiL5aQto.js";
import { d as errorShape } from "./error-codes-Bo8q2D1o.js";
import { n as isWellFormedApprovalId } from "./approval-id-BTRnO3t1.js";
import { o as getOperatorApprovalDetailed, t as OperatorApprovalHistoryCursorError, u as listTerminalOperatorApprovals } from "./operator-approval-store-BxnBnd-1.js";
import { n as canResolveOperatorApproval, r as canReviewOperatorApproval, t as canAccessOperatorApproval } from "./operator-approval-authorization-DgI7PHhT.js";
import { d as prepareApprovalChannelCustody, f as canAccessApprovalSession, u as respondApprovalStorageUnavailable } from "./approval-shared-ahH5xF_7.js";
import { t as publishAppliedApprovalResolution } from "./approval-publication-C03af9bl.js";
//#region src/gateway/server-methods/approval.ts
function buildApprovalSnapshot(record, controlUiBasePath) {
const common = {
id: record.id,
status: record.status,
presentation: record.presentation,
urlPath: `${controlUiBasePath}/approve/${encodeURIComponent(record.id)}`,
createdAtMs: record.createdAtMs,
expiresAtMs: record.expiresAtMs
};
if (record.status === "pending") return common;
if (record.resolvedAtMs === null || record.terminalReason === null) return null;
const terminal = {
...common,
resolvedAtMs: record.resolvedAtMs,
reason: record.terminalReason,
source: {
...record.source.agentId ? { agentId: record.source.agentId } : {},
...record.source.sessionKey ? { sessionKey: record.source.sessionKey } : {}
},
...record.resolver ? { resolver: {
kind: record.resolver.kind,
...record.resolver.id ? { id: record.resolver.id } : {}
} } : {}
};
if (record.status === "allowed") {
if (record.decision !== "allow-once" && record.decision !== "allow-always") return null;
return {
...terminal,
decision: record.decision
};
}
if (record.status === "denied") return {
...terminal,
decision: "deny"
};
return terminal;
}
function resolveApprovalResolver(client) {
const deviceId = normalizeOptionalString(client?.connect?.device?.id);
if (deviceId) return {
kind: "device",
id: deviceId
};
return {
kind: "runtime",
id: normalizeOptionalString(client?.connect?.client?.id) ?? null
};
}
function resolveLegacyApprovalLabel(client) {
return normalizeOptionalString(client?.connect?.client?.displayName) ?? normalizeOptionalString(client?.connect?.client?.id) ?? null;
}
function respondApprovalNotFound(respond) {
respond(false, void 0, errorShape(ErrorCodes.INVALID_REQUEST, "approval not found", { details: { reason: ErrorCodes.APPROVAL_NOT_FOUND } }));
}
function readExactApprovalId(params) {
if (!isRecord(params) || typeof params.id !== "string") return null;
const id = params.id;
return isWellFormedApprovalId(id) ? id : null;
}
function loadVisibleApproval(params) {
if (!(params.allowApprovalRuntime ? canResolveOperatorApproval(params.client) : canReviewOperatorApproval(params.client))) return null;
const liveRecord = params.execApprovalManager.getLiveSnapshot(params.id) ?? params.pluginApprovalManager.getLiveSnapshot(params.id) ?? params.systemAgentApprovalManager?.getLiveSnapshot(params.id);
if (liveRecord && !canAccessApprovalSession({
cfg: params.cfg,
client: params.client,
sessionKey: liveRecord.request.sessionKey,
agentId: liveRecord.request.agentId
})) return null;
if (liveRecord && !canAccessOperatorApproval({
client: params.client,
allowApprovalRuntime: params.allowApprovalRuntime,
binding: { reviewerDeviceIds: liveRecord.approvalReviewerDeviceIds }
})) return null;
let lookup;
try {
lookup = getOperatorApprovalDetailed({
id: params.id,
allowTransportRef: params.allowTransportRef,
databaseOptions: params.databaseOptions
});
} catch (error) {
const corrupt = {
outcome: "corrupt",
id: params.id
};
params.execApprovalManager.reconcileDurableLookup(corrupt);
params.pluginApprovalManager.reconcileDurableLookup(corrupt);
params.systemAgentApprovalManager?.reconcileDurableLookup(corrupt);
throw error;
}
if (lookup.outcome === "found") {
if (!canAccessApprovalSession({
cfg: params.cfg,
client: params.client,
sessionKey: lookup.record.source.sessionKey,
agentId: lookup.record.source.agentId
})) return null;
if (!canAccessOperatorApproval({
client: params.client,
allowApprovalRuntime: params.allowApprovalRuntime,
binding: { reviewerDeviceIds: lookup.record.reviewerDeviceIds }
})) return null;
return (lookup.record.kind === "exec" ? params.execApprovalManager : lookup.record.kind === "plugin" ? params.pluginApprovalManager : params.systemAgentApprovalManager)?.reconcileDurableLookup(lookup) ?? null;
}
const missing = {
outcome: lookup.outcome === "corrupt" ? "corrupt" : "missing",
id: lookup.outcome === "corrupt" ? lookup.id ?? params.id : params.id
};
params.execApprovalManager.reconcileDurableLookup(missing);
params.pluginApprovalManager.reconcileDurableLookup(missing);
params.systemAgentApprovalManager?.reconcileDurableLookup(missing);
return null;
}
function resolveLiveRecord(params) {
return params.liveRecord ?? params.manager.getLiveSnapshot(params.id) ?? void 0;
}
function applyApprovalDecision(params) {
const result = params.forceMalformedDeny ? params.manager.forceDenyDetailed(params.id, "malformed-verdict", params.resolver, "denied", void 0, false, params.localResolvedBy) : params.manager.resolveDetailed(params.id, params.decision, params.resolver, params.localResolvedBy, "operator", params.grantExpiresAtMs !== void 0 ? { grantExpiresAtMs: params.grantExpiresAtMs } : {});
if (result.outcome === "decision-not-allowed") return applyApprovalDecision({
...params,
forceMalformedDeny: true
});
if (result.outcome === "not-found" || result.outcome === "corrupt") return { ok: false };
const applied = result.outcome === "resolved" || result.outcome === "denied";
return {
ok: true,
applied,
record: result.record,
liveRecord: applied ? resolveLiveRecord({
manager: params.manager,
id: params.id,
liveRecord: result.liveRecord
}) : result.liveRecord
};
}
/** Creates kind-agnostic approval lookup and resolution handlers. */
function createApprovalHandlers(params) {
return {
"approval.history": ({ params: rawParams, respond, client, context }) => {
if (!validateApprovalHistoryParams(rawParams)) {
respond(false, void 0, errorShape(ErrorCodes.INVALID_REQUEST, "invalid approval.history params"));
return;
}
const historyParams = rawParams;
let history;
try {
history = listTerminalOperatorApprovals({
cursor: historyParams.cursor,
limit: historyParams.limit,
kind: historyParams.kind,
databaseOptions: params.databaseOptions
});
} catch (error) {
if (error instanceof OperatorApprovalHistoryCursorError) {
respond(false, void 0, errorShape(ErrorCodes.INVALID_REQUEST, "invalid approval.history cursor"));
return;
}
respondApprovalStorageUnavailable({
context,
respond,
operation: "history",
error
});
return;
}
const cfg = context.getRuntimeConfig();
const controlUiBasePath = normalizeControlUiBasePath(cfg.gateway?.controlUi?.basePath);
respond(true, {
items: history.records.flatMap((record) => {
if (!canAccessApprovalSession({
cfg,
client,
sessionKey: record.source.sessionKey,
agentId: record.source.agentId
})) return [];
const snapshot = buildApprovalSnapshot(record, controlUiBasePath);
return snapshot && snapshot.status !== "pending" ? [snapshot] : [];
}),
...history.nextCursor ? { nextCursor: history.nextCursor } : {}
}, void 0);
},
"approval.get": ({ params: rawParams, respond, client, context }) => {
if (!validateApprovalGetParams(rawParams)) {
respond(false, void 0, errorShape(ErrorCodes.INVALID_REQUEST, "invalid approval.get params"));
return;
}
const id = readExactApprovalId(rawParams);
let record;
try {
record = id ? loadVisibleApproval({
id,
client,
cfg: context.getRuntimeConfig(),
execApprovalManager: params.execApprovalManager,
pluginApprovalManager: params.pluginApprovalManager,
systemAgentApprovalManager: params.systemAgentApprovalManager,
databaseOptions: params.databaseOptions
}) : null;
} catch (error) {
respondApprovalStorageUnavailable({
context,
respond,
operation: "lookup",
error
});
return;
}
const controlUiBasePath = normalizeControlUiBasePath(context.getRuntimeConfig()?.gateway?.controlUi?.basePath);
const approval = record ? buildApprovalSnapshot(record, controlUiBasePath) : null;
if (!approval) {
respondApprovalNotFound(respond);
return;
}
respond(true, { approval }, void 0);
},
"approval.resolve": async ({ params: rawParams, respond, client, context }) => {
const validParams = validateApprovalResolveParams(rawParams);
const resolveParams = validParams ? rawParams : null;
if (isRecord(rawParams) && "reviewer" in rawParams && !resolveParams?.reviewer) {
respondApprovalNotFound(respond);
return;
}
const id = readExactApprovalId(rawParams);
let record;
try {
record = id ? loadVisibleApproval({
id,
client,
cfg: context.getRuntimeConfig(),
allowApprovalRuntime: true,
allowTransportRef: true,
execApprovalManager: params.execApprovalManager,
pluginApprovalManager: params.pluginApprovalManager,
systemAgentApprovalManager: params.systemAgentApprovalManager,
databaseOptions: params.databaseOptions
}) : null;
} catch (error) {
respondApprovalStorageUnavailable({
context,
respond,
operation: "lookup",
error
});
return;
}
if (!id || !record) {
respondApprovalNotFound(respond);
return;
}
const custody = resolveParams?.reviewer ? prepareApprovalChannelCustody({
cfg: context.getRuntimeConfig(),
approvalKind: record.kind,
reviewer: resolveParams.reviewer
}) : null;
const liveRecord = record.kind === "exec" ? params.execApprovalManager.getLiveSnapshot(record.id) : record.kind === "plugin" ? params.pluginApprovalManager.getLiveSnapshot(record.id) : params.systemAgentApprovalManager?.getLiveSnapshot(record.id);
if (resolveParams?.reviewer && (!custody || !liveRecord || !custody.authorizes(liveRecord))) {
respondApprovalNotFound(respond);
return;
}
if (record.status !== "pending") {
const controlUiBasePath = normalizeControlUiBasePath(context.getRuntimeConfig()?.gateway?.controlUi?.basePath);
const approval = buildApprovalSnapshot(record, controlUiBasePath);
if (!approval || approval.status === "pending") {
respondApprovalNotFound(respond);
return;
}
respond(true, {
applied: false,
approval
}, void 0);
return;
}
const resolver = custody ? {
kind: "channel",
id: custody.resolverId
} : resolveApprovalResolver(client);
const localResolvedBy = resolveLegacyApprovalLabel(client);
const requestedDecision = resolveParams?.decision ?? null;
const decisionAllowed = requestedDecision === "deny" || requestedDecision !== null && record.presentation.allowedDecisions.includes(requestedDecision);
const kindMatches = resolveParams?.kind === record.presentation.kind;
const forceMalformedDeny = !validParams || !kindMatches || !decisionAllowed;
let resolution;
try {
resolution = record.kind === "exec" ? applyApprovalDecision({
manager: params.execApprovalManager,
id: record.id,
decision: requestedDecision,
forceMalformedDeny,
resolver,
localResolvedBy,
...requestedDecision === "allow-always" && typeof resolveParams?.grantExpiresInDays === "number" ? { grantExpiresAtMs: Date.now() + Math.floor(resolveParams.grantExpiresInDays) * 864e5 } : {}
}) : record.kind === "plugin" ? applyApprovalDecision({
manager: params.pluginApprovalManager,
id: record.id,
decision: requestedDecision,
forceMalformedDeny,
resolver,
localResolvedBy
}) : applyApprovalDecision({
manager: params.systemAgentApprovalManager,
id: record.id,
decision: requestedDecision,
forceMalformedDeny,
resolver,
localResolvedBy
});
} catch (error) {
respondApprovalStorageUnavailable({
context,
respond,
operation: "resolve",
error
});
return;
}
if (!resolution.ok) {
respondApprovalNotFound(respond);
return;
}
const terminalRecord = resolution.record;
if (terminalRecord.status === "pending") {
respondApprovalNotFound(respond);
return;
}
const approval = buildApprovalSnapshot(terminalRecord, normalizeControlUiBasePath(context.getRuntimeConfig()?.gateway?.controlUi?.basePath));
if (!approval) {
respondApprovalNotFound(respond);
return;
}
respond(true, {
applied: resolution.applied,
approval
}, void 0);
if (resolution.applied && resolution.liveRecord) publishAppliedApprovalResolution({
record: terminalRecord,
liveRecord: resolution.liveRecord,
context,
forwarder: params.forwarder,
iosPushDelivery: params.iosPushDelivery,
pluginIosPushDelivery: params.pluginIosPushDelivery
}).catch((error) => {
context.logGateway?.error?.(`${terminalRecord.kind} approvals: unified resolve publication failed: ${String(error)}`);
});
}
};
}
//#endregion
export { createApprovalHandlers };