UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

344 lines (343 loc) 13.3 kB
import { r as defaultRuntime } from "./runtime-CF2WjnNZ.js"; import { s as readConfigFileSnapshot } from "./io.runtime-B9iJRs3w.js"; import { n as sanitizeTerminalText } from "./safe-text-BGBqp1a4.js"; import { r as replaceConfigFile } from "./mutate-ZNN4iFCn.js"; import "./config-Cs0XXL3x.js"; import { t as formatDocsLink } from "./links-ClIwBcy4.js"; import { n as isRich, r as theme } from "./theme-vjDs9tao.js"; import { _ as normalizeExecSecurity, a as maxAsk, b as resolveExecModeFromPolicy, g as normalizeExecMode, m as normalizeExecAsk, o as minSecurity, v as normalizeExecTarget, x as resolveExecModePolicy } from "./exec-approvals-policy-CCFUzTzd.js"; import { n as sanitizeExecApprovalDisplayText } from "./exec-approval-text-sanitize-C1BzZQH8.js"; import { i as resolveExecApprovalsFromFile } from "./exec-approvals-BSZ-fPIY.js"; import { d as updateExecApprovals, l as restoreExecApprovalsSnapshotLocked, o as readExecApprovalsSnapshot } from "./exec-approvals-store-DBR0neS0.js"; import { n as renderTable, t as getTerminalTableWidth } from "./table-DzN9OC44.js"; import { n as collectExecPolicyScopeSnapshots, t as SESSION_EXEC_OVERRIDES_NOTE } from "./exec-approvals-effective-DGNgYRIx.js"; //#region src/cli/exec-policy-cli.ts const EXEC_POLICY_PRESETS = { yolo: { host: "gateway", security: "full", ask: "off", askFallback: "full" }, cautious: { host: "gateway", security: "allowlist", ask: "on-miss", askFallback: "deny" }, "deny-all": { host: "gateway", security: "deny", ask: "off", askFallback: "deny" } }; function failExecPolicy(message) { throw new Error(message); } function formatExecPolicyError(err) { return sanitizeExecPolicyMessage(err instanceof Error ? err.message : String(err)); } async function runExecPolicyAction(action) { try { await action(); } catch (err) { defaultRuntime.error(formatExecPolicyError(err)); defaultRuntime.exit(1); } } function sanitizeExecPolicyTableCell(value) { return sanitizeExecApprovalDisplayText(sanitizeTerminalText(value)); } function sanitizeExecPolicyMessage(value) { return sanitizeTerminalText(String(value)); } function resolveExecPolicyInput(params) { const resolved = {}; if (params.host !== void 0) { const host = normalizeExecTarget(params.host); if (!host) failExecPolicy(`Invalid exec host: ${sanitizeExecPolicyMessage(params.host)}`); resolved.host = host; } if (params.security !== void 0) { const security = normalizeExecSecurity(params.security); if (!security) failExecPolicy(`Invalid exec security: ${sanitizeExecPolicyMessage(params.security)}`); resolved.security = security; } if (params.ask !== void 0) { const ask = normalizeExecAsk(params.ask); if (!ask) failExecPolicy(`Invalid exec ask mode: ${sanitizeExecPolicyMessage(params.ask)}`); resolved.ask = ask; } if (params.askFallback !== void 0) { const askFallback = normalizeExecSecurity(params.askFallback); if (!askFallback) failExecPolicy(`Invalid exec askFallback: ${sanitizeExecPolicyMessage(params.askFallback)}`); resolved.askFallback = askFallback; } return resolved; } function applyConfigExecPolicy(draft, policy) { const root = draft; root.tools ??= {}; root.tools.exec ??= {}; if (policy.host !== void 0) root.tools.exec.host = policy.host; if (policy.security !== void 0 || policy.ask !== void 0) { const currentPolicy = resolveExecModePolicy({ mode: normalizeExecMode(root.tools.exec.mode), security: root.tools.exec.security ?? "full", ask: root.tools.exec.ask ?? "off" }); const security = policy.security ?? currentPolicy.security; const ask = policy.ask ?? currentPolicy.ask; if (ask === "always" || security === "full" && ask === "on-miss") { delete root.tools.exec.mode; root.tools.exec.security = security; root.tools.exec.ask = ask; } else { root.tools.exec.mode = resolveExecModeFromPolicy({ security, ask }); delete root.tools.exec.security; delete root.tools.exec.ask; } } } function applyApprovalsDefaults(file, policy) { const next = structuredClone(file ?? { version: 1 }); next.version = 1; next.defaults ??= {}; if (policy.security !== void 0) next.defaults.security = policy.security; if (policy.ask !== void 0) next.defaults.ask = policy.ask; if (policy.askFallback !== void 0) next.defaults.askFallback = policy.askFallback; return next; } function buildExecPolicyApprovalsRollback(params) { const fields = [ ["security", params.policy.security], ["ask", params.policy.ask], ["askFallback", params.policy.askFallback] ]; const originalDefaults = resolveExecApprovalsFromFile({ file: params.original }).defaults; const currentDefaults = resolveExecApprovalsFromFile({ file: params.current }).defaults; const next = structuredClone(params.current); let changed = false; for (const [field, appliedValue] of fields) { const currentValue = params.current.defaults?.[field]; const originalValue = params.original.defaults?.[field]; const rollbackDoesNotLoosen = field === "ask" ? maxAsk(originalDefaults.ask, currentDefaults.ask) === originalDefaults.ask : minSecurity(originalDefaults[field], currentDefaults[field]) === originalDefaults[field]; if (appliedValue !== void 0 && currentValue === params.written.defaults?.[field] && currentValue !== originalValue && rollbackDoesNotLoosen) { next.defaults = { ...next.defaults, [field]: originalValue }; changed = true; } } return changed ? next : null; } function buildNextExecPolicyConfig(config, policy) { const draft = structuredClone(config); applyConfigExecPolicy(draft, policy); return draft; } async function buildLocalExecPolicyShowPayload() { const configSnapshot = await readConfigFileSnapshot(); const approvalsSnapshot = readExecApprovalsSnapshot(); const scopes = collectExecPolicyScopeSnapshots({ cfg: configSnapshot.config ?? {}, approvals: approvalsSnapshot.file, hostPath: approvalsSnapshot.path }).map(buildExecPolicyShowScope); const baseNote = scopes.some((scope) => scope.runtimeApprovalsSource === "node-runtime") ? "Scopes requesting host=node are node-managed at runtime. Local approvals are shown only for local/gateway scopes." : "Effective exec policy is the host approvals policy intersected with requested tools.exec policy."; return { configPath: configSnapshot.path, approvalsPath: approvalsSnapshot.path, approvalsExists: approvalsSnapshot.exists, effectivePolicy: { note: `${baseNote} ${SESSION_EXEC_OVERRIDES_NOTE}`, scopes } }; } function buildExecPolicyShowScope(snapshot) { const { allowedDecisions: _allowedDecisions, ...baseScope } = snapshot; if (snapshot.host.requested !== "node") return { ...baseScope, runtimeApprovalsSource: "local-file" }; return { ...baseScope, runtimeApprovalsSource: "node-runtime", security: { requested: snapshot.security.requested, requestedSource: snapshot.security.requestedSource, host: "unknown", hostSource: "node runtime approvals", effective: "unknown", note: "runtime policy resolved by node approvals" }, ask: { requested: snapshot.ask.requested, requestedSource: snapshot.ask.requestedSource, host: "unknown", hostSource: "node runtime approvals", effective: "unknown", note: "runtime policy resolved by node approvals" }, askFallback: { effective: "unknown", source: "node runtime approvals" } }; } function renderExecPolicyShow(payload) { const rich = isRich(); const heading = (text) => rich ? theme.heading(text) : text; const muted = (text) => rich ? theme.muted(text) : text; defaultRuntime.log(heading("Exec Policy")); defaultRuntime.log(renderTable({ width: getTerminalTableWidth(), columns: [{ key: "Field", header: "Field", minWidth: 14 }, { key: "Value", header: "Value", minWidth: 24, flex: true }], rows: [ { Field: "Config", Value: sanitizeExecPolicyTableCell(payload.configPath) }, { Field: "Approvals", Value: sanitizeExecPolicyTableCell(payload.approvalsPath) }, { Field: "Approvals State", Value: sanitizeExecPolicyTableCell(payload.approvalsExists ? "stored" : "defaults (no stored overrides)") } ] }).trimEnd()); defaultRuntime.log(""); defaultRuntime.log(heading("Effective Policy")); defaultRuntime.log(renderTable({ width: getTerminalTableWidth(), columns: [ { key: "Scope", header: "Scope", minWidth: 12 }, { key: "Requested", header: "Requested", minWidth: 24, flex: true }, { key: "Host", header: "Host", minWidth: 24, flex: true }, { key: "Effective", header: "Effective", minWidth: 16 } ], rows: payload.effectivePolicy.scopes.map((scope) => ({ Scope: sanitizeExecPolicyTableCell(scope.scopeLabel), Requested: sanitizeExecPolicyTableCell(`host=${scope.host.requested} (${scope.host.requestedSource})\nsecurity=${scope.security.requested} (${scope.security.requestedSource})\nask=${scope.ask.requested} (${scope.ask.requestedSource})`), Host: sanitizeExecPolicyTableCell(`security=${scope.security.host} (${scope.security.hostSource})\nask=${scope.ask.host} (${scope.ask.hostSource})\naskFallback=${scope.askFallback.effective} (${scope.askFallback.source})`), Effective: sanitizeExecPolicyTableCell(`security=${scope.security.effective}\nask=${scope.ask.effective}`) })) }).trimEnd()); defaultRuntime.log(""); defaultRuntime.log(muted(payload.effectivePolicy.note)); } async function applyLocalExecPolicy(policy) { const configSnapshot = await readConfigFileSnapshot(); const nextConfig = buildNextExecPolicyConfig(configSnapshot.config ?? {}, policy); if (nextConfig.tools?.exec?.host === "node") failExecPolicy("Local exec-policy cannot synchronize host=node. Node approvals are fetched from the node at runtime."); const approvalsSnapshot = readExecApprovalsSnapshot(); const nextApprovals = applyApprovalsDefaults(approvalsSnapshot.file, policy); const writtenApprovals = await updateExecApprovals({ baseHash: approvalsSnapshot.hash, update: () => nextApprovals }); if (!writtenApprovals) throw new Error("Exec approvals changed; reload and retry."); try { await replaceConfigFile({ baseHash: configSnapshot.hash, nextConfig }); } catch (err) { try { if (!await restoreExecApprovalsSnapshotLocked(approvalsSnapshot, writtenApprovals.hash)) await updateExecApprovals({ update: (current) => buildExecPolicyApprovalsRollback({ current, original: approvalsSnapshot.file, written: writtenApprovals.file, policy }) }); } catch (rollbackError) { throw new Error(`Config update failed: ${formatExecPolicyError(err)}; exec approvals rollback failed: ${formatExecPolicyError(rollbackError)}`, { cause: rollbackError }); } throw err; } return await buildLocalExecPolicyShowPayload(); } function registerExecPolicyCli(program) { const execPolicy = program.command("exec-policy").description("Show or synchronize requested exec policy with host approvals").addHelpText("after", () => `\n${theme.muted("Docs:")} ${formatDocsLink("/cli/approvals", "docs.openclaw.ai/cli/approvals")}\n`); execPolicy.command("show").description("Show the local config policy, host approvals, and effective merge").option("--json", "Output as JSON", false).action(async (opts) => { await runExecPolicyAction(async () => { const payload = await buildLocalExecPolicyShowPayload(); if (opts.json) { defaultRuntime.writeJson(payload, 0); return; } renderExecPolicyShow(payload); }); }); execPolicy.command("preset <name>").description("Apply a synchronized preset: \"yolo\", \"cautious\", or \"deny-all\"").option("--json", "Output as JSON", false).action(async (name, opts) => { await runExecPolicyAction(async () => { if (!Object.hasOwn(EXEC_POLICY_PRESETS, name)) failExecPolicy(`Unknown exec-policy preset: ${sanitizeExecPolicyMessage(name)}`); const preset = EXEC_POLICY_PRESETS[name]; const payload = await applyLocalExecPolicy(preset); if (opts.json) { defaultRuntime.writeJson({ preset: name, ...payload }, 0); return; } defaultRuntime.log(`Applied exec-policy preset: ${sanitizeExecPolicyMessage(name)}`); defaultRuntime.log(""); renderExecPolicyShow(payload); }); }); execPolicy.command("set").description("Synchronize local config and host approvals using explicit values").option("--host <host>", "Exec host target: auto|sandbox|gateway|node").option("--security <mode>", "Exec security: deny|allowlist|full").option("--ask <mode>", "Exec ask mode: off|on-miss|always").option("--ask-fallback <mode>", "Host approvals fallback: deny|allowlist|full").option("--json", "Output as JSON", false).action(async (opts) => { await runExecPolicyAction(async () => { const policy = resolveExecPolicyInput(opts); if (Object.keys(policy).length === 0) failExecPolicy("Provide at least one of --host, --security, --ask, or --ask-fallback."); const payload = await applyLocalExecPolicy(policy); if (opts.json) { defaultRuntime.writeJson({ applied: policy, ...payload }, 0); return; } defaultRuntime.log("Synchronized local exec policy."); defaultRuntime.log(""); renderExecPolicyShow(payload); }); }); } //#endregion export { registerExecPolicyCli };