UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

282 lines (281 loc) • 13.3 kB
import { n as coerceErrorMessage } from "./error-coercion-W9BtigRm.mjs"; import "./src-BirUqOZp.mjs"; import { i as executeSqliteQuerySync, o as getNodeSqliteKysely, t as compileSqliteQueryBindings } from "./kysely-sync-DUWzQvCU.mjs"; import { i as tableExists } from "./openclaw-state-db-schema-helpers-DKz5xGgh.mjs"; import { l as runOpenClawStateWriteTransaction, r as openOpenClawStateDatabase } from "./openclaw-state-db-DJZ7EKoI.mjs"; import { r as captureOpenClawStateWorkerContext } from "./openclaw-state-worker-context-BvvgY6-t.mjs"; import { n as executeOpenClawStateWorker } from "./openclaw-state-worker-store-BxZXTKna.mjs"; import { t as canonicalizeConfiguredMcpServer } from "./mcp-config-normalize-BxEbywQ8.mjs"; import { t as assertClawPackageLifecycleWriteArtifact } from "./claw-package-lifecycle-lease-C_2EPExP.mjs"; import { n as digestClawValue } from "./digest-CI8Suvd7.mjs"; import { n as runWithOpenClawStateLeaseWorker } from "./openclaw-state-lease-worker-operation-i-E-VJQz.mjs"; import { t as listConfiguredMcpServers } from "./mcp-config-BWr2aT9g.mjs"; import { a as withClawMcpLifecycleLease, t as setConfiguredMcpServer } from "./mcp-config-mutation-B1WU9C8H.mjs"; import { i as selectMcpRefs, n as refToRow, r as rowToRef, t as CLAW_MCP_REF_SCHEMA_VERSION } from "./mcp-records-CU_-ZS8k.mjs"; import { relative } from "node:path"; import { isPathRelativeEscape } from "@openclaw/fs-safe/path"; //#region src/claws/types.ts const CLAW_ADD_PLAN_SCHEMA_VERSION = "openclaw.clawAddPlan.v1"; const CLAW_INSPECT_RESULT_SCHEMA_VERSION = "openclaw.clawInspect.v1"; const CLAW_OUTPUT_STABILITY = "experimental"; //#endregion //#region src/claws/provenance-write.ts async function claimClawPackageRefStatus(ref, status, options) { if (options.readOnly) throw new Error("Claw provenance writes require writable state."); const capturedRef = structuredClone(ref); const nowMs = options.nowMs; assertClawPackageLifecycleWriteArtifact(options.lease, capturedRef); if (options.deletion) return await options.deletion.runWithWorker((scope, deletion, additionalLeases) => { const lease = additionalLeases[0]; if (!lease) throw new Error("Claw package write lost its additional lease"); return scope.execute({ type: "clawProvenance.packageStatus", input: { ref: capturedRef, status, nowMs, lease, deletion } }); }, { additionalLeases: [options.lease], assertCurrent: options.assertCurrent }); const assertCaller = options.assertCurrent?.bind(options); const context = captureOpenClawStateWorkerContext({ ...options, path: options.database?.path ?? options.path }); const assertCurrent = () => { context.admission.assertCurrent(); assertCaller?.(); }; const result = await runWithOpenClawStateLeaseWorker(options.lease, context, (scope, identity) => scope.execute({ type: "clawProvenance.packageStatus", input: { ref: capturedRef, status, nowMs, lease: identity } }), { assertCurrent }); assertCurrent(); return result; } function reconcileClawMcpServerRefsInWorker(agentId, digests, options) { if (options.readOnly) throw new Error("Claw provenance writes require writable state."); const context = captureOpenClawStateWorkerContext({ ...options, path: options.database?.path ?? options.path }); return executeOpenClawStateWorker(context, { type: "clawProvenance.reconcileMcp", input: { agentId, digests, nowMs: options.nowMs } }); } //#endregion //#region src/claws/mcp.ts var ClawMcpInstallError = class extends Error { constructor(code, message, mcpServers) { super(message); this.code = code; this.mcpServers = mcpServers; this.name = "ClawMcpInstallError"; } }; function mcpServerFromActionDetails(details) { const { expectedState: _expectedState, prerequisites: _prerequisites, ...server } = details; return "command" in server || "url" in server ? server : void 0; } function digestClawMcpServer(server) { return digestClawValue(canonicalizeConfiguredMcpServer(server)); } function persistPendingRef(plan, name, server, ownership, options) { const nowMs = options.nowMs ?? Date.now(); const configDigest = digestClawMcpServer(server); const database = openOpenClawStateDatabase(options); const { compiled, bind } = compileSqliteQueryBindings((parameter) => selectMcpRefs(database.db).where("agent_id", "=", parameter((value) => value.agentId)).where("name", "=", parameter((value) => value.name))); const existing = database.db.prepare(compiled.sql).get(...bind({ agentId: plan.agent.finalId, name })); if (existing) { const ref = rowToRef(existing); if (ref.configDigest !== configDigest || ref.status === "failed") throw new ClawMcpInstallError("mcp_provenance_conflict", `MCP server ${JSON.stringify(name)} differs from its ownership record.`, [ref]); return ref; } const ref = { schemaVersion: CLAW_MCP_REF_SCHEMA_VERSION, agentId: plan.agent.finalId, name, configDigest, ...ownership, status: "pending", createdAtMs: nowMs, updatedAtMs: nowMs }; runOpenClawStateWriteTransaction(({ db }) => { executeSqliteQuerySync(db, getNodeSqliteKysely(db).insertInto("claw_mcp_server_refs").values(refToRow(ref))); }, options); return ref; } function updateRef(ref, update, options) { const updated = { ...ref, ...update, updatedAtMs: options.nowMs ?? Date.now() }; runOpenClawStateWriteTransaction(({ db }) => { executeSqliteQuerySync(db, getNodeSqliteKysely(db).updateTable("claw_mcp_server_refs").set({ status: update.status, error: update.error ?? null, updated_at_ms: updated.updatedAtMs }).where("agent_id", "=", ref.agentId).where("name", "=", ref.name)); }, options); return updated; } async function installClawMcpServers(plan, options = {}) { const setMcpServer = options.setMcpServer ?? setConfiguredMcpServer; const listMcpServers = options.listMcpServers ?? listConfiguredMcpServers; const refs = []; for (const action of plan.actions.filter((candidate) => candidate.kind === "mcpServer")) await withClawMcpLifecycleLease(action.id, options, async () => { const server = action.details ? mcpServerFromActionDetails(action.details) : void 0; if (!server) throw new ClawMcpInstallError("mcp_plan_invalid", `MCP server action ${JSON.stringify(action.id)} is invalid.`, refs); const listed = await listMcpServers(); if (!listed.ok) throw new ClawMcpInstallError("mcp_preflight_failed", listed.error, refs); const configured = listed.mcpServers[action.id]; const configDigest = digestClawMcpServer(server); if (configured && digestClawMcpServer(configured) !== configDigest) throw new ClawMcpInstallError("mcp_config_conflict", `MCP server ${JSON.stringify(action.id)} already exists with different configuration.`, refs); const existingRefs = readClawMcpServerRefsByName(action.id, options); const inheritsClawOrigin = existingRefs.length > 0 && existingRefs.every((candidate) => candidate.origin === "claw-introduced" && !candidate.independentOwner); const ownership = configured ? { relationship: "referenced", origin: inheritsClawOrigin ? "claw-introduced" : "pre-existing", independentOwner: !inheritsClawOrigin } : { relationship: "managed", origin: "claw-introduced", independentOwner: false }; let pending = persistPendingRef(plan, action.id, server, ownership, options); refs.push(pending); if (pending.status === "complete") { if (configured) return; const hasSiblingOwner = readClawMcpServerRefsByName(action.id, options).some((candidate) => candidate.agentId !== plan.agent.finalId); if (pending.relationship !== "managed" || pending.origin !== "claw-introduced" || pending.independentOwner || hasSiblingOwner) throw new ClawMcpInstallError("mcp_reconcile_conflict", `MCP server ${JSON.stringify(action.id)} was removed while shared or independently owned and will not be recreated.`, refs); pending = updateRef(pending, { status: "pending" }, options); refs[refs.length - 1] = pending; } if (configured) { refs[refs.length - 1] = updateRef(pending, { status: "complete" }, options); return; } let result; try { result = await setMcpServer({ name: action.id, server, createOnly: true, recordIndependentOwner: false }); } catch (error) { throw new ClawMcpInstallError("mcp_install_uncertain", coerceErrorMessage(error), refs); } if (!result.ok) { refs[refs.length - 1] = updateRef(pending, { status: "failed", error: result.error }, options); throw new ClawMcpInstallError("mcp_install_failed", result.error, refs); } try { refs[refs.length - 1] = updateRef(pending, { status: "complete" }, options); } catch (error) { throw new ClawMcpInstallError("mcp_provenance_failed", `MCP server was configured, but ownership could not be persisted: ${coerceErrorMessage(error)}`, refs); } }); return refs; } function readMcpRefsByIdentity(column, value, options) { const { db } = openOpenClawStateDatabase(options); if (options.readOnly && !tableExists(db, "claw_mcp_server_refs")) return []; const { compiled, bind } = compileSqliteQueryBindings((parameter) => selectMcpRefs(db).where(column, "=", parameter((identity) => identity)).orderBy(column === "agent_id" ? "name" : "agent_id")); return db.prepare(compiled.sql).all(...bind(value)).map(rowToRef); } function readClawMcpServerRefs(agentId, options = {}) { return readMcpRefsByIdentity("agent_id", agentId, options); } function readClawMcpServerRefsByName(name, options = {}) { return readMcpRefsByIdentity("name", name, options); } function clawMcpRemovalSelector(ref) { return `mcp:${ref.name}`; } function planClawMcpServerRemoval(ref, options = {}) { const affectedClawAgentIds = readClawMcpServerRefsByName(ref.name, options).filter((candidate) => candidate.agentId !== ref.agentId).map((candidate) => candidate.agentId).toSorted(); const cleanup = options.referencedCleanup ?? { mode: "retain" }; const explicitlySelected = cleanup.mode === "remove-selected" && (cleanup.selected ?? []).includes(clawMcpRemovalSelector(ref)); const conflicts = affectedClawAgentIds.length > 0 || ref.independentOwner || ref.origin === "pre-existing"; const release = (reason, blocked = false) => ({ ref, action: "release", blocked, affectedClawAgentIds, reason }); if (ref.relationship === "managed") { if (explicitlySelected) return release("--remove-referenced only accepts resources with a referenced relationship.", true); if (affectedClawAgentIds.length > 0) return release("Another Claw still references this MCP server."); if (ref.independentOwner) return release("MCP server has a current non-Claw owner."); return { ref, action: "remove", blocked: false, affectedClawAgentIds }; } if (!explicitlySelected && cleanup.mode !== "remove-if-unused") return release("Referenced resources are retained unless a cleanup mode selects them."); if (!explicitlySelected && conflicts) return release(affectedClawAgentIds.length > 0 ? "Another Claw still references this MCP server." : "MCP server has a current non-Claw owner or pre-existing origin."); if (explicitlySelected && conflicts && !cleanup.allowConflicts) return release("Selected MCP server has other Claw dependents, a non-Claw owner, or pre-existing origin; explicit conflict override is required.", true); return { ref, action: "remove", blocked: false, affectedClawAgentIds }; } function reconcileClawMcpServerRefs(agentId, configuredServers, options = {}) { return reconcileClawMcpServerRefsInWorker(agentId, Object.fromEntries(Object.entries(configuredServers).map(([name, server]) => [name, digestClawMcpServer(server)])), options); } function deleteClawMcpServerRef(agentId, name, options = {}) { runOpenClawStateWriteTransaction(({ db }) => { executeSqliteQuerySync(db, getNodeSqliteKysely(db).deleteFrom("claw_mcp_server_refs").where("agent_id", "=", agentId).where("name", "=", name)); }, options); } function upsertClawMcpServerRef(ref, options = {}) { runOpenClawStateWriteTransaction(({ db }) => { executeSqliteQuerySync(db, getNodeSqliteKysely(db).insertInto("claw_mcp_server_refs").values(refToRow(ref)).onConflict((conflict) => conflict.columns(["agent_id", "name"]).doUpdateSet((eb) => ({ schema_version: eb.ref("excluded.schema_version"), config_digest: eb.ref("excluded.config_digest"), relationship: eb.ref("excluded.relationship"), origin: eb.ref("excluded.origin"), independent_owner: eb.ref("excluded.independent_owner"), status: eb.ref("excluded.status"), error: eb.ref("excluded.error"), updated_at_ms: eb.ref("excluded.updated_at_ms") })))); }, options); } //#endregion //#region src/claws/path-containment.ts function clawContainedRelativePath(root, target) { const child = relative(root, target); return child !== "" && !isPathRelativeEscape(child) ? child : void 0; } //#endregion export { digestClawMcpServer as a, readClawMcpServerRefs as c, upsertClawMcpServerRef as d, claimClawPackageRefStatus as f, CLAW_OUTPUT_STABILITY as h, deleteClawMcpServerRef as i, readClawMcpServerRefsByName as l, CLAW_INSPECT_RESULT_SCHEMA_VERSION as m, ClawMcpInstallError as n, installClawMcpServers as o, CLAW_ADD_PLAN_SCHEMA_VERSION as p, clawMcpRemovalSelector as r, planClawMcpServerRemoval as s, clawContainedRelativePath as t, reconcileClawMcpServerRefs as u };