openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
322 lines (321 loc) • 14.3 kB
JavaScript
import { c as normalizeOptionalString } from "./string-coerce-mnp54Vah.js";
import { _ as uniqueStrings } from "./string-normalization-WNUDCpXX.js";
import { n as GATEWAY_CLIENT_IDS } from "./client-info-CcqJJIan.js";
import { i as detectCommandCarrierArgv, o as detectInlineEvalInSegments } from "./risks-DRxJ1pW5.js";
import { A as resolveExecApprovalAllowedDecisions, j as resolveExecApprovalRequestAllowedDecisions, r as DEFAULT_EXEC_APPROVAL_TIMEOUT_MS } from "./exec-approvals-C6M6SGY3.js";
import { t as resolveExecCommandHighlighting } from "./exec-command-highlighting-CWuIAGVg.js";
import { in as errorShape, rn as ErrorCodes } from "./schema-BwaBORnA.js";
import { $ as validateExecApprovalGetParams, et as validateExecApprovalRequestParams, t as formatValidationErrors, tt as validateExecApprovalResolveParams } from "./src-oj0IwW6K.js";
import { i as sanitizeExecApprovalWarningText, n as sanitizeExecApprovalDisplayText, r as sanitizeExecApprovalDisplayTextWithStatus, t as resolveExecApprovalCommandDisplay } from "./exec-approval-command-display-BYD_3EWn.js";
import { t as analyzeCommandForPolicy } from "./policy-KTeYxilK.js";
import { a as buildSystemRunApprovalEnvBinding, i as buildSystemRunApprovalBinding } from "./system-run-command-BO3cgBjR.js";
import { n as resolveSystemRunApprovalRequestContext } from "./system-run-approval-context-2h9yWcHm.js";
import { a as handlePendingApprovalRequest, c as registerPendingApprovalRecord, d as resolvePendingApprovalRecord, f as respondPendingApprovalLookupError, i as handleApprovalWaitDecision, l as resolveApprovalDecisionParams, n as buildRequestedApprovalEvent, o as isApprovalRecordVisibleToClient, r as handleApprovalResolve, s as listVisiblePendingApprovalRequests, t as bindApprovalRequesterMetadata } from "./approval-shared-bBBBkKDS.js";
//#region src/infra/command-analysis/explain.ts
function summarizeCommandSegmentsForDisplay(segments) {
const riskKinds = [];
const warningLines = [];
const inlineEval = detectInlineEvalInSegments(segments);
if (inlineEval) {
riskKinds.push("inline-eval");
warningLines.push(`Contains inline-eval: ${inlineEval.normalizedExecutable} ${inlineEval.flag}`);
}
for (const segment of segments) {
const effectiveArgv = segment.resolution?.effectiveArgv ?? segment.argv;
for (const hit of detectCommandCarrierArgv(effectiveArgv)) {
riskKinds.push("command-carrier");
warningLines.push(hit.flag ? `Contains command-carrier: ${hit.command} ${hit.flag}` : `Contains command-carrier: ${hit.command}`);
}
}
return {
commandCount: segments.length,
nestedCommandCount: 0,
riskKinds: uniqueStrings(riskKinds),
warningLines: uniqueStrings(warningLines)
};
}
function resolveCommandAnalysisSummaryForDisplay(params) {
const analysis = params.host === "node" ? Array.isArray(params.commandArgv) && params.commandArgv.length > 0 ? analyzeCommandForPolicy({
source: "argv",
argv: params.commandArgv,
cwd: params.cwd ?? void 0
}) : null : analyzeCommandForPolicy({
source: "shell",
command: params.commandText,
cwd: params.cwd ?? void 0
});
if (!analysis?.ok) return null;
const summary = summarizeCommandSegmentsForDisplay(analysis.segments);
const sanitizeText = params.sanitizeText;
if (!sanitizeText) return summary;
return {
commandCount: summary.commandCount,
nestedCommandCount: summary.nestedCommandCount,
riskKinds: summary.riskKinds.map((kind) => sanitizeText(kind)),
warningLines: summary.warningLines.map((line) => sanitizeText(line))
};
}
//#endregion
//#region src/gateway/server-methods/exec-approval.ts
const APPROVAL_ALLOW_ALWAYS_UNAVAILABLE_DETAILS = { reason: "APPROVAL_ALLOW_ALWAYS_UNAVAILABLE" };
const RESERVED_PLUGIN_APPROVAL_ID_PREFIX = "plugin:";
function normalizeCommandSpans(spans, commandLength) {
if (!spans) return;
const candidates = spans.filter((span) => Number.isSafeInteger(span.startIndex) && Number.isSafeInteger(span.endIndex) && span.startIndex >= 0 && span.endIndex > span.startIndex && span.endIndex <= commandLength).toSorted((a, b) => a.startIndex - b.startIndex || b.endIndex - a.endIndex);
const accepted = [];
let cursor = 0;
for (const span of candidates) {
if (span.startIndex < cursor) continue;
accepted.push({
startIndex: span.startIndex,
endIndex: span.endIndex
});
cursor = span.endIndex;
}
return accepted.length > 0 ? accepted : void 0;
}
function createExecApprovalHandlers(manager, opts) {
return {
"exec.approval.get": async ({ params, respond, client }) => {
if (!validateExecApprovalGetParams(params)) {
respond(false, void 0, errorShape(ErrorCodes.INVALID_REQUEST, `invalid exec.approval.get params: ${formatValidationErrors(validateExecApprovalGetParams.errors)}`));
return;
}
const resolved = resolvePendingApprovalRecord({
manager,
inputId: params.id,
client,
exposeAmbiguousPrefixError: true
});
if (!resolved.ok) {
respondPendingApprovalLookupError({
respond,
response: resolved.response
});
return;
}
const { commandText, commandPreview } = resolveExecApprovalCommandDisplay(resolved.snapshot.request);
respond(true, {
id: resolved.approvalId,
commandText,
commandPreview,
allowedDecisions: resolveExecApprovalRequestAllowedDecisions(resolved.snapshot.request),
host: resolved.snapshot.request.host ?? null,
nodeId: resolved.snapshot.request.nodeId ?? null,
agentId: resolved.snapshot.request.agentId ?? null,
expiresAtMs: resolved.snapshot.expiresAtMs
}, void 0);
},
"exec.approval.list": async ({ respond, client }) => {
respond(true, listVisiblePendingApprovalRequests({
manager,
client
}), void 0);
},
"exec.approval.request": async ({ params, respond, context, client }) => {
if (!validateExecApprovalRequestParams(params)) {
respond(false, void 0, errorShape(ErrorCodes.INVALID_REQUEST, `invalid exec.approval.request params: ${formatValidationErrors(validateExecApprovalRequestParams.errors)}`));
return;
}
const p = params;
const twoPhase = p.twoPhase === true;
const timeoutMs = typeof p.timeoutMs === "number" ? p.timeoutMs : DEFAULT_EXEC_APPROVAL_TIMEOUT_MS;
const explicitId = normalizeOptionalString(p.id) ?? null;
const host = normalizeOptionalString(p.host) ?? "";
const nodeId = normalizeOptionalString(p.nodeId) ?? "";
const approvalContext = resolveSystemRunApprovalRequestContext({
host,
command: p.command,
commandArgv: p.commandArgv,
systemRunPlan: p.systemRunPlan,
cwd: p.cwd,
agentId: p.agentId,
sessionKey: p.sessionKey
});
const effectiveCommandArgv = approvalContext.commandArgv;
const effectiveCwd = approvalContext.cwd;
const effectiveAgentId = approvalContext.agentId;
const effectiveSessionKey = approvalContext.sessionKey;
const effectiveCommandText = approvalContext.commandText;
if (host === "node" && !nodeId) {
respond(false, void 0, errorShape(ErrorCodes.INVALID_REQUEST, "nodeId is required for host=node"));
return;
}
if (host === "node" && !approvalContext.plan) {
respond(false, void 0, errorShape(ErrorCodes.INVALID_REQUEST, "systemRunPlan is required for host=node"));
return;
}
if (effectiveCommandText.trim().length === 0) {
respond(false, void 0, errorShape(ErrorCodes.INVALID_REQUEST, "command is required"));
return;
}
if (explicitId?.startsWith(RESERVED_PLUGIN_APPROVAL_ID_PREFIX)) {
respond(false, void 0, errorShape(ErrorCodes.INVALID_REQUEST, `approval ids starting with ${RESERVED_PLUGIN_APPROVAL_ID_PREFIX} are reserved`));
return;
}
if (host === "node" && (!Array.isArray(effectiveCommandArgv) || effectiveCommandArgv.length === 0)) {
respond(false, void 0, errorShape(ErrorCodes.INVALID_REQUEST, "commandArgv is required for host=node"));
return;
}
const envBinding = buildSystemRunApprovalEnvBinding(p.env);
const warningText = normalizeOptionalString(p.warningText);
const commandHighlighting = resolveExecCommandHighlighting({
config: typeof context.getRuntimeConfig === "function" ? context.getRuntimeConfig() : {},
agentId: effectiveAgentId
});
const sanitizedCommandDisplay = sanitizeExecApprovalDisplayTextWithStatus(effectiveCommandText);
if (sanitizedCommandDisplay.truncated || sanitizedCommandDisplay.oversized) {
respond(false, void 0, errorShape(ErrorCodes.INVALID_REQUEST, "command exceeds exec approval display limit", { details: { reason: "EXEC_APPROVAL_COMMAND_DISPLAY_LIMIT" } }));
return;
}
const sanitizedCommandText = sanitizedCommandDisplay.text;
const commandAnalysis = resolveCommandAnalysisSummaryForDisplay({
host,
commandText: effectiveCommandText,
commandArgv: effectiveCommandArgv,
cwd: effectiveCwd,
sanitizeText: sanitizeExecApprovalWarningText
});
const commandSpans = commandHighlighting && sanitizedCommandText === effectiveCommandText ? normalizeCommandSpans(p.commandSpans, sanitizedCommandText.length) : void 0;
const systemRunBinding = host === "node" ? buildSystemRunApprovalBinding({
argv: effectiveCommandArgv,
cwd: effectiveCwd,
agentId: effectiveAgentId,
sessionKey: effectiveSessionKey,
env: p.env
}) : null;
if (explicitId && manager.getSnapshot(explicitId)) {
respond(false, void 0, errorShape(ErrorCodes.INVALID_REQUEST, "approval id already pending"));
return;
}
const request = {
command: sanitizedCommandText,
commandPreview: host === "node" || !approvalContext.commandPreview ? void 0 : sanitizeExecApprovalDisplayText(approvalContext.commandPreview),
commandArgv: host === "node" ? void 0 : effectiveCommandArgv,
envKeys: envBinding.envKeys.length > 0 ? envBinding.envKeys : void 0,
systemRunBinding: systemRunBinding?.binding ?? null,
systemRunPlan: approvalContext.plan,
cwd: effectiveCwd ?? null,
nodeId: host === "node" ? nodeId : null,
host: host || null,
security: p.security ?? null,
ask: p.ask ?? null,
warningText: warningText ? sanitizeExecApprovalWarningText(warningText) : null,
commandAnalysis,
commandSpans,
allowedDecisions: resolveExecApprovalAllowedDecisions({ ask: p.ask ?? null }),
agentId: effectiveAgentId ?? null,
resolvedPath: p.resolvedPath ?? null,
sessionKey: effectiveSessionKey ?? null,
turnSourceChannel: normalizeOptionalString(p.turnSourceChannel) ?? null,
turnSourceTo: normalizeOptionalString(p.turnSourceTo) ?? null,
turnSourceAccountId: normalizeOptionalString(p.turnSourceAccountId) ?? null,
turnSourceThreadId: p.turnSourceThreadId ?? null
};
const record = manager.create(request, timeoutMs, explicitId);
bindApprovalRequesterMetadata({
record,
client
});
const decisionPromise = registerPendingApprovalRecord({
manager,
record,
timeoutMs,
respond
});
if (!decisionPromise) return;
const requestEvent = buildRequestedApprovalEvent(record);
await handlePendingApprovalRequest({
manager,
record,
decisionPromise,
respond,
context,
clientConnId: client?.connId,
requestEventName: "exec.approval.requested",
requestEvent,
twoPhase,
approvalKind: "exec",
requireDeliveryRoute: p.requireDeliveryRoute,
suppressDelivery: p.suppressDelivery,
deliverRequest: () => {
const deliveryTasks = [];
if (opts?.forwarder) deliveryTasks.push(opts.forwarder.handleRequested(requestEvent).catch((err) => {
context.logGateway?.error?.(`exec approvals: forward request failed: ${String(err)}`);
return false;
}));
if (opts?.iosPushDelivery?.handleRequested) deliveryTasks.push(opts.iosPushDelivery.handleRequested(requestEvent, { isTargetVisible: (target) => isApprovalRecordVisibleToClient({
record,
client: { connect: {
client: { id: GATEWAY_CLIENT_IDS.IOS_APP },
device: { id: target.deviceId },
scopes: [...target.scopes]
} }
}) }).catch((err) => {
context.logGateway?.error?.(`exec approvals: iOS push request failed: ${String(err)}`);
return false;
}));
if (deliveryTasks.length === 0) return false;
return (async () => {
let delivered = false;
for (const task of deliveryTasks) delivered = await task || delivered;
return delivered;
})();
},
afterDecision: async (decision) => {
if (decision === null) await opts?.iosPushDelivery?.handleExpired?.(requestEvent);
},
afterDecisionErrorLabel: "exec approvals: iOS push expire failed"
});
},
"exec.approval.waitDecision": async ({ params, respond, client }) => {
await handleApprovalWaitDecision({
manager,
inputId: params.id,
client,
respond
});
},
"exec.approval.resolve": async ({ params, respond, client, context }) => {
const resolveParams = resolveApprovalDecisionParams({
rawParams: params,
validate: validateExecApprovalResolveParams,
methodName: "exec.approval.resolve",
respond
});
if (!resolveParams) return;
const { inputId, decision } = resolveParams;
await handleApprovalResolve({
manager,
inputId,
decision,
respond,
context,
client,
exposeAmbiguousPrefixError: true,
validateDecision: (snapshot) => {
return resolveExecApprovalRequestAllowedDecisions(snapshot.request).includes(decision) ? null : {
message: "allow-always is unavailable because the effective policy requires approval every time",
details: APPROVAL_ALLOW_ALWAYS_UNAVAILABLE_DETAILS
};
},
resolvedEventName: "exec.approval.resolved",
buildResolvedEvent: ({ approvalId, decision: decisionLocal, resolvedBy, snapshot, nowMs }) => ({
id: approvalId,
decision: decisionLocal,
resolvedBy,
ts: nowMs,
request: snapshot.request
}),
forwardResolved: (resolvedEvent) => opts?.forwarder?.handleResolved(resolvedEvent),
forwardResolvedErrorLabel: "exec approvals: forward resolve failed",
extraResolvedHandlers: opts?.iosPushDelivery?.handleResolved ? [{
run: (resolvedEvent) => opts.iosPushDelivery.handleResolved(resolvedEvent),
errorLabel: "exec approvals: iOS push resolve failed"
}] : void 0
});
}
};
}
//#endregion
export { createExecApprovalHandlers };