



















@@ -89,6 +89,7 @@ type ClawHubPublishablePluginPackageFilters = {
8989const CLAWHUB_DEFAULT_REGISTRY = "https://clawhub.ai";
9090const CLAWHUB_REQUEST_TIMEOUT_MS = 30_000;
9191const CLAWHUB_RESPONSE_BODY_MAX_BYTES = 64 * 1024;
92+const CLAWHUB_RATE_LIMIT_RETRY_DELAYS_MS = [1_000, 3_000, 10_000] as const;
9293const OPENCLAW_PLUGIN_CLAWHUB_REPOSITORY = "openclaw/openclaw";
9394const OPENCLAW_PLUGIN_CLAWHUB_WORKFLOW_FILENAME = "plugin-clawhub-release.yml";
9495const SAFE_EXTENSION_ID_RE = /^[a-z0-9][a-z0-9._-]*$/;
@@ -482,43 +483,70 @@ async function hasClawHubTrustedPublisher(
482483`/api/v1/packages/${encodeURIComponent(packageName)}/trusted-publisher`,
483484getRegistryBaseUrl(options.registryBaseUrl),
484485);
485-const request = await fetchClawHubRequest(url, {
486-fetchImpl: options.fetchImpl,
487-requestTimeoutMs: options.requestTimeoutMs,
488-});
489-const { response } = request;
490-491-try {
492-if (!response.ok) {
493-throw new Error(
494-`Failed to query ClawHub trusted publisher for ${packageName}: ${response.status} ${response.statusText}`,
495-);
496-}
486+for (let attempt = 0; ; attempt += 1) {
487+const request = await fetchClawHubRequest(url, {
488+fetchImpl: options.fetchImpl,
489+requestTimeoutMs: options.requestTimeoutMs,
490+});
491+const { response } = request;
497492498-let trustedPublisherDetail: ClawHubTrustedPublisherDetail;
499-const text = await readBoundedResponseText(
500-response,
501-`ClawHub trusted publisher ${packageName}`,
502-CLAWHUB_RESPONSE_BODY_MAX_BYTES,
503-{
504-signal: request.signal,
505-timeoutPromise: request.timeoutPromise,
506-},
507-);
508493try {
509-trustedPublisherDetail = JSON.parse(text) as ClawHubTrustedPublisherDetail;
510-} catch (error) {
511-throw new Error(`Failed to parse ClawHub trusted publisher ${packageName} response.`, {
512-cause: error,
513-});
494+if (response.status !== 429 || attempt >= CLAWHUB_RATE_LIMIT_RETRY_DELAYS_MS.length) {
495+if (!response.ok) {
496+throw new Error(
497+`Failed to query ClawHub trusted publisher for ${packageName}: ${response.status} ${response.statusText}`,
498+);
499+}
500+501+let trustedPublisherDetail: ClawHubTrustedPublisherDetail;
502+const text = await readBoundedResponseText(
503+response,
504+`ClawHub trusted publisher ${packageName}`,
505+CLAWHUB_RESPONSE_BODY_MAX_BYTES,
506+{
507+signal: request.signal,
508+timeoutPromise: request.timeoutPromise,
509+},
510+);
511+try {
512+trustedPublisherDetail = JSON.parse(text) as ClawHubTrustedPublisherDetail;
513+} catch (error) {
514+throw new Error(`Failed to parse ClawHub trusted publisher ${packageName} response.`, {
515+cause: error,
516+});
517+}
518+519+return isOpenClawPluginTrustedPublisher(trustedPublisherDetail.trustedPublisher);
520+}
521+} finally {
522+request.clearTimeout();
514523}
515524516-return isOpenClawPluginTrustedPublisher(trustedPublisherDetail.trustedPublisher);
517-} finally {
518-request.clearTimeout();
525+await delay(clawHubRetryDelayMs(response, attempt));
519526}
520527}
521528529+function clawHubRetryDelayMs(response: Response, attempt: number): number {
530+const retryAfter = response.headers.get("retry-after");
531+if (retryAfter !== null) {
532+const retryAfterSeconds = Number(retryAfter);
533+if (Number.isFinite(retryAfterSeconds) && retryAfterSeconds >= 0) {
534+return Math.round(retryAfterSeconds * 1_000);
535+}
536+const retryAfterAt = Date.parse(retryAfter);
537+if (Number.isFinite(retryAfterAt)) {
538+return Math.max(0, retryAfterAt - Date.now());
539+}
540+}
541+return CLAWHUB_RATE_LIMIT_RETRY_DELAYS_MS[attempt] ?? 0;
542+}
543+544+async function delay(ms: number): Promise<void> {
545+await new Promise<void>((resolve) => {
546+setTimeout(resolve, ms);
547+});
548+}
549+522550function isOpenClawPluginTrustedPublisher(value: unknown): boolean {
523551if (!value || typeof value !== "object" || Array.isArray(value)) {
524552return false;
@@ -581,42 +609,41 @@ export async function collectPluginClawHubReleasePlan(params?: {
581609assertPluginReleaseVersionFloors(selectedPublishable, "Plugin ClawHub release plan");
582610}
583611584-const planned = await Promise.all(
585-selectedPublishable.map(async (plugin): Promise<PluginReleasePlanItemWithPackageState> => {
586-const packageExists = await doesClawHubPackageExist(plugin.packageName, {
587-registryBaseUrl: params?.registryBaseUrl,
588-fetchImpl: params?.fetchImpl,
589-requestTimeoutMs: params?.requestTimeoutMs,
590-});
591-const hasTrustedPublisher = packageExists
592- ? await hasClawHubTrustedPublisher(plugin.packageName, {
593-registryBaseUrl: params?.registryBaseUrl,
594-fetchImpl: params?.fetchImpl,
595-requestTimeoutMs: params?.requestTimeoutMs,
596-})
597- : false;
598-const alreadyPublished = packageExists
599- ? await isPluginVersionPublishedOnClawHub(plugin.packageName, plugin.version, {
600-registryBaseUrl: params?.registryBaseUrl,
601-fetchImpl: params?.fetchImpl,
602-requestTimeoutMs: params?.requestTimeoutMs,
603-})
604- : false;
605-606-return {
607-extensionId: plugin.extensionId,
608-packageDir: plugin.packageDir,
609-packageName: plugin.packageName,
610-version: plugin.version,
611-channel: plugin.channel,
612-publishTag: plugin.publishTag,
613- packageExists,
614- hasTrustedPublisher,
615- alreadyPublished,
616-artifactName: formatClawHubPackageArtifactName(plugin),
617-};
618-}),
619-);
612+const planned: PluginReleasePlanItemWithPackageState[] = [];
613+for (const plugin of selectedPublishable) {
614+const packageExists = await doesClawHubPackageExist(plugin.packageName, {
615+registryBaseUrl: params?.registryBaseUrl,
616+fetchImpl: params?.fetchImpl,
617+requestTimeoutMs: params?.requestTimeoutMs,
618+});
619+const hasTrustedPublisher = packageExists
620+ ? await hasClawHubTrustedPublisher(plugin.packageName, {
621+registryBaseUrl: params?.registryBaseUrl,
622+fetchImpl: params?.fetchImpl,
623+requestTimeoutMs: params?.requestTimeoutMs,
624+})
625+ : false;
626+const alreadyPublished = packageExists
627+ ? await isPluginVersionPublishedOnClawHub(plugin.packageName, plugin.version, {
628+registryBaseUrl: params?.registryBaseUrl,
629+fetchImpl: params?.fetchImpl,
630+requestTimeoutMs: params?.requestTimeoutMs,
631+})
632+ : false;
633+634+planned.push({
635+extensionId: plugin.extensionId,
636+packageDir: plugin.packageDir,
637+packageName: plugin.packageName,
638+version: plugin.version,
639+channel: plugin.channel,
640+publishTag: plugin.publishTag,
641+ packageExists,
642+ hasTrustedPublisher,
643+ alreadyPublished,
644+artifactName: formatClawHubPackageArtifactName(plugin),
645+});
646+}
620647const all = planned.map(stripPackageReleaseState);
621648622649return {
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。