UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

956 lines (955 loc) • 41.9 kB
import { t as assertExperimentalClawsEnabled } from "./experimental-BMzbGmT5.mjs"; import { t as coerceErrorMessage } from "./error-coercion-CvJK4lmH.mjs"; import { o as resolveUserPath } from "./home-dir-Dc6nN6HB.mjs"; import "./src-CdRlGRnW.mjs"; import { t as stableStringify } from "./stable-stringify-Djn3uxNo.mjs"; import { n as filterStringEntries } from "./string-normalization-CECOqf64.mjs"; import { a as writeRuntimeJson, r as defaultRuntime } from "./runtime-DlO3PTAd.mjs"; import { w as root } from "./fs-safe-BUXBSN-4.mjs"; import { o as redactSensitiveUrlLikeString } from "./redact-sensitive-url-BnHakqqk.mjs"; import { _ as redactSensitiveText } from "./redact-DJ8j6ADa.mjs"; import { n as resolvePathViaExistingAncestorSync } from "./boundary-path-CAv_plX7.mjs"; import "./utils-DgZn2dLu.mjs"; import { a as normalizeWindowsPathForComparison } from "./path-guards-CH7noY0Z.mjs"; import { n as normalizeAgentId } from "./agent-id-D1-Q6vR4.mjs"; import { d as resolveAgentWorkspaceDir, x as toAgentEntriesRecord } from "./agent-scope-config-CDYpzA0r.mjs"; import { t as DEFAULT_AGENT_ID } from "./session-key-C09ow4h0.mjs"; import { i as listAgentIds, n as listAgentEntries } from "./agent-roster-BXD1uMJD.mjs"; import { t as redactSensitiveArgv } from "./redact-argv-BHSPUYQR.mjs"; import "./agent-scope-CG17uXFn.mjs"; import { r as getRuntimeConfig } from "./io.runtime-BsUXSsD4.mjs"; import { o as transformConfigFileWithRetry } from "./mutate-DJq1JylX.mjs"; import "./config-DLycWFvB.mjs"; import { n as callGatewayFromCli } from "./gateway-rpc-0aouJxQt.mjs"; import { a as recordAgentProvenance } from "./agent-provenance-H6O0H8k6.mjs"; import { n as DEFAULT_BOOTSTRAP_FILENAME } from "./workspace-bootstrap-policy-CPdXB_so.mjs"; import { t as MAX_WORKSPACE_BOOTSTRAP_FILE_BYTES } from "./workspace-bootstrap-read-B5B8MlNt.mjs"; import { u as seedWorkspaceBootstrap } from "./workspace-BHjQqjjT.mjs"; import { n as resolveCronJobsStorePathFromConfig, t as resolveCronJobsStorePath } from "./paths-C7ju4So0.mjs"; import { l as digestClawBytes } from "./schema-portability-DPQGHUzw.mjs"; import { t as applyAgentConfig } from "./agents.config-CEaxMlw1.mjs"; import { n as readClawManifestFile } from "./reader-tis62fDs.mjs"; import { n as loadCronJobsStoreWithConfigJobsReadOnly } from "./read-only-BnZqzQN0.mjs"; import "./store-ChPMJI5O.mjs"; import { t as listConfiguredMcpServers } from "./mcp-config-tD8qD_3g.mjs"; import { C as installClawCronJobs, a as ClawRemoveError, n as clawMonitorDrainSchema, r as clawMonitorInventorySchema, v as ClawCronInstallError } from "./monitor-cleanup-contract-CjuEG_Vl.mjs"; import { t as findOverlappingWorkspaceAgentIds } from "./agent-delete-safety-DmMgHiSV.mjs"; import { a as installClawMcpServers, f as CLAW_ADD_PLAN_SCHEMA_VERSION, m as CLAW_OUTPUT_STABILITY, p as CLAW_INSPECT_RESULT_SCHEMA_VERSION, t as ClawMcpInstallError } from "./mcp-DOUItsfA.mjs"; import { i as CLAW_REMOVE_RESULT_SCHEMA_VERSION, n as buildClawRemovePlan, r as CLAW_REMOVE_PLAN_SCHEMA_VERSION, t as applyClawRemovePlan } from "./lifecycle-state-DHs_grw5.mjs"; import { C as readClawInstallRecord, O as updateClawInstallRecordStatus, T as readClawPackageRefs, _ as clawContainedRelativePath, b as persistClawInstallRecord, c as readClawStatus, d as createClawWorkspaceFiles, u as ClawWorkspaceWriteError, v as clawInstallRecordMatchesPlan, y as deleteClawInstallRecord } from "./package-remove-plan-ZBjC4Y9P.mjs"; import { c as planClawExtensions, i as logClawExperimentalWarning, n as formatClawDiagnostics, o as buildClawAddPlan, r as logClawAgentConfiguration, s as findClawExtensionPackageCollisions, t as emitClawFailure } from "./claws-cli-output-T3yfpnCk.mjs"; import { i as preflightClawPackage, n as ClawPackageInstallError, o as findResumableIntroducedPluginRequirement, r as installClawPackages, s as readClawResumeStateReadOnly, t as waitUntilGatewayAgentAvailable } from "./claws-cli.gateway-readiness-dtHnW6Z0.mjs"; import { n as ClawExportError, r as exportClawAgent, t as CLAW_EXPORT_RESULT_SCHEMA_VERSION } from "./export-sl87S7si.mjs"; import { t as resolveClawMonitorCleanupBinding } from "./monitor-cleanup-binding-C1Sjco9n.mjs"; import { n as clawPackageRemovalResultSchema } from "./package-remove-contract-CZf8lING.mjs"; import { r as listCronJobsFromGateway } from "./list-jobs-BIQY6N_D.mjs"; import { t as resolvePluginBatchReload } from "./plugins-lifecycle-client-CvQJuvmG.mjs"; import { dirname, resolve } from "node:path"; import { lstat, mkdir, realpath, rmdir } from "node:fs/promises"; //#region src/claws/add-plan-helpers.ts function planWithPackageActions(plan, predicate) { return { ...plan, actions: plan.actions.filter((action) => action.kind !== "package" || predicate(action)) }; } function statusAtLeast(status, phase) { const order = { pending: 0, partial: 0, workspace_ready: 1, config_committed: 2, complete: 3 }; return order[status] >= order[phase]; } function sameCommittedAgent(existingAgent, plan) { return stableStringify(existingAgent) === stableStringify(plan.agent.config); } //#endregion //#region src/claws/bootstrap.ts var ClawBootstrapWriteError = class extends Error { constructor(code, message) { super(message); this.code = code; this.name = "ClawBootstrapWriteError"; } }; async function seedClawPackageBootstrap(plan, options = {}) { const actions = plan.actions.filter((action) => action.kind === "bootstrap"); if (actions.length === 0) return; if (actions.length !== 1) throw new ClawBootstrapWriteError("bootstrap_plan_invalid", "A Claw add plan may contain only one package bootstrap action."); const action = actions[0]; if (!action.source || !action.digest) throw new ClawBootstrapWriteError("bootstrap_plan_invalid", "The package bootstrap action lacks source integrity."); const packageRoot = await realpath(resolve(plan.claw.packageRoot)); const sourcePath = resolve(action.source); const sourceRelative = clawContainedRelativePath(packageRoot, sourcePath); if (!sourceRelative) throw new ClawBootstrapWriteError("bootstrap_source_escape", "BOOTSTRAP.md must remain inside the Claw package."); const read = await (await root(packageRoot)).read(sourceRelative, { hardlinks: "reject", maxBytes: MAX_WORKSPACE_BOOTSTRAP_FILE_BYTES, symlinks: "reject" }); if (resolve(read.realPath) !== sourcePath || digestClawBytes(read.buffer) !== action.digest) throw new ClawBootstrapWriteError("bootstrap_source_changed", "BOOTSTRAP.md changed after consent; run add --dry-run again."); const expectedTarget = resolve(plan.agent.workspace, DEFAULT_BOOTSTRAP_FILENAME); if (resolve(action.target) !== expectedTarget) throw new ClawBootstrapWriteError("bootstrap_target_changed", "The package bootstrap target is not the new agent workspace root."); return (options.seedBootstrap ?? seedWorkspaceBootstrap)({ dir: plan.agent.workspace, content: read.buffer, ...options.nowMs !== void 0 ? { nowMs: options.nowMs } : {}, stateOptions: options }); } //#endregion //#region src/claws/legacy-resume.ts function replaceLegacyCommittedAgent(params) { if (!params.resumePlan || params.resumeRecord?.schemaVersion !== "openclaw.clawInstallRecord.v1" || params.resumeRecord.status === "complete") return; const existingAgent = params.agents.find((agent) => normalizeAgentId(agent.id) === params.normalizedAgentId); if (!existingAgent || !params.matchesPlan(existingAgent, params.resumePlan)) return; return { ...params.config, agents: { ...params.config.agents, entries: toAgentEntriesRecord(params.agents.map((agent) => normalizeAgentId(agent.id) === params.normalizedAgentId ? params.plan.agent.config : agent)) } }; } //#endregion //#region src/claws/add.ts const CLAW_ADD_RESULT_SCHEMA_VERSION = "openclaw.clawAddResult.v1"; var ClawAddMutationError = class extends Error { constructor(code, message) { super(message); this.code = code; this.name = "ClawAddMutationError"; } }; function markInstallStatus(agentId, status, expectedStatuses, options) { (options.updateRecord ?? updateClawInstallRecordStatus)(agentId, status, { ...options, expectedStatuses }); } function clearUnownedInstallRecord(agentId, expectedStatuses, options) { (options.deleteRecord ?? deleteClawInstallRecord)(agentId, { ...options, expectedStatuses }); } function workspacePathKey(value) { return process.platform === "win32" ? normalizeWindowsPathForComparison(value) : value; } function assertWorkspacePathUnchanged(workspace) { const canonicalWorkspace = resolvePathViaExistingAncestorSync(workspace); if (workspacePathKey(canonicalWorkspace) !== workspacePathKey(workspace)) throw new ClawAddMutationError("workspace_path_changed", `Workspace ancestry changed after planning: expected ${JSON.stringify(workspace)}, resolved ${JSON.stringify(canonicalWorkspace)}.`); } async function applyClawAddPlan(plan, options = {}) { if (plan.blockers.length > 0) throw new ClawAddMutationError("plan_blocked", "The Claw add plan contains blockers."); if (options.consentPlanIntegrity !== (options.resumePlan?.planIntegrity ?? plan.planIntegrity)) throw new ClawAddMutationError("plan_integrity_mismatch", "Consent does not match the current Claw add plan; run add --dry-run again."); const persistRecord = options.persistRecord ?? persistClawInstallRecord; let installRecord; try { installRecord = persistRecord(plan, { ...options, status: "pending", expectedExistingRecord: options.resumeRecord, expectedExistingPlan: options.resumePlan, deferLegacyPlanUpgrade: options.resumePlan !== void 0 }); } catch (error) { throw new ClawAddMutationError("provenance_failed", error.message); } let workspaceCreated = false; let configCommitted = false; let workspaceFiles = []; let packages = []; let mcpServers = []; let cronJobs = []; const partialResult = ({ installStatus = "partial", nowMs, ...overrides }) => ({ schemaVersion: CLAW_ADD_RESULT_SCHEMA_VERSION, stability: CLAW_OUTPUT_STABILITY, dryRun: false, mutationAllowed: true, planIntegrity: plan.planIntegrity, status: "partial", claw: plan.claw, agent: plan.agent, workspaceCreated, configCommitted, workspaceFiles, packages, mcpServers, cronJobs, installRecord: { ...installRecord, status: installStatus, updatedAtMs: nowMs ?? Date.now() }, ...overrides }); const workspace = resolve(resolveUserPath(plan.agent.workspace)); const workspacePhaseRecorded = statusAtLeast(installRecord.status, "workspace_ready"); let workspaceState; try { assertWorkspacePathUnchanged(workspace); workspaceState = await lstat(workspace).catch((error) => { if (typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT") return; throw error; }); } catch (error) { clearUnownedInstallRecord(plan.agent.finalId, ["pending", "partial"], options); if (error instanceof ClawAddMutationError) throw error; throw new ClawAddMutationError("workspace_parent_failed", `Could not inspect workspace ${JSON.stringify(workspace)}: ${error.message}`); } if (!workspacePhaseRecorded && workspaceState) { markInstallStatus(plan.agent.finalId, "partial", ["pending", "partial"], options); return partialResult({ workspaceCreated: false, configCommitted: false, packages: [], error: { code: "workspace_collision", message: `Workspace ${JSON.stringify(workspace)} was created after planning.` }, nowMs: options.nowMs }); } if (workspaceState && !workspaceState.isDirectory()) throw new ClawAddMutationError("workspace_collision", `Workspace ${JSON.stringify(workspace)} is no longer a directory.`); workspaceCreated = workspaceState?.isDirectory() ?? false; configCommitted = statusAtLeast(installRecord.status, "config_committed"); const installPackages = options.installPackages ?? installClawPackages; const preserveRecordedPhaseOrMarkPartial = () => { if (workspacePhaseRecorded) return installRecord.status; markInstallStatus(plan.agent.finalId, "partial", ["pending", "partial"], options); return "partial"; }; const hostRequirementPlan = planWithPackageActions(plan, (action) => action.details?.kind === "plugin"); if (hostRequirementPlan.actions.filter((action) => action.kind === "package").length > 0) try { packages = await installPackages(hostRequirementPlan, options); } catch (error) { const packageError = error instanceof ClawPackageInstallError ? error : void 0; const installStatus = preserveRecordedPhaseOrMarkPartial(); return partialResult({ packages: packageError?.installedPackages ?? packages, installStatus, error: { code: packageError?.code ?? "package_install_failed", message: packageError?.message ?? coerceErrorMessage(error) }, nowMs: options.nowMs }); } try { assertWorkspacePathUnchanged(workspace); await mkdir(dirname(workspace), { recursive: true }); assertWorkspacePathUnchanged(workspace); } catch (error) { if (packages.length > 0) return partialResult({ installStatus: preserveRecordedPhaseOrMarkPartial(), error: { code: error instanceof ClawAddMutationError ? error.code : "workspace_parent_failed", message: error instanceof ClawAddMutationError ? error.message : `Could not create parent directory for workspace ${JSON.stringify(workspace)}: ${error.message}` }, nowMs: options.nowMs }); clearUnownedInstallRecord(plan.agent.finalId, ["pending", "partial"], options); if (error instanceof ClawAddMutationError) throw error; throw new ClawAddMutationError("workspace_parent_failed", `Could not create parent directory for workspace ${JSON.stringify(workspace)}: ${error.message}`); } if (!workspaceCreated) { try { await mkdir(workspace); workspaceCreated = true; } catch (error) { markInstallStatus(plan.agent.finalId, "partial", ["pending", "partial"], options); return partialResult({ workspaceCreated: false, configCommitted: false, error: { code: "workspace_collision", message: `Could not create new workspace ${JSON.stringify(workspace)}: ${error.message}` }, nowMs: options.nowMs }); } try { if (!workspacePhaseRecorded) markInstallStatus(plan.agent.finalId, "workspace_ready", [ "pending", "partial", "workspace_ready" ], options); } catch (error) { if (await rmdir(workspace).then(() => true).catch(() => false)) try { clearUnownedInstallRecord(plan.agent.finalId, ["pending", "partial"], options); } catch {} throw new ClawAddMutationError("provenance_failed", error.message); } } try { await (options.seedPackageBootstrap ?? seedClawPackageBootstrap)(plan, { ...options, ...options.nowMs !== void 0 ? { nowMs: options.nowMs } : {} }); } catch (error) { const installStatus = configCommitted ? "config_committed" : "workspace_ready"; markInstallStatus(plan.agent.finalId, installStatus, configCommitted ? ["config_committed"] : ["workspace_ready", "config_committed"], options); return partialResult({ installStatus, error: { code: error instanceof ClawBootstrapWriteError ? error.code : "bootstrap_write_failed", message: coerceErrorMessage(error) }, nowMs: options.nowMs }); } try { await (options.commitConfig ?? (async (transform) => { await transformConfigFileWithRetry({ afterWrite: { mode: "auto" }, transform: (config) => ({ nextConfig: transform(config) }) }); }))((config) => { const existingAgents = listAgentEntries(config); const agentsToPreserve = existingAgents.length > 0 ? existingAgents : [{ id: DEFAULT_AGENT_ID }]; const configWithPreservedAgents = { ...config, agents: { ...config.agents, entries: toAgentEntriesRecord(agentsToPreserve) } }; const normalizedAgentId = normalizeAgentId(plan.agent.finalId); const existingAgent = agentsToPreserve.find((agent) => normalizeAgentId(agent.id) === normalizedAgentId); if (existingAgent) { if (sameCommittedAgent(existingAgent, plan)) return config; const nextConfig = replaceLegacyCommittedAgent({ config: configWithPreservedAgents, agents: agentsToPreserve, normalizedAgentId, plan, resumePlan: options.resumePlan, resumeRecord: options.resumeRecord, matchesPlan: sameCommittedAgent }); if (nextConfig) return nextConfig; throw new ClawAddMutationError("agent_id_collision", "Agent " + JSON.stringify(plan.agent.finalId) + " was created after planning."); } if (findOverlappingWorkspaceAgentIds(configWithPreservedAgents, plan.agent.finalId, workspace).length > 0) throw new ClawAddMutationError("workspace_collision", "Workspace " + JSON.stringify(workspace) + " is already assigned to an agent."); const nextConfig = applyAgentConfig(configWithPreservedAgents, { agentId: normalizedAgentId }); return { ...nextConfig, agents: { ...nextConfig.agents, entries: { ...nextConfig.agents?.entries, ...toAgentEntriesRecord([plan.agent.config]) } } }; }); configCommitted = true; try { recordAgentProvenance(plan.agent.finalId, { createdVia: "claw" }, options); } catch (error) { throw new ClawAddMutationError("provenance_failed", coerceErrorMessage(error)); } if (options.resumePlan && installRecord.schemaVersion === "openclaw.clawInstallRecord.v1") installRecord = persistRecord(plan, { ...options, status: "pending", expectedExistingRecord: options.resumeRecord, expectedExistingPlan: options.resumePlan }); markInstallStatus(plan.agent.finalId, "config_committed", ["workspace_ready", "config_committed"], options); } catch (error) { let installStatus = "workspace_ready"; if (!configCommitted) { if (await rmdir(workspace).then(() => true).catch(() => false)) { workspaceCreated = false; installStatus = "partial"; markInstallStatus(plan.agent.finalId, "partial", ["workspace_ready", "partial"], options); } } return partialResult({ installStatus, error: { code: error instanceof ClawAddMutationError ? error.code : "config_commit_failed", message: coerceErrorMessage(error) }, nowMs: options.nowMs }); } const createFiles = options.createWorkspaceFiles ?? createClawWorkspaceFiles; try { workspaceFiles = await createFiles(plan, options); } catch (error) { const workspaceError = error instanceof ClawWorkspaceWriteError ? error : new ClawWorkspaceWriteError([{ level: "error", code: "workspace_file_io_error", phase: "mutation", path: "$.workspace", message: coerceErrorMessage(error) }], workspaceFiles); markInstallStatus(plan.agent.finalId, "config_committed", ["config_committed"], options); return partialResult({ workspaceFiles: workspaceError.createdFiles, installStatus: "config_committed", nowMs: options.nowMs, error: { code: "workspace_files_failed", message: workspaceError.message, diagnostics: workspaceError.diagnostics } }); } try { const workspacePackagePlan = planWithPackageActions(plan, (action) => action.details?.kind !== "plugin"); if (workspacePackagePlan.actions.filter((action) => action.kind === "package").length > 0) { const workspacePackages = await installPackages(workspacePackagePlan, options); packages = [...packages, ...workspacePackages]; } } catch (error) { const packageError = error instanceof ClawPackageInstallError ? error : void 0; return partialResult({ packages: [...packages, ...packageError?.installedPackages ?? []], installStatus: "config_committed", error: { code: packageError?.code ?? "package_install_failed", message: packageError?.message ?? coerceErrorMessage(error) }, nowMs: options.nowMs }); } const installMcpServers = options.installMcpServers ?? installClawMcpServers; try { mcpServers = await installMcpServers(plan, options); } catch (error) { const mcpError = error instanceof ClawMcpInstallError ? error : void 0; markInstallStatus(plan.agent.finalId, "config_committed", ["config_committed"], options); return partialResult({ mcpServers: mcpError?.mcpServers ?? mcpServers, installStatus: "config_committed", error: { code: mcpError?.code ?? "mcp_install_failed", message: mcpError?.message ?? coerceErrorMessage(error) }, nowMs: options.nowMs }); } const installCronJobs = options.installCronJobs ?? installClawCronJobs; try { cronJobs = await installCronJobs(plan, { ...options, gateway: options.cronGateway }); } catch (error) { const cronError = error instanceof ClawCronInstallError ? error : void 0; markInstallStatus(plan.agent.finalId, "config_committed", ["config_committed"], options); return partialResult({ cronJobs: cronError?.cronJobs ?? cronJobs, installStatus: "config_committed", error: { code: cronError?.code ?? "cron_install_failed", message: cronError?.message ?? coerceErrorMessage(error) }, nowMs: options.nowMs }); } try { markInstallStatus(plan.agent.finalId, "complete", ["config_committed", "complete"], options); return { schemaVersion: CLAW_ADD_RESULT_SCHEMA_VERSION, stability: CLAW_OUTPUT_STABILITY, dryRun: false, mutationAllowed: true, planIntegrity: plan.planIntegrity, status: "complete", claw: plan.claw, agent: plan.agent, workspaceCreated, configCommitted, packages, mcpServers, cronJobs, workspaceFiles, installRecord: { ...installRecord, status: "complete", updatedAtMs: options.nowMs ?? Date.now() } }; } catch (error) { return partialResult({ error: { code: "provenance_failed", message: error.message } }); } } //#endregion //#region src/cli/claws-cli-legacy-resume.ts function authorizeLegacyV1Resume(params) { const finalAgentId = params.opts.agentId?.trim() || params.manifest.agent?.id?.trim(); const consentPlanIntegrity = params.opts.planIntegrity?.trim(); if (!finalAgentId || !consentPlanIntegrity) return; const record = readClawInstallRecord(finalAgentId); if (!record || record.schemaVersion !== "openclaw.clawInstallRecord.v1" || record.status === "complete" || record.planIntegrity !== consentPlanIntegrity || record.claw.kind !== params.source.kind || record.claw.name !== params.source.name || record.claw.version !== params.source.version || record.claw.packageRoot !== params.source.packageRoot || record.claw.manifestPath !== params.source.manifestPath) return; return record; } //#endregion //#region src/cli/claws-cli.monitor-cleanup.ts const binding = () => resolveClawMonitorCleanupBinding(resolveCronJobsStorePathFromConfig(getRuntimeConfig())); const clawMonitorCleanupGateway = { inspect: async (agentId) => clawMonitorInventorySchema.parse(await callGatewayFromCli("claws.monitors", { timeout: "5000" }, { phase: "inspect", agentId, binding: binding() })).monitors, quiesce: async (agentId, operationId, monitors) => { clawMonitorDrainSchema.parse(await callGatewayFromCli("claws.monitors", {}, { phase: "quiesce", agentId, operationId, monitors, binding: binding() })); }, drain: async (agentId, operationId) => { clawMonitorDrainSchema.parse(await callGatewayFromCli("claws.monitors", {}, { phase: "drain", agentId, operationId, binding: binding() })); } }; //#endregion //#region src/cli/claws-cli.package-removal.ts const clawPackageRemovalGateway = async (request) => clawPackageRemovalResultSchema.parse(await callGatewayFromCli("claws.packages.remove", { timeout: "600000" }, { ...request, binding: resolveClawMonitorCleanupBinding(resolveCronJobsStorePathFromConfig(getRuntimeConfig())) })); //#endregion //#region src/cli/claws-cli.runtime.ts function logClawAddPlanSummary(plan, runtime) { runtime.log(`Agent: ${plan.agent.finalId}`); runtime.log(`Workspace: ${plan.agent.workspace}`); logClawAgentConfiguration(plan, runtime); runtime.log(`Actions: ${plan.summary.totalActions}`); runtime.log(`Packages: ${plan.summary.packageActions}`); for (const action of plan.actions.filter((candidate) => candidate.kind === "package")) { const requirementState = typeof action.details?.requirementState === "string" ? action.details.requirementState : "unresolved"; runtime.log(` Requirement ${action.target}: ${requirementState}${action.action === "install" ? " (installation requires this exact plan consent)" : ""}`); } runtime.log(`MCP servers: ${plan.summary.mcpServerActions}`); for (const action of plan.actions.filter((candidate) => candidate.kind === "mcpServer")) { const server = action.details; const target = typeof server?.url === "string" ? redactSensitiveUrlLikeString(server.url) : typeof server?.command === "string" ? redactSensitiveArgv([server.command, ...filterStringEntries(server.args)]).join(" ") : "invalid declaration"; runtime.log(` MCP ${action.id}: ${target}`); } runtime.log(`Cron jobs: ${plan.summary.cronJobActions}`); if (plan.capabilityChanges.length > 0) { runtime.log(`Capability escalations (${plan.capabilityChanges.length}):`); for (const change of plan.capabilityChanges) runtime.log(redactSensitiveText(` ! ${change.kind}:${change.id} ${JSON.stringify(change.effect)}`)); runtime.log("The plan integrity binds every capability line above."); } if (plan.summary.blockedActions > 0) runtime.log(`Blocked actions: ${plan.summary.blockedActions}`); } async function matchingResumeState(plan, opts) { const readOnlyState = opts.dryRun ? await readClawResumeStateReadOnly(plan.agent.finalId) : void 0; const record = opts.dryRun ? readOnlyState?.record : readClawInstallRecord(plan.agent.finalId); if (!record || record.status === "complete" || record.workspace !== plan.agent.workspace || record.claw.kind !== plan.claw.kind || record.claw.name !== plan.claw.name || record.claw.version !== plan.claw.version || record.claw.integrity !== plan.claw.integrity) return; return { record, packageRefs: readOnlyState?.packageRefs ?? readClawPackageRefs({ agentId: plan.agent.finalId }) }; } function requireClawPlanConsent(action, opts, runtime) { if (opts.dryRun || opts.yes && opts.planIntegrity) return false; const code = opts.yes ? "plan_integrity_required" : "consent_required"; const message = opts.yes ? `Claw ${action} consent must include --plan-integrity from the exact dry-run plan.` : `Claw ${action} requires explicit consent; pass --dry-run to preview or --yes with --plan-integrity to ${action === "add" ? "create the new agent and workspace" : "remove owned state"}.`; emitClawFailure(runtime, opts.json, message, { schemaVersion: action === "add" ? CLAW_ADD_PLAN_SCHEMA_VERSION : CLAW_REMOVE_PLAN_SCHEMA_VERSION, stability: CLAW_OUTPUT_STABILITY, ok: false, error: { code, message } }); return true; } async function runClawsInspectCommand(sourcePath, opts, runtime = defaultRuntime) { assertExperimentalClawsEnabled(); const result = await readClawManifestFile(sourcePath); if (!result.ok) { emitClawFailure(runtime, opts.json, formatClawDiagnostics(result.diagnostics), { schemaVersion: CLAW_INSPECT_RESULT_SCHEMA_VERSION, stability: CLAW_OUTPUT_STABILITY, valid: false, diagnostics: result.diagnostics }); return; } const extensionPlan = await planClawExtensions({ extensions: result.openClawProfile?.extensions ?? [], workspace: result.source.packageRoot, packagePreflight: preflightClawPackage }); const extensionCollisions = findClawExtensionPackageCollisions({ packages: result.manifest.packages, extensions: result.openClawProfile?.extensions ?? [] }); const diagnostics = [ ...result.diagnostics, ...extensionPlan.blockers, ...extensionCollisions.map(({ diagnostic }) => diagnostic) ]; const valid = diagnostics.every((diagnostic) => diagnostic.level !== "error"); const payload = { schemaVersion: CLAW_INSPECT_RESULT_SCHEMA_VERSION, stability: CLAW_OUTPUT_STABILITY, valid, source: result.source, manifest: result.manifest, ...result.openClawProfile ? { openClawProfile: result.openClawProfile } : {}, extensions: extensionPlan.extensions, diagnostics }; if (opts.json) { writeRuntimeJson(runtime, payload); if (!valid) runtime.exit(1); return; } logClawExperimentalWarning(runtime); runtime.log(`Claw: ${result.source.name}@${result.source.version}`); runtime.log(`Agent: ${result.manifest.agent.name ?? result.manifest.agent.id}`); runtime.log(`Packages: ${result.manifest.packages.length}`); runtime.log(`Extension requirements: ${extensionPlan.extensions.length}`); for (const extension of extensionPlan.extensions) runtime.log(` ${extension.id}: ${extension.requirementState}; ${extension.detectedFormat ?? "unresolved"} -> ${(extension.mapped ?? []).join(", ") || "no mapped capabilities"}`); runtime.log(`MCP servers: ${Object.keys(result.manifest.mcpServers).length}`); runtime.log(`Cron jobs: ${result.manifest.cronJobs.length}`); if (!valid) { runtime.error(formatClawDiagnostics(diagnostics)); runtime.exit(1); } } async function runClawsAddCommand(sourcePath, opts, runtime = defaultRuntime) { assertExperimentalClawsEnabled(); if (requireClawPlanConsent("add", opts, runtime)) return; let legacyV1ResumeRecord; const result = await readClawManifestFile(sourcePath, { authorizeLegacyDynamicToolProfile: ({ manifest, source }) => { legacyV1ResumeRecord = authorizeLegacyV1Resume({ manifest, source, opts }); return legacyV1ResumeRecord !== void 0; } }); if (!result.ok) { emitClawFailure(runtime, opts.json, formatClawDiagnostics(result.diagnostics), { schemaVersion: CLAW_ADD_PLAN_SCHEMA_VERSION, stability: CLAW_OUTPUT_STABILITY, valid: false, diagnostics: result.diagnostics }); return; } const config = getRuntimeConfig(); const listedMcpServers = await listConfiguredMcpServers(); if (!listedMcpServers.ok) { runtime.error(listedMcpServers.error); runtime.exit(1); return; } const existingAgentIds = listAgentIds(config); const existingWorkspacePaths = existingAgentIds.map((agentId) => resolveAgentWorkspaceDir(config, agentId)); const cronStore = await loadCronJobsStoreWithConfigJobsReadOnly(resolveCronJobsStorePath()); const basePlanContext = { config, ...opts.agentId ? { agentId: opts.agentId } : {}, ...opts.workspace ? { workspace: opts.workspace } : {}, existingAgentIds, existingWorkspacePaths, existingMcpServers: listedMcpServers.mcpServers, existingCronJobIds: cronStore.store.jobs.map((job) => job.id), packagePreflight: preflightClawPackage }; const planInput = { manifest: result.manifest, clawMarkdownBody: result.clawMarkdownBody, packageBootstrap: result.packageBootstrap, openClawProfile: result.openClawProfile, source: result.source, diagnostics: result.diagnostics }; let plan = await buildClawAddPlan({ ...planInput, context: basePlanContext }); let legacyResumePlan = result.legacyOpenClawProfile ? await buildClawAddPlan({ ...planInput, openClawProfile: result.legacyOpenClawProfile, reconstructLegacyDynamicToolProfilePlan: true, context: basePlanContext }) : void 0; let resumableInstallRecord; const resumeState = await matchingResumeState(legacyResumePlan ?? plan, opts); if (result.legacyOpenClawProfile && !resumeState) plan = { ...plan, blockers: [...plan.blockers, { level: "error", code: "claw_resume_plan_mismatch", phase: "plan", path: "$", message: "The incomplete Claw add no longer matches the previously consented plan; remove its partial state before retrying." }] }; if (resumeState) { const { record: resumeRecord, packageRefs: resumePackageRefs } = resumeState; resumableInstallRecord = resumeRecord; const packagePreflight = async (pkg, workspace) => { const preflight = await preflightClawPackage(pkg, workspace); return findResumableIntroducedPluginRequirement({ agentId: resumeRecord.agentId, pkg, preflight, refs: resumePackageRefs }) ? { ...preflight, action: "install" } : preflight; }; const canResumeWorkspace = resumeRecord.status === "workspace_ready" || resumeRecord.status === "config_committed"; const expectedCommittedAgentConfigs = legacyResumePlan ? [legacyResumePlan.agent.config, plan.agent.config] : [plan.agent.config]; const committedAgent = listAgentEntries(config).find((agent) => agent.id === resumeRecord.agentId && expectedCommittedAgentConfigs.some((expected) => stableStringify(agent) === stableStringify(expected))); const canResumeAgent = resumeRecord.status === "config_committed" || resumeRecord.status === "workspace_ready" && committedAgent !== void 0; const resumePlanContext = { ...basePlanContext, packagePreflight, existingAgentIds: canResumeAgent ? existingAgentIds.filter((agentId) => agentId !== resumeRecord.agentId) : existingAgentIds, existingWorkspacePaths: canResumeWorkspace ? existingAgentIds.filter((agentId) => agentId !== resumeRecord.agentId).map((agentId) => resolveAgentWorkspaceDir(config, agentId)) : existingWorkspacePaths, ...canResumeWorkspace ? { resumableWorkspace: resumeRecord.workspace } : {} }; plan = await buildClawAddPlan({ ...planInput, context: resumePlanContext }); if (result.legacyOpenClawProfile) legacyResumePlan = await buildClawAddPlan({ ...planInput, openClawProfile: result.legacyOpenClawProfile, reconstructLegacyDynamicToolProfilePlan: true, context: resumePlanContext }); const expectedResumePlan = legacyResumePlan ?? plan; const exactLegacyResume = !legacyResumePlan || legacyV1ResumeRecord !== void 0 && stableStringify(legacyV1ResumeRecord) === stableStringify(resumeRecord); if (plan.blockers.length === 0 && (!exactLegacyResume || !clawInstallRecordMatchesPlan(resumeRecord, expectedResumePlan))) plan = { ...plan, blockers: [...plan.blockers, { level: "error", code: "claw_resume_plan_mismatch", phase: "plan", path: "$", message: "The incomplete Claw add no longer matches the current plan; remove its partial state before retrying." }] }; } if (plan.blockers.length > 0) { if (opts.json) writeRuntimeJson(runtime, plan); else { logClawExperimentalWarning(runtime); logClawAddPlanSummary(plan, runtime); runtime.error(formatClawDiagnostics(plan.blockers)); } runtime.exit(1); return; } if (opts.dryRun) { if (opts.json) writeRuntimeJson(runtime, plan); else { logClawExperimentalWarning(runtime); runtime.log(`Claw add plan: ${plan.claw.name}@${plan.claw.version}`); logClawAddPlanSummary(plan, runtime); } return; } const consentPlanIntegrity = legacyResumePlan?.planIntegrity ?? plan.planIntegrity; if (opts.planIntegrity !== consentPlanIntegrity) { const message = "The consented Claw plan no longer matches; run add --dry-run again."; emitClawFailure(runtime, opts.json, message, { schemaVersion: CLAW_ADD_RESULT_SCHEMA_VERSION, stability: CLAW_OUTPUT_STABILITY, status: "failed", planIntegrity: plan.planIntegrity, error: { code: "plan_integrity_mismatch", message } }); return; } let addResult; if (!opts.json) logClawExperimentalWarning(runtime); try { addResult = await applyClawAddPlan(plan, { reloadPlugins: await resolvePluginBatchReload(), consentPlanIntegrity: opts.planIntegrity, resumeRecord: resumableInstallRecord, resumePlan: legacyResumePlan, runtime: opts.json ? { ...runtime, log: () => void 0 } : runtime, cronGateway: { add: async (input) => await callGatewayFromCli("cron.add", {}, input), list: async (agentId) => await listCronJobsFromGateway({}, { agentId, includeDisabled: true }), waitUntilAgentAvailable: waitUntilGatewayAgentAvailable } }); } catch (error) { const code = error instanceof ClawAddMutationError ? error.code : "add_failed"; const message = error.message; emitClawFailure(runtime, opts.json, message, { schemaVersion: CLAW_ADD_RESULT_SCHEMA_VERSION, stability: CLAW_OUTPUT_STABILITY, status: "failed", error: { code, message } }); return; } if (opts.json) writeRuntimeJson(runtime, addResult); else { runtime.log(`Added agent: ${addResult.agent.finalId}`); runtime.log(`Workspace: ${addResult.agent.workspace}`); runtime.log(`Status: ${addResult.status}`); if (addResult.error) runtime.error(addResult.error.message); } if (addResult.status !== "complete") runtime.exit(1); } async function runClawsStatusCommand(target, opts, runtime = defaultRuntime) { assertExperimentalClawsEnabled(); const status = await readClawStatus(target); if (opts.json) writeRuntimeJson(runtime, status); else { logClawExperimentalWarning(runtime); runtime.log(`Installed Claws: ${status.summary.claws}`); for (const record of status.records) { runtime.log(`${record.install.agentId}: ${record.install.claw.name}@${record.install.claw.version} (${record.install.status})`); runtime.log(` Agent: ${record.agentState}; bootstrap: ${record.bootstrapState}; files: ${record.workspaceFiles.length}; packages: ${record.packages.length}`); } } if (target && status.records.length === 0) runtime.exit(1); } async function runClawsRemoveCommand(target, opts, runtime = defaultRuntime) { assertExperimentalClawsEnabled(); if (requireClawPlanConsent("remove", opts, runtime)) return; const selected = opts.removeReferenced ?? []; if (opts.removeUnused && selected.length > 0) { runtime.error("Choose either --remove-unused or --remove-referenced, not both."); runtime.exit(1); return; } if (opts.forceReferenced && selected.length === 0) { runtime.error("--force-referenced requires at least one --remove-referenced selector."); runtime.exit(1); return; } const referencedCleanup = selected.length ? { mode: "remove-selected", selected, allowConflicts: Boolean(opts.forceReferenced) } : opts.removeUnused ? { mode: "remove-if-unused" } : { mode: "retain" }; const plan = await buildClawRemovePlan(target, { referencedCleanup, monitorGateway: clawMonitorCleanupGateway }); if (opts.dryRun || plan.blockers.length > 0) { if (opts.json) writeRuntimeJson(runtime, plan); else { logClawExperimentalWarning(runtime); runtime.log(`Remove actions: ${plan.actions.length}`); runtime.log(`Plan integrity: ${plan.planIntegrity}`); for (const action of plan.actions.filter((candidate) => candidate.kind === "packageRef")) runtime.log(` Package ${action.target}: ${action.action}${action.reason ? ` (${action.reason})` : ""}`); for (const action of plan.actions.filter((candidate) => candidate.kind === "mcpServer")) runtime.log(` MCP ${action.id}: ${action.action}${action.reason ? ` (${action.reason})` : ""}`); if (plan.blockers.length > 0) runtime.error(plan.blockers.map((blocker) => blocker.message).join("\n")); } if (plan.blockers.length > 0) runtime.exit(1); return; } try { const result = await applyClawRemovePlan(plan, { monitorGateway: clawMonitorCleanupGateway, packageGateway: clawPackageRemovalGateway, consentPlanIntegrity: opts.planIntegrity, referencedCleanup, cronGateway: { get: async (id) => await callGatewayFromCli("cron.get", {}, { id }), remove: async (id) => await callGatewayFromCli("cron.remove", {}, { id }) } }); if (opts.json) writeRuntimeJson(runtime, result); else { logClawExperimentalWarning(runtime); runtime.log(`${result.agentRemoved ? "Removed agent" : "Agent"}: ${result.agentId}`); runtime.log(`Status: ${result.status}`); for (const pkg of result.packages) runtime.log(` Package ${pkg.kind}:${pkg.ref}@${pkg.version}: ${pkg.action}${pkg.reason ? ` (${pkg.reason})` : ""}`); runtime.log(`Package references released: ${result.packageRefsReleased}`); if (result.error) runtime.error(result.error.message); for (const warning of result.warnings ?? []) runtime.log(`Warning: ${warning}`); if (result.pluginRuntime) runtime.log(`Plugin runtime changed in Gateway generation ${result.pluginRuntime.generation}.`); } if (result.status !== "complete") runtime.exit(1); } catch (error) { const code = error instanceof ClawRemoveError ? error.code : "remove_failed"; const message = error instanceof Error ? error.message : String(error); emitClawFailure(runtime, opts.json, message, { schemaVersion: CLAW_REMOVE_RESULT_SCHEMA_VERSION, stability: CLAW_OUTPUT_STABILITY, status: "failed", error: { code, message } }); } } async function runClawsExportCommand(agentId, opts, runtime = defaultRuntime) { assertExperimentalClawsEnabled(); try { const listedMcpServers = await listConfiguredMcpServers(); if (!listedMcpServers.ok) throw new ClawExportError("mcp_config_unavailable", listedMcpServers.error); const result = await exportClawAgent(agentId, opts.out, { config: getRuntimeConfig(), sourceMcpServers: listedMcpServers.mcpServers, ...opts.bootstrap ? { bootstrapPath: opts.bootstrap } : {} }); if (opts.json) { writeRuntimeJson(runtime, result); return; } logClawExperimentalWarning(runtime); runtime.log(`Exported agent: ${result.agentId}`); runtime.log(`Package directory: ${result.outputDirectory}`); runtime.log(`Workspace files: ${result.manifest.workspace.files.length + Object.keys(result.manifest.workspace.bootstrapFiles).length}`); runtime.log(`Packages: ${result.manifest.packages.length}`); runtime.log(`Bootstrap: ${result.filesWritten.includes("BOOTSTRAP.md") ? "included" : "none"}`); } catch (error) { const code = error instanceof ClawExportError ? error.code : "export_failed"; const message = error instanceof Error ? error.message : String(error); emitClawFailure(runtime, opts.json, message, { schemaVersion: CLAW_EXPORT_RESULT_SCHEMA_VERSION, stability: CLAW_OUTPUT_STABILITY, status: "failed", error: { code, message } }); } } //#endregion export { runClawsAddCommand, runClawsExportCommand, runClawsInspectCommand, runClawsRemoveCommand, runClawsStatusCommand };