openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
1,288 lines • 63.3 kB
JavaScript
import { w as parseStrictPositiveInteger } from "./number-coercion-CLj0HTDM.js";
import { c as isRecord } from "./record-coerce-DItp3I4t.js";
import { l as normalizeOptionalString, o as normalizeLowercaseStringOrEmpty } from "./string-coerce-CIXf7egm.js";
import { y as uniqueStrings } from "./string-normalization-DsCfAx8q.js";
import "./utils-P__uGsPB.js";
import { t as formatErrorMessage } from "./errors-Db3Ymjlb.js";
import { n as MANIFEST_KEY } from "./legacy-names-NIXaj2oi.js";
import { a as sha256Hex$1, t as sha256Base64 } from "./crypto-digest-C4hqTb_e.js";
import { t as BUNDLED_OFFICIAL_EXTERNAL_PLUGIN_CATALOGS } from "./official-external-plugin-bundled-catalogs-dAuKwD3c.js";
import { c as resolveOpenClawReleaseCohortVersion, o as parseRegistryNpmSpec, r as isExactSemverVersion } from "./npm-registry-spec-CM_p1_uq.js";
import { t as parseClawHubPluginSpec } from "./clawhub-spec-Er3Np6VI.js";
import { _ as requestClawHub, r as createClawHubError, u as readClawHubBytes, y as resolveClawHubBaseUrl } from "./clawhub-client-I4w9Lg5d.js";
import "./http-body-D3IMwTJJ.js";
import { i as readResponseWithLimit, t as cancelUnreadResponseBody } from "./http-response-body-CwT_cCNz.js";
import { n as createTempDownloadTarget } from "./temp-download-DkVm2g9b.js";
import { o as isBetaTag } from "./update-channels-BcfztK6k.js";
import { n as isUnavailableNpmTarget, t as PLUGIN_INSTALL_ERROR_CODE } from "./install-types-DY_kphq4.js";
import fs from "node:fs/promises";
import { createHash } from "node:crypto";
//#region src/plugins/plugin-install-default-choice.ts
function normalizePluginInstallDefaultChoice(value) {
return value === "clawhub" || value === "npm" || value === "local" ? value : void 0;
}
//#endregion
//#region src/infra/clawhub-artifacts.ts
const DEFAULT_GITHUB_CODELOAD_URL = "https://codeload.github.com";
function normalizeGitHubCodeloadBaseUrl() {
return (normalizeOptionalString(process.env.CLAWHUB_GITHUB_CODELOAD_BASE_URL) || DEFAULT_GITHUB_CODELOAD_URL).replace(/\/+$/, "") || DEFAULT_GITHUB_CODELOAD_URL;
}
function buildGitHubZipUrl(repo, commit) {
const url = new URL(`${normalizeGitHubCodeloadBaseUrl()}/`);
url.pathname = `${url.pathname.replace(/\/+$/, "")}/${repo.split("/").map((segment) => encodeURIComponent(segment)).join("/")}/zip/${encodeURIComponent(commit)}`;
return url.toString();
}
function formatSha512Integrity(bytes) {
return `sha512-${createHash("sha512").update(bytes).digest("base64")}`;
}
function formatSha1Hex(bytes) {
return createHash("sha1").update(bytes).digest("hex");
}
function safePackageTarballName(name, version) {
return `${name.replace(/^@/, "").replace(/[\\/]+/g, "-").replace(/[^A-Za-z0-9._-]/g, "-") || "package"}-${version}.tgz`;
}
async function stageClawHubArchive(params) {
const sha256Digest = params.sha256Hex ?? Buffer.from(sha256Base64(params.bytes), "base64").toString("hex");
const target = await createTempDownloadTarget(params);
try {
await fs.writeFile(target.path, params.bytes);
return {
archivePath: target.path,
integrity: `sha256-${Buffer.from(sha256Digest, "hex").toString("base64")}`,
sha256Hex: sha256Digest,
artifact: "archive",
...params.result,
cleanup: target.cleanup
};
} catch (error) {
await target.cleanup().catch(() => void 0);
throw error;
}
}
/** Normalizes ClawHub SHA-256 metadata into Subresource Integrity format. */
function normalizeClawHubSha256Integrity(value) {
const trimmed = value.trim();
if (!trimmed) return null;
const prefixedBase64 = /^sha256-([A-Za-z0-9+/]+={0,1})$/.exec(trimmed);
if (prefixedBase64?.[1]) {
try {
const decoded = Buffer.from(prefixedBase64[1], "base64");
if (decoded.length === 32) return `sha256-${decoded.toString("base64")}`;
} catch {
return null;
}
return null;
}
const prefixedHex = /^sha256:([A-Fa-f0-9]{64})$/.exec(trimmed);
if (prefixedHex?.[1]) return `sha256-${Buffer.from(prefixedHex[1], "hex").toString("base64")}`;
if (/^[A-Fa-f0-9]{64}$/.test(trimmed)) return `sha256-${Buffer.from(trimmed, "hex").toString("base64")}`;
return null;
}
/** Normalizes ClawHub SHA-256 metadata into lowercase hex form. */
function normalizeClawHubSha256Hex(value) {
const trimmed = value.trim();
if (!/^[A-Fa-f0-9]{64}$/.test(trimmed)) return null;
return normalizeLowercaseStringOrEmpty(trimmed);
}
async function downloadClawHubPackageArchive(params) {
if (params.artifact === "clawpack") {
if (!params.version) throw new Error("ClawPack package downloads require an explicit version.");
const { response, url, hasToken } = await requestClawHub({
baseUrl: params.baseUrl,
path: `/api/v1/packages/${encodeURIComponent(params.name)}/versions/${encodeURIComponent(params.version)}/artifact/download`,
token: params.token,
timeoutMs: params.timeoutMs,
fetchImpl: params.fetchImpl
});
if (!response.ok) throw await createClawHubError(response, url, hasToken, params.timeoutMs);
const bytes = await readClawHubBytes({
response,
timeoutMs: params.timeoutMs,
resourceLabel: `ClawPack download for ${params.name}@${params.version}`
});
const sha256Digest = sha256Hex$1(bytes);
const npmIntegrity = formatSha512Integrity(bytes);
const npmShasum = formatSha1Hex(bytes);
const headerSha256 = normalizeClawHubSha256Hex(response.headers.get("X-ClawHub-Artifact-Sha256") ?? response.headers.get("X-ClawHub-ClawPack-Sha256") ?? "");
if (!headerSha256) throw new Error(`ClawHub ClawPack download for "${params.name}@${params.version}" is missing X-ClawHub-Artifact-Sha256.`);
if (headerSha256 !== sha256Digest) throw new Error(`ClawHub ClawPack download for "${params.name}@${params.version}" declared sha256 ${headerSha256}, got ${sha256Digest}.`);
const headerNpmIntegrity = normalizeOptionalString(response.headers.get("X-ClawHub-Npm-Integrity"));
if (headerNpmIntegrity && headerNpmIntegrity !== npmIntegrity) throw new Error(`ClawHub ClawPack download for "${params.name}@${params.version}" declared npm integrity ${headerNpmIntegrity}, got ${npmIntegrity}.`);
const headerNpmShasum = normalizeOptionalString(response.headers.get("X-ClawHub-Npm-Shasum"));
if (headerNpmShasum && headerNpmShasum !== npmShasum) throw new Error(`ClawHub ClawPack download for "${params.name}@${params.version}" declared npm shasum ${headerNpmShasum}, got ${npmShasum}.`);
const npmTarballName = normalizeOptionalString(response.headers.get("X-ClawHub-Npm-Tarball-Name")) ?? safePackageTarballName(params.name, params.version);
const rawSpecVersion = response.headers.get("X-ClawHub-ClawPack-Spec-Version");
const specVersion = parseStrictPositiveInteger(rawSpecVersion);
return stageClawHubArchive({
prefix: "openclaw-clawhub-clawpack",
fileName: npmTarballName,
bytes,
sha256Hex: sha256Digest,
result: {
artifact: "clawpack",
clawpackHeaderSha256: headerSha256,
...typeof specVersion === "number" && Number.isSafeInteger(specVersion) && specVersion >= 0 ? { clawpackHeaderSpecVersion: specVersion } : {},
npmIntegrity,
npmShasum,
npmTarballName
}
});
}
const search = params.version ? { version: params.version } : params.tag ? { tag: params.tag } : void 0;
const { response, url, hasToken } = await requestClawHub({
baseUrl: params.baseUrl,
path: `/api/v1/packages/${encodeURIComponent(params.name)}/download`,
search,
token: params.token,
timeoutMs: params.timeoutMs,
fetchImpl: params.fetchImpl
});
if (!response.ok) throw await createClawHubError(response, url, hasToken, params.timeoutMs);
const bytes = await readClawHubBytes({
response,
timeoutMs: params.timeoutMs,
resourceLabel: `package archive download for ${params.name}`
});
return stageClawHubArchive({
prefix: "openclaw-clawhub-package",
fileName: `${params.name}.zip`,
bytes
});
}
async function downloadClawHubSkillArchive(params) {
const { response, url, hasToken } = await requestClawHub({
baseUrl: params.baseUrl,
path: "/api/v1/download",
token: params.token,
timeoutMs: params.timeoutMs,
fetchImpl: params.fetchImpl,
search: {
slug: params.slug,
ownerHandle: params.ownerHandle,
version: params.version,
tag: params.version ? void 0 : params.tag
}
});
if (!response.ok) throw await createClawHubError(response, url, hasToken, params.timeoutMs);
const bytes = await readClawHubBytes({
response,
timeoutMs: params.timeoutMs,
resourceLabel: `skill archive download for ${params.slug}`
});
return stageClawHubArchive({
prefix: "openclaw-clawhub-skill",
fileName: `${params.slug}.zip`,
bytes
});
}
async function downloadClawHubSkillArchiveUrl(params) {
const providedToken = normalizeOptionalString(params.token);
const requestUrl = new URL(params.url, `${resolveClawHubBaseUrl(params.baseUrl)}/`);
const registryOrigin = new URL(`${resolveClawHubBaseUrl(params.baseUrl)}/`).origin;
const skipAuth = providedToken == null && requestUrl.origin !== registryOrigin;
const { response, url, hasToken } = await requestClawHub({
baseUrl: params.baseUrl,
url: params.url,
token: providedToken,
timeoutMs: params.timeoutMs,
fetchImpl: params.fetchImpl,
skipAuth
});
if (!response.ok) throw await createClawHubError(response, url, hasToken, params.timeoutMs);
return stageClawHubArchive({
prefix: "openclaw-clawhub-skill",
fileName: "skill.zip",
bytes: await readClawHubBytes({
response,
timeoutMs: params.timeoutMs,
resourceLabel: `skill archive download at ${url.pathname}`
})
});
}
async function downloadClawHubGitHubSkillArchive(params) {
const downloadUrl = buildGitHubZipUrl(params.repo, params.commit);
const { response, url, hasToken } = await requestClawHub({
url: downloadUrl,
skipAuth: true,
timeoutMs: params.timeoutMs,
fetchImpl: params.fetchImpl
});
if (!response.ok) throw await createClawHubError(response, url, hasToken, params.timeoutMs);
const bytes = await readClawHubBytes({
response,
timeoutMs: params.timeoutMs,
resourceLabel: `GitHub source archive for ${params.repo}@${params.commit}`
});
return stageClawHubArchive({
prefix: "openclaw-clawhub-github-skill",
fileName: `${params.commit}.zip`,
bytes
});
}
//#endregion
//#region src/plugins/clawhub-error-codes.ts
/** Stable ClawHub install error codes used by plugin install policy and diagnostics. */
const CLAWHUB_INSTALL_ERROR_CODE = {
INVALID_SPEC: "invalid_spec",
PACKAGE_NOT_FOUND: "package_not_found",
VERSION_NOT_FOUND: "version_not_found",
NO_INSTALLABLE_VERSION: "no_installable_version",
SKILL_PACKAGE: "skill_package",
UNSUPPORTED_FAMILY: "unsupported_family",
PRIVATE_PACKAGE: "private_package",
INCOMPATIBLE_PLUGIN_API: "incompatible_plugin_api",
INVALID_GATEWAY_VERSION: "invalid_gateway_version",
UNKNOWN_GATEWAY_VERSION: "unknown_gateway_version",
INCOMPATIBLE_GATEWAY: "incompatible_gateway",
ARTIFACT_UNAVAILABLE: "artifact_unavailable",
MISSING_ARCHIVE_INTEGRITY: "missing_archive_integrity",
ARTIFACT_DOWNLOAD_UNAVAILABLE: "artifact_download_unavailable",
ARCHIVE_INTEGRITY_MISMATCH: "archive_integrity_mismatch",
CLAWHUB_SECURITY_UNAVAILABLE: "clawhub_security_unavailable",
CLAWHUB_DOWNLOAD_BLOCKED: "clawhub_download_blocked"
};
/**
* Detects ClawHub failures caused by a target that is not published, as opposed
* to a broken install. Channel-aware installs use this to widen the selector
* instead of failing when the requested release has no artifact.
*/
function isUnavailableClawHubTarget(result) {
return result.code === CLAWHUB_INSTALL_ERROR_CODE.PACKAGE_NOT_FOUND || result.code === CLAWHUB_INSTALL_ERROR_CODE.VERSION_NOT_FOUND;
}
//#endregion
//#region src/plugins/install-channel-specs.ts
/** Only declared identities participate; a ClawHub slug never implies an npm package. */
function resolvePluginInstallSources(install, explicitSource) {
const sources = [];
for (const source of ["npm", "clawhub"]) {
const spec = (source === "npm" ? install.npmSpec : install.clawhubSpec)?.trim();
if (!spec || explicitSource && source !== explicitSource) continue;
const integritySource = install.npmSpec?.trim() ? "npm" : "clawhub";
sources.push({
source,
spec,
...source === integritySource && install.expectedIntegrity ? { expectedIntegrity: install.expectedIntegrity } : {}
});
}
return sources;
}
function isUnavailablePluginSource(source, result) {
if (result.ok) return false;
return source === "npm" ? result.code === PLUGIN_INSTALL_ERROR_CODE.RELEASE_COHORT_UNAVAILABLE || isUnavailableNpmTarget({
ok: false,
code: result.code
}) : isUnavailableClawHubTarget({
ok: false,
code: result.code
}) || result.code === CLAWHUB_INSTALL_ERROR_CODE.ARTIFACT_UNAVAILABLE || result.code === CLAWHUB_INSTALL_ERROR_CODE.ARTIFACT_DOWNLOAD_UNAVAILABLE;
}
/** Availability alone permits a declared secondary; every attempt owns its artifact review. */
async function installWithSourceFallback(params) {
for (const [index, source] of params.sources.entries()) {
const attempt = await params.install(source);
const secondary = params.sources[index + 1];
if (!secondary || !isUnavailablePluginSource(source.source, params.result(attempt))) return {
attempt,
source
};
await params.onFallback(`${source.spec} unavailable; using ${secondary.spec} instead.`);
}
throw new Error("Plugin has no declared remote install source.");
}
/** Bare specs and latest retain default intent while following the active release channel. */
function resolveDefaultNpmSpec(spec) {
const parsed = parseRegistryNpmSpec(spec);
if (!parsed) return null;
if (parsed.selectorKind === "none" || parsed.selectorKind === "tag" && parsed.selector?.toLowerCase() === "latest") return parsed;
return null;
}
function resolveNpmInstallSpecsForUpdateChannel(params) {
if (params.updateChannel === "extended-stable" || params.updateChannel === "stable" && params.versionBoundToCore) {
const target = resolveDefaultNpmSpec(params.spec);
if (target && params.officialPackageName === target.name) {
const coreVersion = params.coreVersion?.trim();
if (!coreVersion || !isExactSemverVersion(coreVersion)) {
const policy = params.updateChannel === "extended-stable" ? "Extended-stable" : "Version-bound";
throw new Error(`${policy} plugin resolution for ${target.name} requires an exact core version.`);
}
const installVersion = params.versionBoundToCore ? resolveOpenClawReleaseCohortVersion(coreVersion) : coreVersion;
return {
installSpec: `${target.name}@${installVersion}`,
recordSpec: params.spec
};
}
return {
installSpec: params.spec,
recordSpec: params.spec
};
}
const betaTarget = resolveDefaultNpmSpec(params.spec);
if (params.updateChannel !== "beta" || !betaTarget) return {
installSpec: params.spec,
recordSpec: params.spec
};
const coreVersion = params.coreVersion?.trim();
const betaVersion = params.officialPackageName === betaTarget.name && coreVersion && isExactSemverVersion(coreVersion) && isBetaTag(coreVersion) ? coreVersion : "beta";
const betaSpec = `${betaTarget.name}@${betaVersion}`;
return {
installSpec: betaSpec,
recordSpec: params.spec,
fallbackSpec: params.spec,
fallbackLabel: betaSpec
};
}
function resolveClawHubInstallSpecsForUpdateChannel(params) {
const parsed = parseClawHubPluginSpec(params.spec);
if (parsed && params.officialPackageName === parsed.name && (params.updateChannel === "extended-stable" || params.updateChannel === "stable" && params.versionBoundToCore)) return {
installSpec: `clawhub:${resolveNpmInstallSpecsForUpdateChannel({
...params,
spec: `${parsed.name}${parsed.version ? `@${parsed.version}` : ""}`
}).installSpec}`,
recordSpec: params.spec
};
if (params.updateChannel !== "beta" || !parsed || parsed.version && parsed.version.toLowerCase() !== "latest") return {
installSpec: params.spec,
recordSpec: params.spec
};
const betaSpec = `clawhub:${params.officialPackageName === parsed.name ? resolveNpmInstallSpecsForUpdateChannel({
...params,
spec: parsed.name
}).installSpec : `${parsed.name}@beta`}`;
return {
installSpec: betaSpec,
recordSpec: params.spec,
fallbackSpec: params.spec,
fallbackLabel: betaSpec
};
}
/**
* Installs the channel-resolved spec, widening to the operator's own selector
* when that release has no published artifact. The degrade is announced rather
* than silent, because it changes which build the operator ends up running.
*/
async function installWithChannelFallback(params) {
const result = await params.install(params.installSpec);
const { fallbackSpec } = params;
if (!fallbackSpec || fallbackSpec === params.installSpec || !params.isRetryable(result)) return result;
await params.onFallback(`No ${params.installSpec} release is published; installing ${fallbackSpec} instead.`);
return await params.install(fallbackSpec);
}
//#endregion
//#region src/plugins/official-external-plugin-catalog.ts
/** Reads official external plugin/channel/provider catalogs into manifest-like metadata. */
var HostedCatalogSnapshotWriteError = class extends Error {
constructor(originalError) {
super("hosted catalog snapshot write failed");
this.name = "HostedCatalogSnapshotWriteError";
this.originalError = originalError;
}
};
var HostedCatalogSignedFeedMonotonicityError = class extends Error {
constructor(message) {
super(message);
this.name = "HostedCatalogSignedFeedMonotonicityError";
}
};
const SUPPORTED_OFFICIAL_EXTERNAL_CATALOG_FEED_SCHEMA_VERSIONS = /* @__PURE__ */ new Set([1, 2]);
const DEFAULT_OFFICIAL_EXTERNAL_PLUGIN_CATALOG_FEED_URL = "https://clawhub.ai/v1/feeds/plugins";
const DEFAULT_OFFICIAL_EXTERNAL_PLUGIN_CATALOG_FEED_PROFILE = "clawhub-public";
const DEFAULT_OFFICIAL_EXTERNAL_PLUGIN_CATALOG_FEED_ID = "clawhub-official";
const DEFAULT_OFFICIAL_EXTERNAL_PLUGIN_CATALOG_CLAWHUB_SOURCE_REF = "public-clawhub";
const DEFAULT_OFFICIAL_EXTERNAL_PLUGIN_CATALOG_NPM_SOURCE_REF = "public-npm";
const DEFAULT_OFFICIAL_EXTERNAL_PLUGIN_CATALOG_CLAWHUB_TRUSTED_KEYS = [];
const DEFAULT_OFFICIAL_EXTERNAL_PLUGIN_CATALOG_PROFILE_CONFIG = {
feeds: { [DEFAULT_OFFICIAL_EXTERNAL_PLUGIN_CATALOG_FEED_PROFILE]: {
url: DEFAULT_OFFICIAL_EXTERNAL_PLUGIN_CATALOG_FEED_URL,
feedId: DEFAULT_OFFICIAL_EXTERNAL_PLUGIN_CATALOG_FEED_ID
} },
sources: {
[DEFAULT_OFFICIAL_EXTERNAL_PLUGIN_CATALOG_CLAWHUB_SOURCE_REF]: {
type: "clawhub",
baseUrl: "https://clawhub.ai"
},
[DEFAULT_OFFICIAL_EXTERNAL_PLUGIN_CATALOG_NPM_SOURCE_REF]: {
type: "npm",
registry: "https://registry.npmjs.org/"
}
}
};
const DEFAULT_HOSTED_OFFICIAL_EXTERNAL_PLUGIN_CATALOG_TIMEOUT_MS = 5e3;
const DEFAULT_HOSTED_OFFICIAL_EXTERNAL_PLUGIN_CATALOG_MAX_BYTES = 1048576;
const DEFAULT_HOSTED_OFFICIAL_EXTERNAL_PLUGIN_CATALOG_CHUNK_TIMEOUT_MS = 5e3;
const DSSE_ENVELOPE_MEDIA_TYPE = "application/vnd.dsse+json";
const OFFICIAL_EXTERNAL_PLUGIN_CATALOG_FEED_HOSTNAME_ALLOWLIST = ["clawhub.ai"];
const ISO_CALENDAR_DATE_PREFIX_RE = /^(\d{4})-(\d{2})-(\d{2})/u;
function parseOfficialExternalPluginCatalogTimestamp(value) {
const timestamp = value.trim();
const parsed = Date.parse(timestamp);
if (!Number.isFinite(parsed)) return;
const calendarDate = ISO_CALENDAR_DATE_PREFIX_RE.exec(timestamp);
if (!calendarDate) return parsed;
const year = Number(calendarDate[1]);
const month = Number(calendarDate[2]);
const day = Number(calendarDate[3]);
const daysInMonth = [
31,
year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0) ? 29 : 28,
31,
30,
31,
30,
31,
31,
30,
31,
30,
31
];
return month >= 1 && month <= 12 && day >= 1 && day <= daysInMonth[month - 1] ? parsed : void 0;
}
function isOfficialExternalPluginCatalogSequence(value) {
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
}
function isOfficialExternalPluginCatalogFeed(raw) {
if (!isRecord(raw)) return false;
const sequence = raw.sequence;
const generatedAt = raw.generatedAt;
const generatedAtMs = typeof generatedAt === "string" ? parseOfficialExternalPluginCatalogTimestamp(generatedAt) : void 0;
const entries = raw.entries;
return typeof raw.schemaVersion === "number" && SUPPORTED_OFFICIAL_EXTERNAL_CATALOG_FEED_SCHEMA_VERSIONS.has(raw.schemaVersion) && typeof raw.id === "string" && raw.id.trim().length > 0 && typeof generatedAt === "string" && generatedAt.trim().length > 0 && generatedAtMs !== void 0 && isOfficialExternalPluginCatalogSequence(sequence) && Array.isArray(entries);
}
function parseOfficialExternalPluginCatalogEntries(raw) {
if (Array.isArray(raw)) return raw.filter((entry) => isRecord(entry));
if (isOfficialExternalPluginCatalogFeed(raw)) return raw.entries.filter((entry) => isRecord(entry));
if (!isRecord(raw)) return [];
if ("schemaVersion" in raw) return [];
const list = raw.entries ?? raw.packages ?? raw.plugins;
if (!Array.isArray(list)) return [];
return list.filter((entry) => isRecord(entry));
}
function normalizeHostedCatalogHeader(value) {
return normalizeOptionalString(value) || void 0;
}
function sha256Hex(value) {
return `sha256:${createHash("sha256").update(value).digest("hex")}`;
}
function resolveHostedCatalogFeedUrl(raw) {
let parsed;
try {
parsed = new URL(raw.trim());
} catch {
throw new Error("hosted catalog feed URL is invalid");
}
if (parsed.protocol !== "https:") throw new Error("hosted catalog feed URL must use HTTPS");
if (parsed.username || parsed.password) throw new Error("hosted catalog feed URL must not include credentials");
if (parsed.search || parsed.hash) throw new Error("hosted catalog feed URL must not include query strings or fragments");
return parsed;
}
function resolveOfficialExternalPluginCatalogProfileConfig(config) {
const configuredDefaultFeed = config?.feeds?.[DEFAULT_OFFICIAL_EXTERNAL_PLUGIN_CATALOG_FEED_PROFILE];
const bundledVerification = DEFAULT_OFFICIAL_EXTERNAL_PLUGIN_CATALOG_CLAWHUB_TRUSTED_KEYS.length > 0 ? {
mode: "signed",
keys: DEFAULT_OFFICIAL_EXTERNAL_PLUGIN_CATALOG_CLAWHUB_TRUSTED_KEYS
} : void 0;
const defaultFeed = DEFAULT_OFFICIAL_EXTERNAL_PLUGIN_CATALOG_PROFILE_CONFIG.feeds?.[DEFAULT_OFFICIAL_EXTERNAL_PLUGIN_CATALOG_FEED_PROFILE] ?? {
url: DEFAULT_OFFICIAL_EXTERNAL_PLUGIN_CATALOG_FEED_URL,
feedId: DEFAULT_OFFICIAL_EXTERNAL_PLUGIN_CATALOG_FEED_ID
};
return {
feeds: {
...config?.feeds,
[DEFAULT_OFFICIAL_EXTERNAL_PLUGIN_CATALOG_FEED_PROFILE]: {
...defaultFeed,
...bundledVerification ? { verification: bundledVerification } : {},
...configuredDefaultFeed
}
},
sources: {
...DEFAULT_OFFICIAL_EXTERNAL_PLUGIN_CATALOG_PROFILE_CONFIG.sources,
...config?.sources
}
};
}
function resolveHostedCatalogFeedSource(params) {
const explicitFeedUrl = normalizeOptionalString(params.feedUrl);
const explicitProfileName = normalizeOptionalString(params.feedProfile);
if (explicitFeedUrl) {
const url = resolveHostedCatalogFeedUrl(explicitFeedUrl);
if (!OFFICIAL_EXTERNAL_PLUGIN_CATALOG_FEED_HOSTNAME_ALLOWLIST.includes(url.hostname)) throw new Error("hosted catalog feed URL hostname is not allowed");
const defaultProfile = explicitProfileName === void 0 ? resolveOfficialExternalPluginCatalogProfileConfig(params.catalogConfig).feeds[DEFAULT_OFFICIAL_EXTERNAL_PLUGIN_CATALOG_FEED_PROFILE] : void 0;
const profileName = explicitProfileName ?? (defaultProfile && resolveHostedCatalogFeedUrl(defaultProfile.url).href === url.href ? DEFAULT_OFFICIAL_EXTERNAL_PLUGIN_CATALOG_FEED_PROFILE : void 0);
const profileConfig = profileName === void 0 ? void 0 : resolveOfficialExternalPluginCatalogProfileConfig(params.catalogConfig);
const profile = profileName === void 0 ? void 0 : profileConfig?.feeds[profileName];
if (profileName !== void 0 && !profile) throw new Error(`hosted catalog feed profile "${profileName}" is not configured`);
return {
url,
hostnameAllowlist: OFFICIAL_EXTERNAL_PLUGIN_CATALOG_FEED_HOSTNAME_ALLOWLIST,
...profile?.feedId ? { expectedFeedId: profile.feedId } : {},
...profile?.verification ? { verification: profile.verification } : {}
};
}
const profileName = explicitProfileName ?? DEFAULT_OFFICIAL_EXTERNAL_PLUGIN_CATALOG_FEED_PROFILE;
const profile = resolveOfficialExternalPluginCatalogProfileConfig(params.catalogConfig).feeds[profileName];
if (!profile) throw new Error(`hosted catalog feed profile "${profileName}" is not configured`);
const url = resolveHostedCatalogFeedUrl(profile.url);
return {
url,
hostnameAllowlist: uniqueStrings([...OFFICIAL_EXTERNAL_PLUGIN_CATALOG_FEED_HOSTNAME_ALLOWLIST, url.hostname]),
...profile.feedId ? { expectedFeedId: profile.feedId } : {},
verification: profile.verification
};
}
function getFeedEntryInstallCandidateRecords(entry) {
const candidates = (isRecord(entry.install) ? entry.install : void 0)?.candidates;
if (!Array.isArray(candidates)) return [];
return candidates.filter((candidate) => isRecord(candidate));
}
function getFeedEntryInstallCandidates(entry) {
if (normalizeOptionalString(entry.state) !== "available") return [];
if (normalizeOptionalString(entry.publisher?.trust) !== "official") return [];
return getFeedEntryInstallCandidateRecords(entry);
}
function shouldRequireManifestInstallSourceRef(params) {
const feedUrl = normalizeOptionalString(params.feedUrl);
if (feedUrl) try {
return resolveHostedCatalogFeedUrl(feedUrl).href !== resolveHostedCatalogFeedUrl(DEFAULT_OFFICIAL_EXTERNAL_PLUGIN_CATALOG_FEED_URL).href;
} catch {
return true;
}
const profileName = normalizeOptionalString(params.feedProfile) ?? DEFAULT_OFFICIAL_EXTERNAL_PLUGIN_CATALOG_FEED_PROFILE;
if (profileName !== DEFAULT_OFFICIAL_EXTERNAL_PLUGIN_CATALOG_FEED_PROFILE) return true;
const profileConfig = resolveOfficialExternalPluginCatalogProfileConfig(params.catalogConfig);
const profileUrl = normalizeOptionalString(profileConfig.feeds[profileName]?.url);
try {
return resolveHostedCatalogFeedUrl(profileUrl ?? DEFAULT_OFFICIAL_EXTERNAL_PLUGIN_CATALOG_FEED_URL).href !== resolveHostedCatalogFeedUrl(DEFAULT_OFFICIAL_EXTERNAL_PLUGIN_CATALOG_FEED_URL).href;
} catch {
return true;
}
}
function getManifestInstallSourceRefCandidate(entry) {
const install = getOfficialExternalPluginCatalogManifest(entry)?.install;
if (!install) return;
if (!Boolean(normalizeOptionalString(install.clawhubSpec) || normalizeOptionalString(install.npmSpec) || normalizeOptionalString(install.localPath))) return;
return {
sourceRef: normalizeOptionalString(install.sourceRef),
package: normalizeOptionalString(install.npmSpec) ?? normalizeOptionalString(install.clawhubSpec)
};
}
function filterOfficialExternalPluginCatalogEntriesBySourceRefs(entries, params) {
let configuredSourceRefs;
return entries.filter((entry) => {
configuredSourceRefs ??= new Set(Object.keys(resolveOfficialExternalPluginCatalogProfileConfig(params?.catalogConfig).sources));
let candidates = getFeedEntryInstallCandidateRecords(entry);
if (params?.requireManifestInstallSourceRef) {
const manifestCandidate = getManifestInstallSourceRefCandidate(entry);
if (manifestCandidate) candidates = [...candidates, manifestCandidate];
else if (candidates.length === 0) candidates = [{}];
}
let valid = true;
for (const candidate of candidates) {
const sourceRef = normalizeOptionalString(candidate.sourceRef);
if (!sourceRef || !configuredSourceRefs.has(sourceRef)) valid = false;
}
return valid;
});
}
function parseHostedCatalogContentLength(raw, maxBytes) {
const normalized = normalizeOptionalString(raw);
if (!normalized) return;
if (!/^\d+$/.test(normalized)) throw new Error("hosted catalog feed has invalid content-length");
const size = Number(normalized);
if (!Number.isSafeInteger(size) || size > maxBytes) throw new Error(`hosted catalog feed exceeds ${maxBytes} bytes`);
}
async function readHostedCatalogResponseText(params) {
parseHostedCatalogContentLength(params.response.headers.get("content-length"), params.maxBytes);
if (!params.response.body || typeof params.response.body.getReader !== "function") throw new Error("hosted catalog feed streaming response body unavailable");
const buffer = await readResponseWithLimit(params.response, params.maxBytes, {
chunkTimeoutMs: params.chunkTimeoutMs,
onOverflow: ({ maxBytes }) => /* @__PURE__ */ new Error(`hosted catalog feed exceeds ${maxBytes} bytes`),
onIdleTimeout: ({ chunkTimeoutMs }) => /* @__PURE__ */ new Error(`hosted catalog feed read timed out after ${chunkTimeoutMs}ms`)
});
return new TextDecoder("utf-8", { fatal: true }).decode(buffer);
}
function bundledOfficialExternalPluginCatalogEntries() {
return BUNDLED_OFFICIAL_EXTERNAL_PLUGIN_CATALOGS.flatMap((source) => filterOfficialExternalPluginCatalogEntriesBySourceRefs(parseOfficialExternalPluginCatalogEntries(source)));
}
function dedupeOfficialExternalPluginCatalogEntries(entries) {
const resolved = /* @__PURE__ */ new Map();
for (const entry of entries) {
const key = resolveOfficialExternalPluginCatalogEntryKey(entry);
if (key && !resolved.has(key)) resolved.set(key, entry);
}
return [...resolved.values()];
}
function resolveOfficialExternalPluginCatalogEntryKey(entry) {
const pluginId = resolveOfficialExternalPluginId(entry);
if (pluginId) return `${normalizeOptionalString(entry.kind) ?? "plugin"}:${pluginId}`;
const name = normalizeOptionalString(entry.name);
if (name) return name;
const id = normalizeOptionalString(entry.id);
if (id) return `${normalizeOptionalString(entry.kind) ?? normalizeOptionalString(entry.type) ?? "plugin"}:${id}`;
}
function bundledFallbackResult(error, metadata) {
return {
source: "bundled-fallback",
entries: listOfficialExternalPluginCatalogEntries(),
error: formatErrorMessage(error),
...metadata ? { metadata } : {}
};
}
function emptyBundledFallbackResult(error) {
return {
source: "bundled-fallback",
entries: [],
error: formatErrorMessage(error)
};
}
async function parseHostedCatalogFeedBody(params) {
const raw = JSON.parse(params.body);
if (params.verification?.mode === "signed") {
const { verifyOfficialExternalPluginCatalogSignedEnvelope } = await import("./official-external-plugin-catalog-envelope-BOz7uwz_.js");
const threshold = params.verification.threshold ?? 1;
const verification = verifyOfficialExternalPluginCatalogSignedEnvelope(raw, {
trustedKeys: params.verification.keys,
threshold,
...params.allowLegacyBetaEnvelope ? { allowLegacyBetaEnvelope: true } : {}
});
if (!verification.ok) {
const invalidTimestampSequence = verification.error === "invalid-payload" && "authenticatedPayload" in verification ? readOfficialExternalPluginCatalogInvalidTimestampSequence(verification.authenticatedPayload) : void 0;
if (invalidTimestampSequence !== void 0) throw new HostedCatalogFeedTimestampError(verification.message, invalidTimestampSequence);
throw new Error(verification.message);
}
if (params.expectedFeedId && verification.feed.id !== params.expectedFeedId) throw new Error(`hosted catalog feed id "${verification.feed.id}" did not match expected "${params.expectedFeedId}"`);
const generatedAtMs = parseOfficialExternalPluginCatalogTimestamp(verification.feed.generatedAt);
const expiresAt = normalizeOptionalString(verification.feed.expiresAt);
if (generatedAtMs === void 0) throw new Error("hosted catalog signed feed requires a valid generatedAt value");
let expired;
if (!expiresAt) {
if (params.allowMissingExpiry !== true) throw new Error("hosted catalog signed feed requires a valid expiresAt value");
expired = true;
} else {
const expiresAtMs = parseOfficialExternalPluginCatalogTimestamp(expiresAt);
if (expiresAtMs === void 0) throw new Error("hosted catalog signed feed requires a valid expiresAt value");
if (expiresAtMs <= generatedAtMs) throw new Error("hosted catalog signed feed expiresAt must be later than generatedAt");
expired = expiresAtMs <= params.now.getTime();
}
if (expired && params.allowExpired !== true) throw new Error(expiresAt ? `hosted catalog signed feed expired at ${expiresAt}` : "hosted catalog signed feed has no expiresAt");
return {
feed: enforceHostedCatalogFeedInstallAuthority(verification.feed),
trust: {
mode: "signed",
signedBy: verification.signedBy,
signatureCount: verification.signatureCount ?? 1,
threshold,
verifiedAt: params.verifiedAt
},
...expired ? { expired: true } : {}
};
}
if (!isOfficialExternalPluginCatalogFeed(raw)) throw new Error("hosted catalog feed did not match a supported schema version");
if (params.expectedFeedId && raw.id !== params.expectedFeedId) throw new Error(`hosted catalog feed id "${raw.id}" did not match expected "${params.expectedFeedId}"`);
return { feed: enforceHostedCatalogFeedInstallAuthority(raw) };
}
function enforceHostedCatalogFeedInstallAuthority(feed) {
if (feed.schemaVersion < 2) return feed;
return {
...feed,
entries: feed.entries.map((entry) => {
const state = normalizeOptionalString(entry.state);
const publisherTrust = normalizeOptionalString(entry.publisher?.trust);
return state === "available" && publisherTrust === "official" ? entry : removeOfficialExternalPluginCatalogInstallAuthority(entry);
})
};
}
var HostedCatalogFeedTimestampError = class extends Error {
constructor(message, sequence) {
super(message);
this.sequence = sequence;
}
};
function readOfficialExternalPluginCatalogInvalidTimestampSequence(raw) {
if (!isRecord(raw)) return;
if (typeof raw.generatedAt === "string" && parseOfficialExternalPluginCatalogTimestamp(raw.generatedAt) !== void 0) return;
const normalized = {
...raw,
generatedAt: "1970-01-01T00:00:00.000Z"
};
return isOfficialExternalPluginCatalogFeed(normalized) ? normalized.sequence : void 0;
}
async function loadHostedCatalogSnapshotResult(params) {
assertSnapshotMatchesRequestValidators({
snapshot: params.snapshot,
ifNoneMatch: params.ifNoneMatch,
ifModifiedSince: params.ifModifiedSince
});
const checksum = sha256Hex(params.snapshot.body);
if (checksum !== params.snapshot.metadata.checksum) throw new Error("hosted catalog snapshot checksum mismatch");
if (params.expectedSha256 && params.expectedSha256 !== checksum) throw new Error("hosted catalog snapshot checksum did not match expected checksum");
const parsed = await parseHostedCatalogFeedBody({
body: params.snapshot.body,
expectedFeedId: params.expectedFeedId,
verification: params.verification,
verifiedAt: params.snapshot.trust?.verifiedAt ?? params.snapshot.savedAt,
allowLegacyBetaEnvelope: true,
now: params.now,
allowExpired: true,
allowMissingExpiry: true
});
const entries = dedupeOfficialExternalPluginCatalogEntries(filterOfficialExternalPluginCatalogEntriesBySourceRefs(parseOfficialExternalPluginCatalogEntries(parsed.feed), {
catalogConfig: params.catalogConfig,
requireManifestInstallSourceRef: params.requireManifestInstallSourceRef
}));
const visibleEntries = parsed.expired ? entries.map((entry) => removeOfficialExternalPluginCatalogInstallAuthority(entry)) : entries;
return {
source: "hosted-snapshot",
entries: visibleEntries,
feed: parsed.expired ? {
...parsed.feed,
entries: visibleEntries
} : parsed.feed,
metadata: params.snapshot.metadata,
snapshot: params.snapshot,
...parsed.trust ? { trust: parsed.trust } : {},
error: parsed.expired ? `${formatErrorMessage(params.error)}; ${parsed.feed.expiresAt ? `hosted catalog signed feed expired at ${parsed.feed.expiresAt}` : "hosted catalog signed feed has no expiresAt"}` : formatErrorMessage(params.error)
};
}
function removeOfficialExternalPluginCatalogInstallAuthority(entry) {
const { install: _feedInstall, [MANIFEST_KEY]: manifest, ...metadata } = entry;
if (!manifest) return {
...metadata,
state: "unavailable"
};
const { install: _manifestInstall, ...manifestMetadata } = manifest;
return {
...metadata,
state: "unavailable",
[MANIFEST_KEY]: manifestMetadata
};
}
function isHostedCatalogSignedFeedRollback(params) {
if (params.candidate.sequence < params.current.sequence) return true;
if (params.candidate.sequence > params.current.sequence) return false;
if (params.current.generatedAt === void 0) return false;
return Date.parse(params.candidate.generatedAt) < Date.parse(params.current.generatedAt);
}
function assertSnapshotMatchesRequestValidators(params) {
if (params.ifNoneMatch && params.snapshot.metadata.etag !== params.ifNoneMatch) throw new Error("hosted catalog snapshot ETag did not match request validator");
if (!params.ifNoneMatch && params.ifModifiedSince && params.snapshot.metadata.lastModified !== params.ifModifiedSince) throw new Error("hosted catalog snapshot Last-Modified did not match request validator");
}
async function snapshotOrBundledFallbackResult(params) {
if (params.snapshotStore) try {
const snapshot = await params.snapshotStore.read(params.url);
if (snapshot) return await loadHostedCatalogSnapshotResult({
snapshot,
error: params.error,
expectedSha256: params.expectedSha256,
ifNoneMatch: params.ifNoneMatch,
ifModifiedSince: params.ifModifiedSince,
catalogConfig: params.catalogConfig,
requireManifestInstallSourceRef: params.requireManifestInstallSourceRef,
expectedFeedId: params.expectedFeedId,
verification: params.verification,
now: params.now
});
} catch (snapshotErr) {
if (params.verification?.mode === "signed") return emptyBundledFallbackResult(`${formatErrorMessage(params.error)}; snapshot fallback failed: ${formatErrorMessage(snapshotErr)}`);
return bundledFallbackResult(`${formatErrorMessage(params.error)}; snapshot fallback failed: ${formatErrorMessage(snapshotErr)}`, params.metadata);
}
if (params.verification?.mode === "signed") return emptyBundledFallbackResult(params.error);
return bundledFallbackResult(params.error, params.metadata);
}
async function resolveHostedCatalogSnapshotStore(params) {
if (params.snapshotStore !== void 0) return params.snapshotStore ?? void 0;
const { createSqliteHostedOfficialExternalPluginCatalogSnapshotStore } = await import("./official-external-plugin-catalog-snapshot-store-CvlOD0gL.js");
return createSqliteHostedOfficialExternalPluginCatalogSnapshotStore({
...params.env ? { env: params.env } : {},
...params.stateDir ? { stateDir: params.stateDir } : {},
...params.stateDatabasePath ? { stateDatabasePath: params.stateDatabasePath } : {}
});
}
async function loadHostedOfficialExternalPluginCatalogEntries(params) {
let source;
try {
source = resolveHostedCatalogFeedSource({
feedUrl: params?.feedUrl,
feedProfile: params?.feedProfile,
catalogConfig: params?.catalogConfig
});
} catch (err) {
return bundledFallbackResult(err);
}
const { url } = source;
const snapshotStore = await resolveHostedCatalogSnapshotStore({
snapshotStore: params?.snapshotStore,
env: params?.env,
stateDir: params?.stateDir,
stateDatabasePath: params?.stateDatabasePath
});
const expectedSha256 = normalizeOptionalString(params?.expectedSha256);
const currentTime = () => params?.now?.() ?? /* @__PURE__ */ new Date();
const requireManifestInstallSourceRef = shouldRequireManifestInstallSourceRef({
feedUrl: params?.feedUrl,
feedProfile: params?.feedProfile,
catalogConfig: params?.catalogConfig
});
if (params?.offline === true) return await snapshotOrBundledFallbackResult({
error: "hosted catalog feed offline mode",
snapshotStore,
url: url.href,
expectedSha256,
catalogConfig: params?.catalogConfig,
requireManifestInstallSourceRef,
expectedFeedId: source.expectedFeedId,
verification: source.verification,
now: currentTime()
});
const headers = new Headers();
const ifNoneMatch = normalizeOptionalString(params?.ifNoneMatch);
const signedOperation = source.verification?.mode === "signed";
const ifModifiedSince = signedOperation ? void 0 : normalizeOptionalString(params?.ifModifiedSince);
if (ifNoneMatch) headers.set("if-none-match", ifNoneMatch);
if (ifModifiedSince) headers.set("if-modified-since", ifModifiedSince);
if (signedOperation) headers.set("accept", DSSE_ENVELOPE_MEDIA_TYPE);
const metadataBase = (response) => {
const etag = normalizeHostedCatalogHeader(response.headers.get("etag"));
const lastModified = normalizeHostedCatalogHeader(response.headers.get("last-modified"));
return {
url: url.href,
status: response.status,
...etag ? { etag } : {},
...lastModified ? { lastModified } : {}
};
};
let response;
let release;
try {
const { fetchWithSsrFGuard } = await import("./fetch-guard-BckiUump.js");
const guarded = await fetchWithSsrFGuard({
url: url.href,
fetchImpl: params?.fetchImpl,
init: {
method: "GET",
headers
},
requireHttps: true,
maxRedirects: 2,
timeoutMs: params?.timeoutMs ?? DEFAULT_HOSTED_OFFICIAL_EXTERNAL_PLUGIN_CATALOG_TIMEOUT_MS,
policy: { hostnameAllowlist: source.hostnameAllowlist },
auditContext: "official-external-plugin-catalog-feed"
});
response = guarded.response;
release = guarded.release;
const base = metadataBase(response);
if (response.status === 304) return await snapshotOrBundledFallbackResult({
error: "hosted catalog feed returned HTTP 304",
snapshotStore,
url: url.href,
metadata: base,
expectedSha256,
ifNoneMatch,
ifModifiedSince,
catalogConfig: params?.catalogConfig,
requireManifestInstallSourceRef,
expectedFeedId: source.expectedFeedId,
verification: source.verification,
now: currentTime()
});
if (!response.ok) return await snapshotOrBundledFallbackResult({
error: `hosted catalog feed returned HTTP ${response.status}`,
snapshotStore,
url: url.href,
metadata: base,
expectedSha256,
ifNoneMatch,
ifModifiedSince,
catalogConfig: params?.catalogConfig,
requireManifestInstallSourceRef,
expectedFeedId: source.expectedFeedId,
verification: source.verification,
now: currentTime()
});
if (signedOperation && response.headers.get("content-type")?.split(";", 1)[0]?.trim().toLowerCase() !== DSSE_ENVELOPE_MEDIA_TYPE) return await snapshotOrBundledFallbackResult({
error: `signed hosted catalog feed must use ${DSSE_ENVELOPE_MEDIA_TYPE}`,
snapshotStore,
url: url.href,
metadata: base,
expectedSha256,
ifNoneMatch,
catalogConfig: params?.catalogConfig,
requireManifestInstallSourceRef,
expectedFeedId: source.expectedFeedId,
verification: source.verification,
now: currentTime()
});
const body = await readHostedCatalogResponseText({
response,
maxBytes: params?.maxBytes ?? DEFAULT_HOSTED_OFFICIAL_EXTERNAL_PLUGIN_CATALOG_MAX_BYTES,
chunkTimeoutMs: params?.chunkTimeoutMs ?? DEFAULT_HOSTED_OFFICIAL_EXTERNAL_PLUGIN_CATALOG_CHUNK_TIMEOUT_MS
});
const checksum = sha256Hex(body);
const metadata = {
...base,
checksum
};
if (expectedSha256 && expectedSha256 !== checksum) return await snapshotOrBundledFallbackResult({
error: `hosted catalog feed checksum mismatch: expected ${expectedSha256}`,
snapshotStore,
url: url.href,
metadata,
expectedSha256,
ifNoneMatch,
ifModifiedSince,
catalogConfig: params?.catalogConfig,
requireManifestInstallSourceRef,
expectedFeedId: source.expectedFeedId,
verification: source.verification,
now: currentTime()
});
const now = currentTime();
const verifiedAt = now.toISOString();
const parsed = await parseHostedCatalogFeedBody({
body,
expectedFeedId: source.expectedFeedId,
verification: source.verification,
verifiedAt,
now
}).catch(async (err) => {
return await snapshotOrBundledFallbackResult({
error: err,
snapshotStore,
url: url.href,
metadata,
expectedSha256,
ifNoneMatch,
ifModifiedSince,
catalogConfig: params?.catalogConfig,
requireManifestInstallSourceRef,
expectedFeedId: source.expectedFeedId,
verification: source.verification,
now
});
});
if ("source" in parsed) return parsed;
if (snapshotStore && parsed.trust?.mode === "signed") {
const currentSnapshot = await snapshotStore.read(url.href);
if (currentSnapshot?.trust?.mode === "signed") {
const current = currentSnapshot.monotonic?.mode === "signed-feed" ? currentSnapshot.monotonic : (await parseHostedCatalogFeedBody({
body: currentSnapshot.body,
expectedFeedId: source.expectedFeedId,
verification: source.verification,
verifiedAt: currentSnapshot.trust.verifiedAt,
allowLegacyBetaEnvelope: true,
now,
allowExpired: true,
allowMissingExpiry: true
}).catch((err) => {
if (err instanceof HostedCatalogFeedTimestampError) return { feed: { sequence: err.sequence } };
throw err;
})).feed;
if (isHostedCatalogSignedFeedRollback({
candidate: parsed.feed,
current
})) throw new HostedCatalogSignedFeedMonotonicityError("hosted catalog signed feed sequence is older than current snapshot");
}
}
const entries = filterOfficialExternalPluginCatalogEntriesBySourceRefs(parseOfficialExternalPluginCatalogEntries(parsed.feed), {
catalogConfig: params?.catalogConfig,
requireManifestInstallSourceRef
});
await snapshotStore?.write({
body,
metadata,
savedAt: verifiedAt,
...parsed.trust ? { trust: parsed.trust } : {},
...parsed.trust?.mode === "signed" ? { monotonic: {
mode: "signed-feed",
sequence: parsed.feed.sequence,
generatedAt: parsed.feed.generatedAt
} } : {}
}).catch((err) => {
if (err instanceof HostedCatalogSignedFeedMonotonicityError) throw err;
if (params?.requireSnapshotWrite) throw new HostedCatalogSnapshotWriteError(err);
});
return {
source: "hosted",
entries: dedupeOfficialExternalPluginCatalogEntries(entries),
feed: parsed.feed,
metadata,
...parsed.trust ? { trust: parsed.trust } : {}
};
} catch (err) {
if (err instanceof HostedCatalogSnapshotWriteError) throw err.originalError;
return await snapshotOrBundledFallbackResult({
error: err,
snapshotStore,
url: url.href,
expectedSha256,
ifNoneMatch,
ifModifiedSince,
catalogConfig: params?.catalogConfig,
requireManifestInstallSourceRef,
expectedFeedId: source.expectedFeedId,
verification: source.verification,
now: currentTime()
});
} finally {
await cancelUnreadResponseBody(response);
await release?.().catch(() => void 0);
}
}
function formatFeedInstallCandidateSpec(candidate) {
const packageName = normalizeOptionalString(candidate.package);
if (!packageName) return;
const version = normalizeOptionalString(candidate.version);
if (!version || packageName.endsWith(`@${version}`)) return packageName;
return `${packageName}@${version}`;
}
function getFeedEntryCandidateSourceType(candidate, config) {
const sourceRef = normalizeOptionalString(candidate.sourceRef);
if (!sourceRef) return;
return resolveOfficialExternalPluginCatalogProfileConfig(config).sources[sourceRef]?.type;
}
function resolveFeedEntryInstallSources(params) {
const candidates = getFeedEntryInstallCandidates(params.entry);
return ["npm", "clawhub"].flatMap((source) => {
const candidate = candidates.find((entry) => getFeedEntryCandidateSourceType(entry, params.catalogConfig) === source && Boolean(normalizeOptionalString(entry.package)));
const spec = candidate && formatFeedInstallCandidateSpec(candidate);
if (!candidate || !spec) return [];
const expectedIntegrity = source === "npm" ? normalizeNpmExpectedIntegrity(candidate.integrity) : normalizeClawHubSha256ExpectedIntegrity(candidate.integrity);
return [{
source,
spec: source === "clawhub" ? `clawhub:${spec}` : spec,
...expectedIntegrity ? { expectedIntegrity } : {}
}];
});
}
function resolveFeedEntryInstallCandidate(params) {
const source = resolveFeedEntryInstallSources(params)[0];
return source ? {
...source.source === "npm" ? { npmSpec: source.spec } : { clawhubSpec: source.spec },
defaultChoice: source.source,
...source.expectedIntegrity ? { expectedIntegrity: source.expectedIntegrity } : {}
} : null;
}
/** Source-specific catalog pins stay attached to the artifact they authenticate. */
function resolveOfficialExternalPluginInstallSources(entry, params) {
const install = params?.resolvedInstall === void 0 ? resolveOfficialExternalPluginInstall(entry, params) : params.resolvedInstall;
if (!install) return [];
const candidates = resolveFeedEntryInstallSources({
entry,
catalogConfig: params?.catalogConfig
});
return candidates.length > 0 ? candidates : resolvePluginInstallSources(install);
}
function normalizeClawHubSha256ExpectedIntegrity(value) {
const integrity = normalizeOptionalString(value);
return integrity ? normalizeClawHubSha256Integrity(integrity) ?? void 0 : void 0;
}
function normalizeNpmExpectedIntegrity(value) {
const integrity = normalizeOptionalString(value);
if (!integrity || !/^[a-z0-9]+-[A-Za-z0-9+/=]+$/i.test(integrity)) return;
return integrity;
}
/** Returns manifest metadata from an official external catalog entry when present. */
function getOfficialExternalPluginCatalogManifest(entry) {
const manifest = entry[MANIFEST_KEY];
return isRecord(manifest) ? manifest : void 0;
}
function resolveOfficialExternalPluginId(entry) {
const manifest = getOfficialExternalPluginCatalogManifest(entry);
return normalizeOptionalString(manifest?.plugin?.id) ?? normalizeOptionalString(manifest?.channel?.id) ?? normalizeOptionalString(manifest?.providers?.[0]?.id) ?? normalizeOptionalString(entry.id);
}
/** Returns legacy plugin ids used only for trusted update migrations. */
function resolveOfficialExternalPluginLegacyIds(entry) {
return uniqueStrings((getOfficialExternalPluginCatalogManifest(entry)?.legacyPluginIds ?? []).map((pluginId) => normalizeOptionalString(pluginId)).filter((pluginId) => Boolean(pluginId)));
}
/** Returns former npm package names accepted only for trusted update migrations. */
function resolveOfficialExternalPluginLegacyNpmPackageNames(entry) {
return uniqueStrings((getOfficialExternalPluginCatalogManifest(entry)?.legacyNpmPackageNames ?? []).map((packageName) => normalizeOptionalString(packageName)).filter((packageName) => Boolean(packageName)));
}
/** Returns the host-owned setup migration selected for an external channel cutover. */
function resolveOfficialExternalChannelCompatibilityMigration(channelId) {
const entry = getOfficialExternalPluginCatalogEntry(channelId);
return normalizeOptionalString(getOfficialExternalPluginCatalogManifest(entry ?? {})?.channelHostConfig?.compatibilityMigration);
}
function resolveOfficialExternalPluginLookupIds(entry) {
const manifest = getOfficialExternalPluginCatalogManifest(entry);
const lookupIds = [normalizeOptionalString(manifest?.plugin?.id), normalizeOptionalString(manifest?.channel?.id)];
for (const provider of manifest?.providers ?? []) {
lookupIds.push(normalizeOptionalString(provider.id));
for (const alias of provider.aliases ?? []) lookupIds.push(normalizeOptionalString(alias));
}
return uniqueStrings(lookupIds.filter((value) => Boolean(value)));
}
function resolveOfficialExternalPluginLabel(entry) {
const manifest = getOfficialExternalPluginCatalogManifest(entry);
return normalizeOptionalString(manifest?.plugin?.label) ?? normalizeOptionalString(manifest?.channel?.label) ?? normalizeOptionalString(manifest?.providers?.[0]?.name) ?? normalizeOptionalString(entry.title) ?? normalizeOptionalString(entry.name) ?? resolveOfficialExternalPluginId(entry) ?? "plugin";
}
function resolveOfficialExternalPluginInstall(entry, params) {
const state = normalizeOptionalString(entry.state);
const publisherTrust = normalizeOptionalString(entry.publisher?.trust);
if ((state || publisherTrust) && (state !== "available" || publisherTrust !== "official")) return null;
const install = getOfficialExternalPluginCatalogManifest(entry)?.install;
const clawhubSpec = normalizeOptionalString(install?.clawhubSpec);
const manifestNpmSpec = normalizeOptionalString(install?.npmSpec);
const localPath = normalizeOptionalString(install?.localPath);
const candidateInstall = resolveFeedEntryInstallCandidate({
entry,
catalogConfig: params?.catalogConfig
});
if (candidateInstall) return {
...candidateInstall,
...install?.minHostVersion ? { minHostVersion: install.minHostVersion } : {},
...install?.allowInvalidConfigRecovery === true ? { allowInvalidConfigRecovery: true } : {}
};
const hasFeedInstallCandidates = getFeedEntryInstallCandidateRecords(entry).length > 0;
const npmSpec = manifestNpmSpec ?? (hasFeedInstallCandidates || clawhubSpec ? void 0 : normalizeOptionalString(entry.name));
const defaultChoice = normalizePluginInstallDefaultChoice(install?.defaultChoice) ?? (npmSpec ? "npm" : clawhubSpec ? "clawhub" : localPath ? "local" : void 0);
if (!clawhubSpec && !npmSpec && !localPath) return null;
return {
...clawhubSpec ? { clawhubSpec } : {},
...npmSpec ? { npmSpec } : {},
...localPath ? { localPath } : {},
...defaultChoice ? { defaultChoice } : {},
...install?.minHostVersion ? { minHostVersion: install.minHostVersion } : {},
...install?.expectedIntegrity ? { expectedIntegrity: install.expectedIntegrity } : {},
...install?.allowInvalidConfigRecovery === true ? { allowInvalidConfigRecovery: true } : {}
};
}
async function loadConfiguredHostedOfficialExternalPluginCatalogEntries(params) {
return await loadHostedOfficialExternalPluginCatalogEntries(params);
}
function listOfficialExternalPluginCatalogEntries() {
return dedupeOfficialExternalPluginCatalogEntries(bundledOfficialExternalPluginCatalogEntries());
}
/** Returns whether an id is the canonical id of an official external plugin. */
function isOfficialExternalPluginId(pluginId) {
const normalized = normalizeOptionalString(pluginId)?.toLowerCase();
if (!normalized) return false;
return listOfficialExternalPluginCatalogEntries().some((entry) => resolveOfficialExternalPluginId(entry)?.toLowerCase() === normalized);
}
/** Resolves official external plugin owners for configured capability provider ids. */
function resolveOfficialExternalProviderContractPluginIds(params) {
const configuredProviderIds = new Set([...params.providerIds].map((providerId) => normalizeOptionalString(providerId)?.toLowerCase()).filter((providerId) => Boolean(providerId)));
if (configuredProviderIds.size === 0) return [];
const pluginIds = /* @__PURE__ */ new Set();
for (const entry of listOfficialExternalPluginCatalogEntries()) {
const pluginId = resolveOfficialExternalPluginId(entry);
const providerIds = getOfficialExternalPluginCatalogManifest(entry)?.contracts?.[params.contract];
if (pluginId && providerIds?.some((providerId) => {
const normalized = normalizeOptionalString(providerId)?.toLowerCase();
return normalized ? configuredProviderIds.has(normalized) : false;
})) pluginIds.add(pluginId);
}
return [...pluginIds].toSorted((left, right) => left.localeCompare(right));
}
/** Resolves official web provider owners from matching documented environment credentials. */
function resolveOfficialExternalWebProviderContractPluginIdsForEnv(params) {
const pluginIds = /* @__PURE__ */ new Set();
for (const entry of listOfficialExternalPluginCatalogEntries()) {
const pluginId = resolveOfficialExternalPluginId(entry);
const manifest = getOfficialExternalPluginCatalogManifest(entry);
const contractProviderIds = new Set((manifest?.contracts?.[params.contract] ?? []).map((providerId) => normalizeOptionalString(providerId)?.toLowerCase()).filter((providerId) => Boolean(providerId)));
if (pluginId && contractProviderIds.size > 0 && manifest?.webSearchProviders?.some((provider) => {
const providerId = normalizeOptionalString(provider.id)?.toLowerCase();
return providerId !== void 0 && contractProviderIds.has(providerId) && provider.envVars?.some((envVar) => Boolean(params.env[envVar]?.trim()));
})) pluginIds.add(pluginId);
}
return [...pluginIds].toSorted((left, right) => left.localeCompare(right));
}
/** Resolves official external plugin owners for configured model provider ids. */
function resolveOfficialExternalProviderPluginIds(params) {
const configuredProviderIds = new Set([...params.providerIds].map((providerId) => normalizeOptionalString(providerId)?.toLowerCase()).filter((providerId) => Boolean(providerId)));
if (configuredProviderIds.size === 0) return [];
const pluginIds = /* @__PURE__ */ new Set();
for (const entry of listOfficialExternalProviderCatalogEntries()) {
const pluginId = resolveOfficialExternalPluginId(entry);
const providers = getOfficialExternalPluginCatalogManifest(entry)?.providers;
if (pluginId && providers?.some((provider) => [provider.id, ...provider.aliases ?? []].some((providerId) => {
const normalized = normalizeOptionalString(providerId)?.toLowerCase();
return normalized ? configuredProviderIds.has(normalized) : false;
}))) pluginIds.add(pluginId);
}
return [...pluginIds].toSorted((left, right) => left.localeCompare(right));
}
/** Resolves official external provider owners with configured environment credentials. */
function resolveOfficialExternalProviderPluginIdsForEnv(env) {
const pluginIds = /* @__PURE__ */ new Set();
for (const entry of listOfficialExternalProviderCatalogEntries()) {
const pluginId = resolveOfficialExternalPluginId(entry);
const providers = getOfficialExternalPluginCatalogManifest(entry)?.providers;
if (pluginId && providers?.some((provider) => provider.envVars?.some((envVar) => Boolean(env[envVar]?.trim())))) pluginIds.add(pluginId);
}
return [...pluginIds].toSorted((left, right) => left.localeCompare(right));
}
function listOfficialExternalChannelCatalogEntries() {
return listOfficialExternalPluginCatalogEntries().filter((entry) => Boolean(getOfficialExternalPluginCatalogManifest(entry)?.channel));
}
function listOfficialExternalChannelEnvVars() {
return listOfficialExternalChannelCatalogEntries().flatMap((entry) => {
const channel = getOfficialExternalPluginCatalogManifest(entry)?.channel;
const channelId = normalizeOptionalString(channel?.id)?.toLowerCase();
const envVars = uniqueStrings([
...channel?.envVars ?? [],
...channel?.configuredState?.env?.allOf ?? [],
...channel?.configuredState?.env?.anyOf ?? []
].map((envVar) => normalizeOptionalString(envVar)).filter((envVar) => Boolean(envVar)));
return channelId && envVars.length > 0 ? [{
channelId,
envVars
}] : [];
});
}
const CHANNEL_SECRET_FIELD_PATTERN = /^[A-Za-z][A-Za-z0-9]*$/;
const CHANNEL_SECRET_ENV_PATTERN = /^[A-Z][A-Z0-9_]*$/;
/** Returns a validated host fallback secret contract for one external channel. */
function getOfficialExternalChannelSecretContract(channelId) {
const normalizedChannelId = normalizeOptionalString(channelId)?.toLowerCase();
if (!normalizedChannelId) return;
const fields = getOfficialExternalPluginCatalogManifest(listOfficialExternalChannelCatalogEntries().find((candidate) => {
return normalizeOptionalString(getOfficialExternalPluginCatalogManifest(candidate)?.channel?.id)?.toLowerCase() === normalizedChannelId;
}) ?? {})?.channelSecrets?.fields;
if (!fields) return;
const normalizedFields = fields.flatMap((field) => {
const fieldName = normalizeOptionalString(field.field);
const activationField = normalizeOptionalString(field.activationField);
const activationEnv = normalizeOptionalString(field.activationEnv);
if (!fieldName || !CHANNEL_SECRET_FIELD_PATTERN.test(fieldName) || activationField !== void 0 && !CHANNEL_SECRET_FIELD_PATTERN.test(activationField) || activationEnv !== void 0 && !CHANNEL_SECRET_ENV_PATTERN.test(activationEnv)) return [];
return [{
field: fieldName,
...activationField ? { activationField } : {},
...activationEnv ? { activationEnv } : {}
}];
});
return normalizedFields.length > 0 ? {
channelId: normalizedChannelId,
fields: normalizedFields
} : void 0;
}
/** Returns trusted host validation clauses for one official external channel. */
function getOfficialExternalChannelHostSchemaAllOf(channelId) {
const normalizedChannelId = normalizeOptionalString(channelId)?.toLowerCase();
if (!normalizedChannelId) return [];
const clauses = getOfficialExternalPluginCatalogManifest(listOfficialExternalChannelCatalogEntries().find((candidate) => {
return normalizeOptionalString(getOfficialExternalPluginCatalogManifest(candidate)?.channel?.id)?.toLowerCase() === normalizedChannelId;
}) ?? {})?.channelHostConfig?.schemaAllOf;
return Array.isArray(clauses) ? clauses.filter(isRecord) : [];
}
function listOfficialExternalProviderCatalogEntries() {
return listOfficialExternalPluginCatalogEntries().filter((entry) => (getOfficialExternalPluginCatalogManifest(entry)?.providers?.length ?? 0) > 0);
}
function getOfficialExternalPluginCatalogEntry(pluginId) {
const normalized = pluginId.trim();
if (!normalized) return;
return listOfficialExternalPluginCatalogEntries().find((entry) => resolveOfficialExternalPluginLookupIds(entry).includes(normalized));
}
function getOfficialExternalPluginCatalogEntryForPackage(packageName) {
const normalized = packageName?.trim();
if (!normalized) return;
return listOfficialExternalPluginCatalogEntries().find((entry) => normalizeOptionalString(entry.name) === normalized);
}
/** Source discovery alone does not make an external package part of the core distribution. */
function isExternallyDistributedPlugin(plugin) {
const entry = getOfficialExternalPluginCatalogEntryForPackage(plugin.packageName);
return plugin.packageBuild?.bundledDist === false || entry !== void 0 && resolveOfficialExternalPluginId(entry) === plugin.pluginId;
}
//#endregion
export { installWithSourceFallback as A, downloadClawHubSkillArchive as B, resolveOfficialExternalPluginLegacyNpmPackageNames as C, resolveOfficialExternalProviderPluginIdsForEnv as D, resolveOfficialExternalProviderPluginIds as E, resolvePluginInstallSources as F, normalizeClawHubSha256Hex as H, CLAWHUB_INSTALL_ERROR_CODE as I, isUnavailableClawHubTarget as L, resolveClawHubInstallSpecsForUpdateChannel as M, resolveDefaultNpmSpec as N, resolveOfficialExternalWebProviderContractPluginIdsForEnv as O, resolveNpmInstallSpecsForUpdateChannel as P, downloadClawHubGitHubSkillArchive as R, resolveOfficialExternalPluginLegacyIds as S, resolveOfficialExternalProviderContractPluginIds as T, normalizeClawHubSha256Integrity as U, downloadClawHubSkillArchiveUrl as V, normalizePluginInstallDefaultChoice as W, resolveOfficialExternalChannelCompatibilityMigration as _, getOfficialExternalPluginCatalogEntryForPackage as a, resolveOfficialExternalPluginInstallSources as b, isOfficialExternalPluginCatalogFeed as c, listOfficialExternalChannelCatalogEntries as d, listOfficialExternalChannelEnvVars as f, parseOfficialExternalPluginCatalogTimestamp as g, loadConfiguredHostedOfficialExternalPluginCatalogEntries as h, getOfficialExternalPluginCatalogEntry as i, isUnavailablePluginSource as j, installWithChannelFallback as k, isOfficialExternalPluginCatalogSequence as l, listOfficialExternalProviderCatalogEntries as m, getOfficialExternalChannelHostSchemaAllOf as n, getOfficialExternalPluginCatalogManifest as o, listOfficialExternalPluginCatalogEntries as p, getOfficialExternalChannelSecretContract as r, isExternallyDistributedPlugin as s, HostedCatalogSignedFeedMonotonicityError as t, isOfficialExternalPluginId as u, resolveOfficialExternalPluginId as v, resolveOfficialExternalPluginLookupIds as w, resolveOfficialExternalPluginLabel as x, resolveOfficialExternalPluginInstall as y, downloadClawHubPackageArchive as z };