UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

741 lines (740 loc) • 34.6 kB
import { n as coerceErrorMessage } from "./error-coercion-W9BtigRm.mjs"; import "./src-BirUqOZp.mjs"; import { o as asRecord } from "./record-coerce-TIsquoVR.mjs"; import { t as safeParseJson } from "./json-coercion-pfKbZnCJ.mjs"; import { t as stableStringify } from "./stable-stringify-Djn3uxNo.mjs"; import { r as defaultRuntime } from "./runtime-BRg2NubB.mjs"; import { t as sleep } from "./sleep-dMoNLoKC.mjs"; import { t as formatErrorMessage } from "./errors-BvOkmQM5.mjs"; import { n as resolvePreferredOpenClawTmpDir } from "./tmp-openclaw-dir-DR7bHNl9.mjs"; import { n as ok, t as err } from "./result-BQGgYouL.mjs"; import { l as hasSqliteWorkerOutcomeUnknown } from "./sqlite-worker-contract-C51cJHzg.mjs"; import { n as openExistingOpenClawStateDatabaseReadOnly } from "./openclaw-state-db-DJZ7EKoI.mjs"; import { r as resolveInstalledPluginIndexInstallOwner } from "./installed-plugin-index-install-owner-CQJv99I6.mjs"; import { a as readPluginMetadataStateRow } from "./bundled-discovery-state-o6UjTl0-.mjs"; import { n as hashStableJson } from "./installed-plugin-index-hash-N3AVaUCF.mjs"; import { N as normalizeClawHubSha256Integrity } from "./official-external-plugin-catalog-BKfROutK.mjs"; import { t as parseInstalledPluginIndex } from "./installed-plugin-index-store-DvrINVXo.mjs"; import { t as createInstalledPluginOwnershipResolver } from "./installed-plugin-package-ownership-yp92PpU9.mjs"; import { n as callGatewayFromCli } from "./gateway-rpc-9mVMq9dg.mjs"; import { a as withPluginLifecycleLease, t as hasPluginLifecycleLease } from "./plugin-lifecycle-lease-CJFG12c4.mjs"; import { i as resolveLocalProviderAuthEvidence } from "./model-auth-env-BNV47PY-.mjs"; import { n as readClawInstallRecordFromDatabase } from "./provenance-read.kernel-BQe6L4vF.mjs"; import { t as PLUGIN_ARTIFACT_ADAPTER_IDENTITY } from "./install-artifact-inspection-X2JhMF_r.mjs"; import { t as installPluginFromClawHub } from "./clawhub-kAIBPffz.mjs"; import { r as withClawPackageLifecycleLease } from "./claw-package-lifecycle-lease-C_2EPExP.mjs"; import { n as resolvePluginCapabilityConsentCliOptions } from "./plugin-capability-consent-C4lnA35J.mjs"; import { t as createPluginInstallLogger } from "./plugins-command-helpers-1t-5vty0.mjs"; import { t as installManagedPlugin } from "./management-mutations-BsicUGFk.mjs"; import { C as readClawPackageRefs, E as updateClawPackageRefStatus, M as preflightPluginInstall, N as resolveInstalledClawHubPlugin, b as persistClawPackageRef } from "./package-remove-plan-CgRv4IRz.mjs"; import { t as resolveClawHubInstallConfirmation } from "./clawhub-install-confirmation-cbUTWJxB.mjs"; import { t as withPluginSourceCleanup } from "./source-cleanup-DZvm5K-z.mjs"; import { r as uninstallPluginWithPolicy } from "./management-uninstall-cLuBWvZc.mjs"; import { n as preflightSkillFromClawHub, t as installSkillFromClawHub } from "./clawhub-NMdVUJhA.mjs"; import { t as inspectPluginGenerationSources } from "./plugin-generation-source-inspection-Kb8Qao32.mjs"; import fs from "node:fs"; import { tempWorkspace } from "@openclaw/fs-safe/temp"; //#region src/claws/package-resume.ts function ownerInstallIsNewerThanRefs(installedAt, refs) { const timestamp = Date.parse(installedAt ?? ""); return Number.isFinite(timestamp) && refs.length > 0 && refs.every((ref) => timestamp > ref.updatedAtMs); } function persistedExtensionMatchesPreflight(ref, preflight) { if (!ref.extension) return true; if (!preflight.ok) return false; return stableStringify({ detectedFormat: ref.extension.detectedFormat, mapped: ref.extension.mapped, unavailable: ref.extension.unavailable, adapterIdentity: ref.extension.adapterIdentity }) === stableStringify({ detectedFormat: preflight.detectedFormat, mapped: preflight.mapped ?? [], unavailable: preflight.unavailable ?? [], adapterIdentity: preflight.adapterIdentity }); } function findResumableIntroducedPluginRequirement(params) { if (params.pkg.kind !== "plugin" || !params.preflight.ok || params.preflight.action !== "reuse") return; const expectedRawIntegrity = params.expectedIntegrity ?? params.preflight.integrity; if (!expectedRawIntegrity || !params.preflight.installedIntegrity) return; const expectedIntegrity = normalizeClawHubSha256Integrity(expectedRawIntegrity); const installedIntegrity = normalizeClawHubSha256Integrity(params.preflight.installedIntegrity); if (!expectedIntegrity || installedIntegrity !== expectedIntegrity) return; const ref = params.refs.find((candidate) => candidate.agentId === params.agentId && candidate.kind === params.pkg.kind && candidate.source === params.pkg.source && candidate.ref === params.pkg.ref && candidate.version === params.pkg.version && normalizeClawHubSha256Integrity(candidate.integrity) === expectedIntegrity && candidate.status === "complete" && candidate.relationship === "referenced" && candidate.origin === "claw-introduced" && !candidate.independentOwner && persistedExtensionMatchesPreflight(candidate, params.preflight)); return ref && !ownerInstallIsNewerThanRefs(params.preflight.installedAt, [ref]) ? ref : void 0; } async function readClawResumeStateReadOnly(agentId, options = {}) { const database = await openExistingOpenClawStateDatabaseReadOnly({ ...options, requireCanonicalSchema: true }); if (!database) return; try { const record = readClawInstallRecordFromDatabase(database.db, agentId); if (!record) return; return { record, packageRefs: readClawPackageRefs({ ...options, database, readOnly: true, agentId }) }; } finally { database.walMaintenance.close(); } } //#endregion //#region src/claws/package-setup-requirements.ts function resolveClawPluginSetupRequirements(params) { const providers = params.setup?.providers ?? []; if (providers.some((provider) => (provider.envVars ?? []).some((name) => Boolean(params.env[name]?.trim())) || resolveLocalProviderAuthEvidence(provider.authEvidence, params.env))) return []; return providers.flatMap((provider) => { const envVars = provider.envVars ?? []; const authEvidence = provider.authEvidence ?? []; if (envVars.length === 0 && authEvidence.length === 0) return []; return [{ kind: "plugin-setup", plugin: params.pluginId, provider: provider.id, envVars, authMethods: provider.authMethods ?? [] }]; }); } //#endregion //#region src/plugins/install-runtime-batch.ts function indexFromRow(value) { if (value === void 0) return; const index = parseInstalledPluginIndex(asRecord(safeParseJson(value))?.index); if (!index) throw new Error("Plugin batch has an invalid installed-index receipt"); return index; } /** Owns ephemeral committed facts and cleanup across the CLI-to-Gateway lease gap. */ var PluginInstallRuntimeBatch = class { constructor(options, reload) { this.options = options; this.reload = reload; this.installs = []; this.targets = []; this.retained = /* @__PURE__ */ new Set(); this.sourceChecks = /* @__PURE__ */ new Map(); this.phase = "collecting"; } get hasCommitted() { return this.installs.some((entry) => entry.commit !== void 0); } close() { this.phase = "closed"; for (const entry of this.installs.splice(0)) { entry.commit = void 0; entry.cleanups.length = 0; } this.targets = []; this.retained.clear(); this.sourceChecks.clear(); } assertOpen() { if (this.phase === "closed") throw new Error("Plugin installation batch is closed"); } assertCollecting() { if (this.phase !== "collecting") throw new Error("Plugin installation batch no longer accepts mutations"); } install() { this.assertCollecting(); const entry = { cleanups: [] }; this.installs.push(entry); return { record: (commit, assertSourceCurrent) => { this.assertCollecting(); if (entry.commit) throw new Error("Plugin install already committed in this batch"); entry.commit = commit; if (assertSourceCurrent) this.sourceChecks.set(commit.pluginId, assertSourceCurrent); else this.sourceChecks.delete(commit.pluginId); }, deferCleanup: (cleanup, sourcePath) => { this.assertCollecting(); if (!entry.commit) throw new Error("Cannot transfer cleanup before plugin installation commits"); const configPath = entry.commit.write.configWrite.path; const original = fs.lstatSync(sourcePath, { bigint: true, throwIfNoEntry: false }); entry.cleanups.push(async (index, assertOwned, warn) => { const assertUnclaimed = () => { assertOwned(); const current = fs.lstatSync(sourcePath, { bigint: true, throwIfNoEntry: false }); if (current && (!original || current.dev !== original.dev || current.ino !== original.ino)) throw new Error(`Retired plugin source changed before cleanup: ${sourcePath}`); if (createInstalledPluginOwnershipResolver(index, this.options.env).isSourceInUse(sourcePath, [])) throw new Error(`Retired plugin source acquired a current owner: ${sourcePath}`); assertOwned(); }; await withPluginSourceCleanup(sourcePath, { configPath, env: this.options.env, assertCurrent: assertUnclaimed }, (assertCurrent) => cleanup(assertCurrent, warn)); }); } }; } retain(pluginId) { this.assertCollecting(); this.retained.add(pluginId); } /** Called before the original batch lease exits, after its compensation has settled. */ async prepare(lease) { this.assertCollecting(); this.phase = "preparing"; const databasePath = lease.databasePath; lease.assertOwned(); this.assertOpen(); this.databasePath = databasePath; if (!this.hasCommitted && this.retained.size === 0) { this.phase = "prepared"; return; } const row = await readPluginMetadataStateRow("installed-index", { path: databasePath }); lease.assertOwned(); this.assertOpen(); const index = indexFromRow(row?.value_json); const current = index?.installRecords ?? {}; const targets = /* @__PURE__ */ new Map(); for (const pluginId of this.retained) { if (!current[pluginId]) throw new Error(`Resumable plugin ${pluginId} is no longer installed`); const source = inspectPluginGenerationSources((index?.plugins ?? []).filter((entry) => resolveInstalledPluginIndexInstallOwner(entry) === pluginId && entry.enabled && entry.origin !== "bundled").map((entry) => ({ pluginId: entry.pluginId, rootDir: entry.rootDir, entryFile: entry.source === entry.manifestPath ? entry.source : void 0 }))); this.sourceChecks.set(pluginId, source.assertSourceCurrent); targets.set(pluginId, { pluginId, installHash: hashStableJson(current[pluginId]), sourceDigests: source.sourceDigests }); } const finalCommits = /* @__PURE__ */ new Map(); for (const { commit } of this.installs) { if (!commit) continue; finalCommits.set(commit.pluginId, commit); } for (const commit of finalCommits.values()) { const { pluginId, write } = commit; if (write.mutation.databasePath !== databasePath) throw new Error("Plugin batch commit belongs to a different state database"); const installed = indexFromRow(write.mutation.after.value_json)?.installRecords[pluginId]; if (commit.operation === "uninstall") { if (installed || current[pluginId]) throw new Error(`Plugin ${pluginId} compensation did not remove its installed record`); targets.delete(pluginId); continue; } if (!installed) throw new Error(`Plugin ${pluginId} is missing from its committed index receipt`); const installHash = hashStableJson(installed); if (!current[pluginId] || hashStableJson(current[pluginId]) !== installHash) throw new Error(`Plugin ${pluginId} changed during batch settlement`); targets.set(pluginId, { pluginId, installHash, sourceDigests: commit.sourceDigests }); } lease.assertOwned(); this.assertOpen(); this.targets = [...targets.values()].toSorted((a, b) => a.pluginId.localeCompare(b.pluginId)); this.phase = "prepared"; } /** The caller must leave every batch lease before invoking the captured runtime client. */ async finish(warn) { if (this.phase !== "prepared" || !this.databasePath || hasPluginLifecycleLease()) throw new Error("Plugin batch was not prepared or its handoff already started"); this.phase = "applying"; const targets = this.targets; let application; try { if (targets.length === 0) return; application = await this.reload(targets); this.assertOpen(); for (const warning of application.warnings ?? []) warn(warning); await withPluginLifecycleLease(this.options, async (lease) => { const assertOwned = () => { this.assertOpen(); lease.assertOwned(); if (lease.databasePath !== this.databasePath) throw new Error("Plugin cleanup belongs to a different state database"); }; assertOwned(); const sourceCleanups = this.installs.flatMap(({ commit, cleanups }) => commit && targets.some((target) => target.pluginId === commit.pluginId) ? cleanups : []); if (sourceCleanups.length === 0) return; const row = await readPluginMetadataStateRow("installed-index", { path: lease.databasePath }); assertOwned(); const index = indexFromRow(row?.value_json); if (!index) throw new Error("Plugin index disappeared before source cleanup"); const records = index.installRecords; const assertCurrent = () => { assertOwned(); for (const target of targets) { this.sourceChecks.get(target.pluginId)?.(); if (!records[target.pluginId] || hashStableJson(records[target.pluginId]) !== target.installHash) throw new Error(`Plugin ${target.pluginId} changed before committed source cleanup`); } }; for (const cleanup of sourceCleanups) { assertCurrent(); await cleanup(index, assertCurrent, warn); assertCurrent(); } }); return application; } catch (error) { throw new Error(`Plugin installations are saved: ${targets.map((target) => target.pluginId).join(", ")}. ` + (application ? `Gateway generation ${application.generation} was applied, but source cleanup failed. ` : "Runtime activation was not confirmed. ") + `Fix the reported issue, then run openclaw plugins reload <plugin-id> for each affected plugin. ${formatErrorMessage(error)}`, { cause: error }); } finally { this.close(); } } }; //#endregion //#region src/claws/plugin-runtime.ts async function runClawPluginBatch(options, pluginCount, run, runtimeFailure) { if (!options.reloadPlugins || options.runtimeBatch) return await withPluginLifecycleLease(options, () => run(options.runtimeBatch)); if (pluginCount > 64) throw new Error(`A live Claw requirement batch supports at most 64 plugin packages. Split the requirement batch before installing.`); const batch = new PluginInstallRuntimeBatch(options, options.reloadPlugins); let completed; const operation = await withPluginLifecycleLease(options, async (lease) => { let result; try { result = ok(await run(batch)); } catch (error) { result = err(error); } completed = result; await batch.prepare(lease); return result; }).catch((error) => { const committed = batch.hasCommitted; batch.close(); if (!committed && !completed) throw error; throw runtimeFailure(error, completed ?? err(error)); }); try { const runtime = options.runtime ?? defaultRuntime; const application = await batch.finish((message) => runtime.log(message)); if (application) runtime.log(`Plugin requirements applied in Gateway generation ${application.generation}.`); } catch (error) { throw runtimeFailure(error, operation); } if (!operation.ok) throw operation.error; return operation.value; } //#endregion //#region src/claws/packages.ts var ClawPackageInstallError = class extends Error { constructor(code, message, installedPackages, options) { super(message, options); this.code = code; this.installedPackages = installedPackages; this.name = "ClawPackageInstallError"; } }; function packageFromAction(action) { const details = action.details; if (details?.kind !== "skill" && details?.kind !== "plugin") throw new Error(`Package action ${JSON.stringify(action.id)} has no valid package kind.`); if (details.source !== "clawhub" || !details.ref || !details.version || !details.integrity || !normalizeClawHubSha256Integrity(details.integrity)) throw new Error(`Package action ${JSON.stringify(action.id)} is not a pinned ClawHub package with integrity.`); if (details.ownerAction !== "install" && details.ownerAction !== "reuse") throw new Error(`Package action ${JSON.stringify(action.id)} has no planned owner state.`); if (details.kind === "plugin" && !details.installId) throw new Error(`Package action ${JSON.stringify(action.id)} has no resolved plugin id.`); return { kind: details.kind, source: details.source, ref: details.ref, version: details.version, integrity: details.integrity, ownerAction: details.ownerAction, ...details.extension ? { extension: details.extension } : {}, ...details.installId ? { installId: details.installId } : {}, ...details.riskWarning ? { riskWarning: details.riskWarning } : {} }; } async function probeClawPluginArtifact(pkg, isolateFromLiveExtensions, deps) { const probePlugin = deps.probePlugin ?? installPluginFromClawHub; const request = { spec: `clawhub:${pkg.ref}@${pkg.version}`, dryRun: true }; if (!isolateFromLiveExtensions) return await probePlugin(request); const workspace = await tempWorkspace({ rootDir: resolvePreferredOpenClawTmpDir(), prefix: "openclaw-claw-plugin-probe-" }); try { return await probePlugin({ ...request, extensionsDir: workspace.dir }); } finally { await workspace.cleanup().catch(() => void 0); } } async function preflightClawPackage(pkg, workspaceDir, options = {}) { if (pkg.kind === "skill") { const result = await preflightSkillFromClawHub({ workspaceDir, slug: pkg.ref, version: pkg.version }); return result.ok ? result : { ok: false, code: result.code, message: result.error }; } const result = await (options.deps?.preflightPlugin ?? preflightPluginInstall)({ clawhubPackage: pkg.ref, rawSpec: `clawhub:${pkg.ref}@${pkg.version}`, expectedVersion: pkg.version }); if (!result.ok && result.code !== "plugin_version_conflict") return { ok: false, code: result.code, message: result.error }; const probe = await probeClawPluginArtifact(pkg, !(result.ok && result.action === "install"), options.deps ?? {}); if (!probe.ok) return { ok: false, code: probe.code ?? "plugin_preflight_failed", message: probe.error }; if (!probe.artifactInspection) return { ok: false, code: "plugin_artifact_inspection_unavailable", message: `Plugin ${pkg.ref}@${pkg.version} did not return canonical artifact inspection.` }; if (probe.artifactInspection.format === "agent") return { ok: false, code: "plugin_artifact_format_unsupported", message: `Plugin ${pkg.ref}@${pkg.version} uses unsupported Claw extension format agent.` }; const integrity = probe.clawhub.integrity ? normalizeClawHubSha256Integrity(probe.clawhub.integrity) : null; if (!integrity) return { ok: false, code: "plugin_integrity_unavailable", message: `Plugin ${pkg.ref}@${pkg.version} did not resolve an artifact integrity.` }; const requirements = resolveClawPluginSetupRequirements({ pluginId: probe.pluginId, setup: probe.setup, env: options.env ?? process.env }); const artifact = { integrity, installId: probe.pluginId, ...requirements.length > 0 ? { requirements } : {}, detectedFormat: probe.artifactInspection.format, mapped: probe.artifactInspection.mapped, unavailable: probe.artifactInspection.unavailable, adapterIdentity: PLUGIN_ARTIFACT_ADAPTER_IDENTITY, ...probe.warning ? { warning: probe.warning } : {} }; if (!result.ok) return { ok: false, code: result.code, installedVersion: result.installedVersion, ...artifact, message: `Plugin ${pkg.ref}@${pkg.version} conflicts with installed version ${result.installedVersion}.` }; if (result.action === "reuse" && (result.installedId !== probe.pluginId || !result.installedIntegrity || normalizeClawHubSha256Integrity(result.installedIntegrity) !== integrity)) return { ok: false, code: "plugin_integrity_conflict", message: `Plugin ${pkg.ref}@${pkg.version} is installed as ${result.installedId} with integrity ${result.installedIntegrity ?? "unknown"}, expected ${probe.pluginId} with ${integrity}.` }; return { ok: true, action: result.action, ...artifact, ...result.action === "reuse" && result.installedIntegrity ? { installedIntegrity: result.installedIntegrity } : {}, ...result.action === "reuse" && result.installedAt ? { installedAt: result.installedAt } : {} }; } async function installClawPackages(plan, options = {}) { const pluginCount = plan.actions.filter((action) => action.kind === "package" && action.details?.kind === "plugin").length; if (!pluginCount) return await installClawPackagesUnlocked(plan, options); return await runClawPluginBatch(options, pluginCount, (runtimeBatch) => installClawPackagesUnlocked(plan, { ...options, runtimeBatch }), (failure, operation) => { const original = !operation.ok && operation.error instanceof ClawPackageInstallError ? operation.error : void 0; return new ClawPackageInstallError(original?.code ?? "package_runtime_failed", [original?.message, coerceErrorMessage(failure)].filter(Boolean).join("\n"), original?.installedPackages ?? (operation.ok ? operation.value : []), { cause: !operation.ok ? new AggregateError([operation.error, failure]) : failure }); }); } async function installClawPackagesUnlocked(plan, options) { const deps = options.deps ?? {}; const runtime = options.runtime ?? defaultRuntime; const installPlugin = deps.installPlugin ?? (async (params) => { const result = await installManagedPlugin(params); for (const warning of result.warnings ?? []) runtime.log(warning); runtime.log(`Installed plugin requirement: ${result.plugin.id}`); if (!params.deferRuntime && !params.applyRuntime) runtime.log("Restart the gateway to load plugins."); }); const uninstallPlugin = deps.uninstallPlugin ?? (async (params) => { const result = await uninstallPluginWithPolicy(params); if (!result.ok) throw new Error(result.error); runtime.log(`Rolled back plugin requirement: ${result.value.pluginId}`); }); const installSkill = deps.installSkill ?? installSkillFromClawHub; const preflightPlugin = deps.preflightPlugin ?? preflightPluginInstall; const preflightSkill = deps.preflightSkill ?? preflightSkillFromClawHub; const persistPackageRef = deps.persistPackageRef ?? persistClawPackageRef; const completePackageRef = deps.completePackageRef ?? updateClawPackageRefStatus; const readPackageRefs = deps.readPackageRefs ?? readClawPackageRefs; const withPackageLease = deps.withPackageLease ?? withClawPackageLifecycleLease; const resolvePlugin = deps.resolvePlugin ?? resolveInstalledClawHubPlugin; const installedPackages = []; const installedPlugins = []; for (const action of plan.actions.filter((candidate) => candidate.kind === "package")) try { const pkg = packageFromAction(action); await withPackageLease(pkg.kind === "plugin" ? { kind: pkg.kind, source: pkg.source, ref: pkg.ref } : { kind: pkg.kind, source: pkg.source, ref: pkg.ref, workspace: plan.agent.workspace }, async (packageLease) => { if (pkg.kind === "skill") { const preflight = await preflightSkill({ workspaceDir: plan.agent.workspace, slug: pkg.ref, version: pkg.version, expectedIntegrity: pkg.integrity }); packageLease.assertOwned(); if (!preflight.ok) throw new Error(preflight.error); if (preflight.action !== pkg.ownerAction || preflight.warning !== pkg.riskWarning || normalizeClawHubSha256Integrity(preflight.integrity) !== normalizeClawHubSha256Integrity(pkg.integrity)) throw new ClawPackageInstallError("package_owner_state_changed", `Skill ${pkg.ref}@${pkg.version} changed after planning; run add --dry-run again.`, installedPackages); if (preflight.action === "reuse") { installedPackages.push(persistPackageRef(plan, pkg, { ...options, status: "complete", relationship: "managed", origin: "pre-existing", independentOwner: true })); return; } let packageRef = persistPackageRef(plan, pkg, { ...options, status: "pending", relationship: "managed", origin: "claw-introduced", independentOwner: false }); installedPackages.push(packageRef); options.onExternalMutation?.(pkg); const installed = await installSkill({ workspaceDir: plan.agent.workspace, slug: pkg.ref, version: pkg.version, expectedIntegrity: pkg.integrity, clawManaged: true }); packageLease.assertOwned(); if (!installed.ok) throw new Error(installed.error); packageRef = completePackageRef(packageRef, "complete", options); installedPackages[installedPackages.length - 1] = packageRef; return; } const preflight = await preflightPlugin({ clawhubPackage: pkg.ref, rawSpec: `clawhub:${pkg.ref}@${pkg.version}`, expectedVersion: pkg.version }); packageLease.assertOwned(); if (!preflight.ok) throw new Error(preflight.code === "plugin_version_conflict" ? `Plugin ${pkg.ref}@${pkg.version} conflicts with installed version ${preflight.installedVersion}.` : preflight.error); const resumableRequirement = pkg.ownerAction === "install" && preflight.action === "reuse" ? findResumableIntroducedPluginRequirement({ agentId: plan.agent.finalId, pkg, preflight, expectedIntegrity: pkg.integrity, refs: readPackageRefs({ ...options, agentId: plan.agent.finalId, kind: pkg.kind, source: pkg.source, ref: pkg.ref, version: pkg.version }) }) : void 0; if (preflight.action !== pkg.ownerAction && !resumableRequirement) throw new ClawPackageInstallError("package_owner_state_changed", `Plugin ${pkg.ref}@${pkg.version} owner state changed from ${pkg.ownerAction} to ${preflight.action}; run add --dry-run again.`, installedPackages); const probe = await probeClawPluginArtifact(pkg, true, { probePlugin: deps.probePlugin }); packageLease.assertOwned(); if (!probe.ok) throw new Error(probe.error); const probeIntegrity = probe.clawhub.integrity ? normalizeClawHubSha256Integrity(probe.clawhub.integrity) : null; const plannedExtensionInspection = pkg.extension ? { detectedFormat: pkg.extension.detectedFormat, mapped: pkg.extension.mapped, unavailable: pkg.extension.unavailable, adapterIdentity: pkg.extension.adapterIdentity } : void 0; const probedExtensionInspection = probe.artifactInspection ? { detectedFormat: probe.artifactInspection.format, mapped: probe.artifactInspection.mapped, unavailable: probe.artifactInspection.unavailable, adapterIdentity: PLUGIN_ARTIFACT_ADAPTER_IDENTITY } : void 0; if (probe.pluginId !== pkg.installId || probeIntegrity !== normalizeClawHubSha256Integrity(pkg.integrity) || probe.warning !== pkg.riskWarning || plannedExtensionInspection && stableStringify(probedExtensionInspection) !== stableStringify(plannedExtensionInspection)) throw new ClawPackageInstallError("package_owner_state_changed", `Plugin ${pkg.ref}@${pkg.version} identity or trust state changed after planning; run add --dry-run again.`, installedPackages); if (preflight.action === "reuse") { if (preflight.installedId !== pkg.installId || !preflight.installedIntegrity || normalizeClawHubSha256Integrity(preflight.installedIntegrity) !== normalizeClawHubSha256Integrity(pkg.integrity)) throw new ClawPackageInstallError("package_owner_state_changed", `Plugin ${pkg.ref}@${pkg.version} identity changed after planning; run add --dry-run again.`, installedPackages); if (resumableRequirement) { options.runtimeBatch?.retain(probe.pluginId); installedPackages.push(persistPackageRef(plan, pkg, { ...options, status: "complete", relationship: resumableRequirement.relationship, origin: resumableRequirement.origin, independentOwner: resumableRequirement.independentOwner })); return; } const existingRefs = readPackageRefs({ ...options, kind: pkg.kind, source: pkg.source, ref: pkg.ref, version: pkg.version }); const inheritsClawOrigin = existingRefs.length > 0 && existingRefs.every((candidate) => candidate.origin === "claw-introduced" && !candidate.independentOwner) && !ownerInstallIsNewerThanRefs(preflight.installedAt, existingRefs); installedPackages.push(persistPackageRef(plan, pkg, { ...options, status: "complete", relationship: "referenced", origin: inheritsClawOrigin ? "claw-introduced" : "pre-existing", independentOwner: !inheritsClawOrigin })); return; } let packageRef = persistPackageRef(plan, pkg, { ...options, status: "pending", relationship: "referenced", origin: "claw-introduced", independentOwner: false }); installedPackages.push(packageRef); options.onExternalMutation?.(pkg); await installPlugin({ request: { source: "clawhub", packageName: pkg.ref, version: pkg.version, mode: options.pluginInstallMode ?? "install", expectedIntegrity: pkg.integrity, expectedPluginId: probe.pluginId }, env: options.env, beforePersistentApply: () => packageLease.assertOwned(), logger: createPluginInstallLogger(runtime), confirmInstall: resolveClawHubInstallConfirmation(), ...resolvePluginCapabilityConsentCliOptions({ action: "install", runtime }), invalidateRuntimeCache: false, clawManaged: true, deferRuntime: options.runtimeBatch?.install() }); if (options.pluginInstallMode !== "update") installedPlugins.push({ installId: probe.pluginId, packageIndex: installedPackages.length - 1 }); packageLease.assertOwned(); packageRef = completePackageRef(packageRef, "complete", options); installedPackages[installedPackages.length - 1] = packageRef; }, options); } catch (error) { if (hasSqliteWorkerOutcomeUnknown(error)) throw error; const pending = installedPackages.at(-1); if (pending?.status === "pending") try { installedPackages[installedPackages.length - 1] = completePackageRef(pending, "failed", options); } catch {} const rollbackErrors = []; for (const installedPlugin of installedPlugins.toReversed()) { const packageRef = installedPackages[installedPlugin.packageIndex]; if (!packageRef) continue; try { await withPackageLease({ kind: "plugin", source: "clawhub", ref: packageRef.ref }, async (rollbackLease) => { if (readPackageRefs({ ...options, kind: "plugin", source: "clawhub", ref: packageRef.ref, version: packageRef.version, integrity: packageRef.integrity }).filter((ref) => ref.agentId !== plan.agent.finalId && (ref.status === "pending" || ref.status === "complete")).length > 0) { rollbackErrors.push(`kept plugin ${installedPlugin.installId} because another Claw now references it`); return; } const currentRefs = readPackageRefs({ ...options, kind: "plugin", source: "clawhub", ref: packageRef.ref, version: packageRef.version }); if (currentRefs.some((candidate) => candidate.independentOwner)) { rollbackErrors.push(`kept plugin ${installedPlugin.installId} because it now has a direct owner`); return; } const installed = await resolvePlugin({ clawhubPackage: packageRef.ref }); const installedIntegrity = installed.status === "found" && installed.record.integrity ? normalizeClawHubSha256Integrity(installed.record.integrity) : null; if (installed.status !== "found" || installed.pluginId !== installedPlugin.installId || installed.installedVersion !== packageRef.version || installedIntegrity !== normalizeClawHubSha256Integrity(packageRef.integrity) || ownerInstallIsNewerThanRefs(installed.record.installedAt, currentRefs)) { rollbackErrors.push(`kept plugin ${installedPlugin.installId} because its installed identity changed after Claw installation`); return; } await uninstallPlugin({ pluginId: installedPlugin.installId, caller: "cli", invalidateRuntimeCache: false, clawManaged: true, beforePersistentApply: () => rollbackLease.assertOwned(), onWarning: (warning) => runtime.log(warning), deferRuntime: options.runtimeBatch?.install() }); rollbackLease.assertOwned(); installedPackages[installedPlugin.packageIndex] = completePackageRef(installedPackages[installedPlugin.packageIndex] ?? packageRef, "rolled_back", options); }, options); } catch (rollbackError) { if (hasSqliteWorkerOutcomeUnknown(rollbackError)) throw rollbackError; rollbackErrors.push(`could not remove plugin ${installedPlugin.installId}: ${coerceErrorMessage(rollbackError)}`); } } const message = coerceErrorMessage(error); if (rollbackErrors.length > 0) throw new ClawPackageInstallError("package_rollback_failed", `${message} Rollback incomplete: ${rollbackErrors.join("; ")}.`, installedPackages, { cause: error }); throw new ClawPackageInstallError(error instanceof ClawPackageInstallError ? error.code : "package_install_failed", message, installedPackages, { cause: error }); } return installedPackages; } //#endregion //#region src/cli/claws-cli.gateway-readiness.ts const CLAW_AGENT_RELOAD_TIMEOUT_MS = 15e3; const CLAW_AGENT_RELOAD_POLL_MS = 100; async function waitUntilGatewayAgentAvailable(agentId) { const deadline = Date.now() + CLAW_AGENT_RELOAD_TIMEOUT_MS; let lastError; while (Date.now() < deadline) { try { const response = await callGatewayFromCli("config.get", { timeout: "5000" }, {}); if (Object.hasOwn(response.config?.agents?.entries ?? {}, agentId) && typeof response.configRevisionHash === "string" && response.configRevisionHash === response.appliedConfigHash) return; lastError = void 0; } catch (error) { lastError = error; } await sleep(CLAW_AGENT_RELOAD_POLL_MS); } const suffix = lastError instanceof Error ? `: ${lastError.message}` : ""; throw new Error(`Gateway did not apply the Claw agent configuration in time${suffix}`); } //#endregion export { runClawPluginBatch as a, preflightClawPackage as i, ClawPackageInstallError as n, findResumableIntroducedPluginRequirement as o, installClawPackages as r, readClawResumeStateReadOnly as s, waitUntilGatewayAgentAvailable as t };