diff --git a/convex/lib/publicRouteReservations.test.ts b/convex/lib/publicRouteReservations.test.ts index 9e1be473..b34007d5 100644 --- a/convex/lib/publicRouteReservations.test.ts +++ b/convex/lib/publicRouteReservations.test.ts @@ -1,5 +1,8 @@ import { describe, expect, it } from "vitest"; -import { isReservedPublicOwnerHandle } from "./publicRouteReservations"; +import { + isReservedOpenClawExtensionHandle, + isReservedPublicOwnerHandle, +} from "./publicRouteReservations"; describe("public route reservations", () => { it.each(["admin", "clawhub", "docs", "plugins", "skills"])( @@ -9,6 +12,11 @@ describe("public route reservations", () => { }, ); + it.each(["codex", "tencent"])("reserves @%s as an OpenClaw alias", (handle) => { + expect(isReservedOpenClawExtensionHandle(handle)).toBe(true); + expect(isReservedPublicOwnerHandle(handle)).toBe(false); + }); + it("does not normalize at-sign prefixes", () => { expect(isReservedPublicOwnerHandle("@clawhub")).toBe(false); }); diff --git a/convex/lib/publicRouteReservations.ts b/convex/lib/publicRouteReservations.ts index a330041c..a0e926b7 100644 --- a/convex/lib/publicRouteReservations.ts +++ b/convex/lib/publicRouteReservations.ts @@ -1,10 +1,19 @@ +import { OPENCLAW_EXTENSION_SLUG_TO_PACKAGE } from "clawhub-schema"; + const RESERVED_PUBLIC_OWNER_HANDLES = new Set(["admin", "clawhub", "docs", "plugins", "skills"]); +const RESERVED_OPENCLAW_EXTENSION_HANDLES = new Set( + Object.keys(OPENCLAW_EXTENSION_SLUG_TO_PACKAGE), +); const RESERVED_UNSCOPED_PACKAGE_NAMES = new Set(["publish"]); export function isReservedPublicOwnerHandle(handle: string | undefined | null) { return Boolean(handle && RESERVED_PUBLIC_OWNER_HANDLES.has(handle.trim().toLowerCase())); } +export function isReservedOpenClawExtensionHandle(handle: string | undefined | null) { + return Boolean(handle && RESERVED_OPENCLAW_EXTENSION_HANDLES.has(handle.trim().toLowerCase())); +} + export function isReservedUnscopedPackageName(name: string | undefined | null) { return Boolean(name && RESERVED_UNSCOPED_PACKAGE_NAMES.has(name.trim().toLowerCase())); } diff --git a/convex/publishers.ts b/convex/publishers.ts index b6711ea4..00b8f4f0 100644 --- a/convex/publishers.ts +++ b/convex/publishers.ts @@ -12,6 +12,7 @@ import { isPackageBlockedFromPublic } from "./lib/packageSecurity"; import { toPublicPublisher } from "./lib/public"; import { formatReservedPublicOwnerHandleMessage, + isReservedOpenClawExtensionHandle, isReservedPublicOwnerHandle, } from "./lib/publicRouteReservations"; import { @@ -1078,6 +1079,9 @@ async function ensureOrgPublisherHandleWithActor( if (!normalizePublisherHandle(args.memberHandle)) { throw new ConvexError("memberHandle required when creating org publisher"); } + if (!existingPublisher && !existingUser && isReservedOpenClawExtensionHandle(handle)) { + throw new ConvexError(formatReservedPublicOwnerHandleMessage(handle)); + } const publisherId = await ctx.db.insert("publishers", { kind: "org", @@ -1686,6 +1690,9 @@ async function createOrgPublisherForUser( if (await isHandleReservedForAnotherUser(ctx, handle, args.actorUserId)) { throw new ConvexError(`Handle "@${handle}" is reserved for another user`); } + if (isReservedOpenClawExtensionHandle(handle)) { + throw new ConvexError(formatReservedPublicOwnerHandleMessage(handle)); + } const now = Date.now(); const publisherId = await ctx.db.insert("publishers", { diff --git a/convex/users.ts b/convex/users.ts index 603dd671..48e85344 100644 --- a/convex/users.ts +++ b/convex/users.ts @@ -14,6 +14,7 @@ import { syncGitHubProfile } from "./lib/githubAccount"; import { toPublicUser } from "./lib/public"; import { formatReservedPublicOwnerHandleMessage, + isReservedOpenClawExtensionHandle, isReservedPublicOwnerHandle, } from "./lib/publicRouteReservations"; import { @@ -760,6 +761,10 @@ async function canUserClaimHandle( if (await isHandleReservedForAnotherUser(ctx, normalizedHandle, userId)) return false; const publisher = await getPublisherByHandle(ctx, normalizedHandle); + if (isReservedOpenClawExtensionHandle(normalizedHandle)) { + const existingUser = await getUserByHandleOrPersonalPublisher(ctx, normalizedHandle); + return existingUser?._id === userId; + } if (!publisher || publisher.deletedAt || publisher.deactivatedAt) return true; return publisher.kind === "user" && publisher.linkedUserId === userId; } @@ -2056,6 +2061,9 @@ async function ensurePublisherHandleWithActor( if (existing?.deletedAt || existing?.deactivatedAt) { throw new Error("Handle belongs to a deleted or deactivated user"); } + if (!existing && isReservedOpenClawExtensionHandle(normalizedHandle)) { + throw new ConvexError(formatReservedPublicOwnerHandleMessage(normalizedHandle)); + } const now = Date.now(); const displayName = args.displayName?.trim() || normalizedHandle; diff --git a/packages/schema/dist/index.d.ts b/packages/schema/dist/index.d.ts index 85f6fa8f..af5ec8e8 100644 --- a/packages/schema/dist/index.d.ts +++ b/packages/schema/dist/index.d.ts @@ -5,6 +5,7 @@ export * from "./catalogMetadata.js"; export * from "./docsLinks.js"; export * from "./license.js"; export * from "./openclawContract.js"; +export * from "./openClawExtensionSlugs.js"; export * from "./packages.js"; export * from "./pluginCategories.js"; export { ApiRoutes, LegacyApiRoutes } from "./routes.js"; diff --git a/packages/schema/dist/index.js b/packages/schema/dist/index.js index 73240071..a4e03f00 100644 --- a/packages/schema/dist/index.js +++ b/packages/schema/dist/index.js @@ -4,6 +4,7 @@ export * from "./catalogMetadata.js"; export * from "./docsLinks.js"; export * from "./license.js"; export * from "./openclawContract.js"; +export * from "./openClawExtensionSlugs.js"; export * from "./packages.js"; export * from "./pluginCategories.js"; export { ApiRoutes, LegacyApiRoutes } from "./routes.js"; diff --git a/packages/schema/dist/index.js.map b/packages/schema/dist/index.js.map index 61ba0998..a2709000 100644 --- a/packages/schema/dist/index.js.map +++ b/packages/schema/dist/index.js.map @@ -1 +1 @@ -{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AACrD,cAAc,kBAAkB,CAAC;AACjC,cAAc,sBAAsB,CAAC;AACrC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,cAAc,CAAC;AAC7B,cAAc,uBAAuB,CAAC;AACtC,cAAc,eAAe,CAAC;AAC9B,cAAc,uBAAuB,CAAC;AACtC,OAAO,EAAE,SAAS,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AACzD,cAAc,cAAc,CAAC;AAC7B,cAAc,gBAAgB,CAAC"} \ No newline at end of file +{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AACrD,cAAc,kBAAkB,CAAC;AACjC,cAAc,sBAAsB,CAAC;AACrC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,cAAc,CAAC;AAC7B,cAAc,uBAAuB,CAAC;AACtC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,eAAe,CAAC;AAC9B,cAAc,uBAAuB,CAAC;AACtC,OAAO,EAAE,SAAS,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AACzD,cAAc,cAAc,CAAC;AAC7B,cAAc,gBAAgB,CAAC"} \ No newline at end of file diff --git a/packages/schema/dist/openClawExtensionSlugs.d.ts b/packages/schema/dist/openClawExtensionSlugs.d.ts new file mode 100644 index 00000000..f83ef11f --- /dev/null +++ b/packages/schema/dist/openClawExtensionSlugs.d.ts @@ -0,0 +1,192 @@ +export declare const OPENCLAW_EXTENSION_SLUG_TO_PACKAGE: { + readonly acpx: "@openclaw/acpx"; + readonly "active-memory": "@openclaw/active-memory"; + readonly alibaba: "@openclaw/alibaba-provider"; + readonly "alibaba-provider": "@openclaw/alibaba-provider"; + readonly "amazon-bedrock": "@openclaw/amazon-bedrock-provider"; + readonly "amazon-bedrock-mantle": "@openclaw/amazon-bedrock-mantle-provider"; + readonly "amazon-bedrock-mantle-provider": "@openclaw/amazon-bedrock-mantle-provider"; + readonly "amazon-bedrock-provider": "@openclaw/amazon-bedrock-provider"; + readonly anthropic: "@openclaw/anthropic-provider"; + readonly "anthropic-provider": "@openclaw/anthropic-provider"; + readonly "anthropic-vertex": "@openclaw/anthropic-vertex-provider"; + readonly "anthropic-vertex-provider": "@openclaw/anthropic-vertex-provider"; + readonly arcee: "@openclaw/arcee-provider"; + readonly "arcee-provider": "@openclaw/arcee-provider"; + readonly "azure-speech": "@openclaw/azure-speech"; + readonly bluebubbles: "@openclaw/bluebubbles"; + readonly bonjour: "@openclaw/bonjour"; + readonly brave: "@openclaw/brave-plugin"; + readonly "brave-plugin": "@openclaw/brave-plugin"; + readonly browser: "@openclaw/browser-plugin"; + readonly "browser-plugin": "@openclaw/browser-plugin"; + readonly byteplus: "@openclaw/byteplus-provider"; + readonly "byteplus-provider": "@openclaw/byteplus-provider"; + readonly cerebras: "@openclaw/cerebras-provider"; + readonly "cerebras-provider": "@openclaw/cerebras-provider"; + readonly chutes: "@openclaw/chutes-provider"; + readonly "chutes-provider": "@openclaw/chutes-provider"; + readonly "cloudflare-ai-gateway": "@openclaw/cloudflare-ai-gateway-provider"; + readonly "cloudflare-ai-gateway-provider": "@openclaw/cloudflare-ai-gateway-provider"; + readonly codex: "@openclaw/codex"; + readonly comfy: "@openclaw/comfy-provider"; + readonly "comfy-provider": "@openclaw/comfy-provider"; + readonly "copilot-proxy": "@openclaw/copilot-proxy"; + readonly deepgram: "@openclaw/deepgram-provider"; + readonly "deepgram-provider": "@openclaw/deepgram-provider"; + readonly deepinfra: "@openclaw/deepinfra-provider"; + readonly "deepinfra-provider": "@openclaw/deepinfra-provider"; + readonly deepseek: "@openclaw/deepseek-provider"; + readonly "deepseek-provider": "@openclaw/deepseek-provider"; + readonly "device-pair": "@openclaw/device-pair"; + readonly "diagnostics-otel": "@openclaw/diagnostics-otel"; + readonly "diagnostics-prometheus": "@openclaw/diagnostics-prometheus"; + readonly diffs: "@openclaw/diffs"; + readonly "diffs-language-pack": "@openclaw/diffs-language-pack"; + readonly discord: "@openclaw/discord"; + readonly "document-extract": "@openclaw/document-extract-plugin"; + readonly "document-extract-plugin": "@openclaw/document-extract-plugin"; + readonly duckduckgo: "@openclaw/duckduckgo-plugin"; + readonly "duckduckgo-plugin": "@openclaw/duckduckgo-plugin"; + readonly elevenlabs: "@openclaw/elevenlabs-speech"; + readonly "elevenlabs-speech": "@openclaw/elevenlabs-speech"; + readonly exa: "@openclaw/exa-plugin"; + readonly "exa-plugin": "@openclaw/exa-plugin"; + readonly fal: "@openclaw/fal-provider"; + readonly "fal-provider": "@openclaw/fal-provider"; + readonly feishu: "@openclaw/feishu"; + readonly "file-transfer": "@openclaw/file-transfer"; + readonly firecrawl: "@openclaw/firecrawl-plugin"; + readonly "firecrawl-plugin": "@openclaw/firecrawl-plugin"; + readonly fireworks: "@openclaw/fireworks-provider"; + readonly "fireworks-provider": "@openclaw/fireworks-provider"; + readonly "github-copilot": "@openclaw/github-copilot-provider"; + readonly "github-copilot-provider": "@openclaw/github-copilot-provider"; + readonly google: "@openclaw/google-plugin"; + readonly "google-meet": "@openclaw/google-meet"; + readonly "google-plugin": "@openclaw/google-plugin"; + readonly googlechat: "@openclaw/googlechat"; + readonly gradium: "@openclaw/gradium-speech"; + readonly "gradium-speech": "@openclaw/gradium-speech"; + readonly groq: "@openclaw/groq-provider"; + readonly "groq-provider": "@openclaw/groq-provider"; + readonly huggingface: "@openclaw/huggingface-provider"; + readonly "huggingface-provider": "@openclaw/huggingface-provider"; + readonly imessage: "@openclaw/imessage"; + readonly inworld: "@openclaw/inworld-speech"; + readonly "inworld-speech": "@openclaw/inworld-speech"; + readonly irc: "@openclaw/irc"; + readonly kilocode: "@openclaw/kilocode-provider"; + readonly "kilocode-provider": "@openclaw/kilocode-provider"; + readonly kimi: "@openclaw/kimi-provider"; + readonly "kimi-coding": "@openclaw/kimi-provider"; + readonly "kimi-provider": "@openclaw/kimi-provider"; + readonly line: "@openclaw/line"; + readonly litellm: "@openclaw/litellm-provider"; + readonly "litellm-provider": "@openclaw/litellm-provider"; + readonly "llm-task": "@openclaw/llm-task"; + readonly lmstudio: "@openclaw/lmstudio-provider"; + readonly "lmstudio-provider": "@openclaw/lmstudio-provider"; + readonly lobster: "@openclaw/lobster"; + readonly matrix: "@openclaw/matrix"; + readonly mattermost: "@openclaw/mattermost"; + readonly "memory-core": "@openclaw/memory-core"; + readonly "memory-lancedb": "@openclaw/memory-lancedb"; + readonly "memory-wiki": "@openclaw/memory-wiki"; + readonly microsoft: "@openclaw/microsoft-speech"; + readonly "microsoft-foundry": "@openclaw/microsoft-foundry"; + readonly "microsoft-speech": "@openclaw/microsoft-speech"; + readonly "migrate-claude": "@openclaw/migrate-claude"; + readonly "migrate-hermes": "@openclaw/migrate-hermes"; + readonly minimax: "@openclaw/minimax-provider"; + readonly "minimax-provider": "@openclaw/minimax-provider"; + readonly mistral: "@openclaw/mistral-provider"; + readonly "mistral-provider": "@openclaw/mistral-provider"; + readonly moonshot: "@openclaw/moonshot-provider"; + readonly "moonshot-provider": "@openclaw/moonshot-provider"; + readonly msteams: "@openclaw/msteams"; + readonly "nextcloud-talk": "@openclaw/nextcloud-talk"; + readonly nostr: "@openclaw/nostr"; + readonly nvidia: "@openclaw/nvidia-provider"; + readonly "nvidia-provider": "@openclaw/nvidia-provider"; + readonly ollama: "@openclaw/ollama-provider"; + readonly "ollama-provider": "@openclaw/ollama-provider"; + readonly "open-prose": "@openclaw/open-prose"; + readonly openai: "@openclaw/openai-provider"; + readonly "openai-provider": "@openclaw/openai-provider"; + readonly opencode: "@openclaw/opencode-provider"; + readonly "opencode-go": "@openclaw/opencode-go-provider"; + readonly "opencode-go-provider": "@openclaw/opencode-go-provider"; + readonly "opencode-provider": "@openclaw/opencode-provider"; + readonly openrouter: "@openclaw/openrouter-provider"; + readonly "openrouter-provider": "@openclaw/openrouter-provider"; + readonly openshell: "@openclaw/openshell-sandbox"; + readonly "openshell-sandbox": "@openclaw/openshell-sandbox"; + readonly perplexity: "@openclaw/perplexity-plugin"; + readonly "perplexity-plugin": "@openclaw/perplexity-plugin"; + readonly "phone-control": "@openclaw/phone-control"; + readonly "qa-channel": "@openclaw/qa-channel"; + readonly "qa-lab": "@openclaw/qa-lab"; + readonly "qa-matrix": "@openclaw/qa-matrix"; + readonly qianfan: "@openclaw/qianfan-provider"; + readonly "qianfan-provider": "@openclaw/qianfan-provider"; + readonly qqbot: "@openclaw/qqbot"; + readonly qwen: "@openclaw/qwen-provider"; + readonly "qwen-provider": "@openclaw/qwen-provider"; + readonly runway: "@openclaw/runway-provider"; + readonly "runway-provider": "@openclaw/runway-provider"; + readonly searxng: "@openclaw/searxng-plugin"; + readonly "searxng-plugin": "@openclaw/searxng-plugin"; + readonly senseaudio: "@openclaw/senseaudio-provider"; + readonly "senseaudio-provider": "@openclaw/senseaudio-provider"; + readonly sglang: "@openclaw/sglang-provider"; + readonly "sglang-provider": "@openclaw/sglang-provider"; + readonly signal: "@openclaw/signal"; + readonly "skill-workshop": "@openclaw/skill-workshop"; + readonly slack: "@openclaw/slack"; + readonly stepfun: "@openclaw/stepfun-provider"; + readonly "stepfun-provider": "@openclaw/stepfun-provider"; + readonly "synology-chat": "@openclaw/synology-chat"; + readonly synthetic: "@openclaw/synthetic-provider"; + readonly "synthetic-provider": "@openclaw/synthetic-provider"; + readonly "talk-voice": "@openclaw/talk-voice"; + readonly tavily: "@openclaw/tavily-plugin"; + readonly "tavily-plugin": "@openclaw/tavily-plugin"; + readonly telegram: "@openclaw/telegram"; + readonly tencent: "@openclaw/tencent-provider"; + readonly "tencent-provider": "@openclaw/tencent-provider"; + readonly "thread-ownership": "@openclaw/thread-ownership"; + readonly tlon: "@openclaw/tlon"; + readonly together: "@openclaw/together-provider"; + readonly "together-provider": "@openclaw/together-provider"; + readonly tokenjuice: "@openclaw/tokenjuice"; + readonly "tts-local-cli": "@openclaw/tts-local-cli"; + readonly twitch: "@openclaw/twitch"; + readonly venice: "@openclaw/venice-provider"; + readonly "venice-provider": "@openclaw/venice-provider"; + readonly "vercel-ai-gateway": "@openclaw/vercel-ai-gateway-provider"; + readonly "vercel-ai-gateway-provider": "@openclaw/vercel-ai-gateway-provider"; + readonly vllm: "@openclaw/vllm-provider"; + readonly "vllm-provider": "@openclaw/vllm-provider"; + readonly "voice-call": "@openclaw/voice-call"; + readonly volcengine: "@openclaw/volcengine-provider"; + readonly "volcengine-provider": "@openclaw/volcengine-provider"; + readonly voyage: "@openclaw/voyage-provider"; + readonly "voyage-provider": "@openclaw/voyage-provider"; + readonly vydra: "@openclaw/vydra-provider"; + readonly "vydra-provider": "@openclaw/vydra-provider"; + readonly "web-readability": "@openclaw/web-readability-plugin"; + readonly "web-readability-plugin": "@openclaw/web-readability-plugin"; + readonly webhooks: "@openclaw/webhooks"; + readonly whatsapp: "@openclaw/whatsapp"; + readonly xai: "@openclaw/xai-plugin"; + readonly "xai-plugin": "@openclaw/xai-plugin"; + readonly xiaomi: "@openclaw/xiaomi-provider"; + readonly "xiaomi-provider": "@openclaw/xiaomi-provider"; + readonly zai: "@openclaw/zai-provider"; + readonly "zai-provider": "@openclaw/zai-provider"; + readonly zalo: "@openclaw/zalo"; + readonly zalouser: "@openclaw/zalouser"; +}; +export declare function getOpenClawExtensionPackageName(slug: string): string | null; +export declare function getOpenClawPackageCandidateNames(requestedName: string): string[]; diff --git a/packages/schema/dist/openClawExtensionSlugs.js b/packages/schema/dist/openClawExtensionSlugs.js new file mode 100644 index 00000000..0d84f1a9 --- /dev/null +++ b/packages/schema/dist/openClawExtensionSlugs.js @@ -0,0 +1,203 @@ +export const OPENCLAW_EXTENSION_SLUG_TO_PACKAGE = { + acpx: "@openclaw/acpx", + "active-memory": "@openclaw/active-memory", + alibaba: "@openclaw/alibaba-provider", + "alibaba-provider": "@openclaw/alibaba-provider", + "amazon-bedrock": "@openclaw/amazon-bedrock-provider", + "amazon-bedrock-mantle": "@openclaw/amazon-bedrock-mantle-provider", + "amazon-bedrock-mantle-provider": "@openclaw/amazon-bedrock-mantle-provider", + "amazon-bedrock-provider": "@openclaw/amazon-bedrock-provider", + anthropic: "@openclaw/anthropic-provider", + "anthropic-provider": "@openclaw/anthropic-provider", + "anthropic-vertex": "@openclaw/anthropic-vertex-provider", + "anthropic-vertex-provider": "@openclaw/anthropic-vertex-provider", + arcee: "@openclaw/arcee-provider", + "arcee-provider": "@openclaw/arcee-provider", + "azure-speech": "@openclaw/azure-speech", + bluebubbles: "@openclaw/bluebubbles", + bonjour: "@openclaw/bonjour", + brave: "@openclaw/brave-plugin", + "brave-plugin": "@openclaw/brave-plugin", + browser: "@openclaw/browser-plugin", + "browser-plugin": "@openclaw/browser-plugin", + byteplus: "@openclaw/byteplus-provider", + "byteplus-provider": "@openclaw/byteplus-provider", + cerebras: "@openclaw/cerebras-provider", + "cerebras-provider": "@openclaw/cerebras-provider", + chutes: "@openclaw/chutes-provider", + "chutes-provider": "@openclaw/chutes-provider", + "cloudflare-ai-gateway": "@openclaw/cloudflare-ai-gateway-provider", + "cloudflare-ai-gateway-provider": "@openclaw/cloudflare-ai-gateway-provider", + codex: "@openclaw/codex", + comfy: "@openclaw/comfy-provider", + "comfy-provider": "@openclaw/comfy-provider", + "copilot-proxy": "@openclaw/copilot-proxy", + deepgram: "@openclaw/deepgram-provider", + "deepgram-provider": "@openclaw/deepgram-provider", + deepinfra: "@openclaw/deepinfra-provider", + "deepinfra-provider": "@openclaw/deepinfra-provider", + deepseek: "@openclaw/deepseek-provider", + "deepseek-provider": "@openclaw/deepseek-provider", + "device-pair": "@openclaw/device-pair", + "diagnostics-otel": "@openclaw/diagnostics-otel", + "diagnostics-prometheus": "@openclaw/diagnostics-prometheus", + diffs: "@openclaw/diffs", + "diffs-language-pack": "@openclaw/diffs-language-pack", + discord: "@openclaw/discord", + "document-extract": "@openclaw/document-extract-plugin", + "document-extract-plugin": "@openclaw/document-extract-plugin", + duckduckgo: "@openclaw/duckduckgo-plugin", + "duckduckgo-plugin": "@openclaw/duckduckgo-plugin", + elevenlabs: "@openclaw/elevenlabs-speech", + "elevenlabs-speech": "@openclaw/elevenlabs-speech", + exa: "@openclaw/exa-plugin", + "exa-plugin": "@openclaw/exa-plugin", + fal: "@openclaw/fal-provider", + "fal-provider": "@openclaw/fal-provider", + feishu: "@openclaw/feishu", + "file-transfer": "@openclaw/file-transfer", + firecrawl: "@openclaw/firecrawl-plugin", + "firecrawl-plugin": "@openclaw/firecrawl-plugin", + fireworks: "@openclaw/fireworks-provider", + "fireworks-provider": "@openclaw/fireworks-provider", + "github-copilot": "@openclaw/github-copilot-provider", + "github-copilot-provider": "@openclaw/github-copilot-provider", + google: "@openclaw/google-plugin", + "google-meet": "@openclaw/google-meet", + "google-plugin": "@openclaw/google-plugin", + googlechat: "@openclaw/googlechat", + gradium: "@openclaw/gradium-speech", + "gradium-speech": "@openclaw/gradium-speech", + groq: "@openclaw/groq-provider", + "groq-provider": "@openclaw/groq-provider", + huggingface: "@openclaw/huggingface-provider", + "huggingface-provider": "@openclaw/huggingface-provider", + imessage: "@openclaw/imessage", + inworld: "@openclaw/inworld-speech", + "inworld-speech": "@openclaw/inworld-speech", + irc: "@openclaw/irc", + kilocode: "@openclaw/kilocode-provider", + "kilocode-provider": "@openclaw/kilocode-provider", + kimi: "@openclaw/kimi-provider", + "kimi-coding": "@openclaw/kimi-provider", + "kimi-provider": "@openclaw/kimi-provider", + line: "@openclaw/line", + litellm: "@openclaw/litellm-provider", + "litellm-provider": "@openclaw/litellm-provider", + "llm-task": "@openclaw/llm-task", + lmstudio: "@openclaw/lmstudio-provider", + "lmstudio-provider": "@openclaw/lmstudio-provider", + lobster: "@openclaw/lobster", + matrix: "@openclaw/matrix", + mattermost: "@openclaw/mattermost", + "memory-core": "@openclaw/memory-core", + "memory-lancedb": "@openclaw/memory-lancedb", + "memory-wiki": "@openclaw/memory-wiki", + microsoft: "@openclaw/microsoft-speech", + "microsoft-foundry": "@openclaw/microsoft-foundry", + "microsoft-speech": "@openclaw/microsoft-speech", + "migrate-claude": "@openclaw/migrate-claude", + "migrate-hermes": "@openclaw/migrate-hermes", + minimax: "@openclaw/minimax-provider", + "minimax-provider": "@openclaw/minimax-provider", + mistral: "@openclaw/mistral-provider", + "mistral-provider": "@openclaw/mistral-provider", + moonshot: "@openclaw/moonshot-provider", + "moonshot-provider": "@openclaw/moonshot-provider", + msteams: "@openclaw/msteams", + "nextcloud-talk": "@openclaw/nextcloud-talk", + nostr: "@openclaw/nostr", + nvidia: "@openclaw/nvidia-provider", + "nvidia-provider": "@openclaw/nvidia-provider", + ollama: "@openclaw/ollama-provider", + "ollama-provider": "@openclaw/ollama-provider", + "open-prose": "@openclaw/open-prose", + openai: "@openclaw/openai-provider", + "openai-provider": "@openclaw/openai-provider", + opencode: "@openclaw/opencode-provider", + "opencode-go": "@openclaw/opencode-go-provider", + "opencode-go-provider": "@openclaw/opencode-go-provider", + "opencode-provider": "@openclaw/opencode-provider", + openrouter: "@openclaw/openrouter-provider", + "openrouter-provider": "@openclaw/openrouter-provider", + openshell: "@openclaw/openshell-sandbox", + "openshell-sandbox": "@openclaw/openshell-sandbox", + perplexity: "@openclaw/perplexity-plugin", + "perplexity-plugin": "@openclaw/perplexity-plugin", + "phone-control": "@openclaw/phone-control", + "qa-channel": "@openclaw/qa-channel", + "qa-lab": "@openclaw/qa-lab", + "qa-matrix": "@openclaw/qa-matrix", + qianfan: "@openclaw/qianfan-provider", + "qianfan-provider": "@openclaw/qianfan-provider", + qqbot: "@openclaw/qqbot", + qwen: "@openclaw/qwen-provider", + "qwen-provider": "@openclaw/qwen-provider", + runway: "@openclaw/runway-provider", + "runway-provider": "@openclaw/runway-provider", + searxng: "@openclaw/searxng-plugin", + "searxng-plugin": "@openclaw/searxng-plugin", + senseaudio: "@openclaw/senseaudio-provider", + "senseaudio-provider": "@openclaw/senseaudio-provider", + sglang: "@openclaw/sglang-provider", + "sglang-provider": "@openclaw/sglang-provider", + signal: "@openclaw/signal", + "skill-workshop": "@openclaw/skill-workshop", + slack: "@openclaw/slack", + stepfun: "@openclaw/stepfun-provider", + "stepfun-provider": "@openclaw/stepfun-provider", + "synology-chat": "@openclaw/synology-chat", + synthetic: "@openclaw/synthetic-provider", + "synthetic-provider": "@openclaw/synthetic-provider", + "talk-voice": "@openclaw/talk-voice", + tavily: "@openclaw/tavily-plugin", + "tavily-plugin": "@openclaw/tavily-plugin", + telegram: "@openclaw/telegram", + tencent: "@openclaw/tencent-provider", + "tencent-provider": "@openclaw/tencent-provider", + "thread-ownership": "@openclaw/thread-ownership", + tlon: "@openclaw/tlon", + together: "@openclaw/together-provider", + "together-provider": "@openclaw/together-provider", + tokenjuice: "@openclaw/tokenjuice", + "tts-local-cli": "@openclaw/tts-local-cli", + twitch: "@openclaw/twitch", + venice: "@openclaw/venice-provider", + "venice-provider": "@openclaw/venice-provider", + "vercel-ai-gateway": "@openclaw/vercel-ai-gateway-provider", + "vercel-ai-gateway-provider": "@openclaw/vercel-ai-gateway-provider", + vllm: "@openclaw/vllm-provider", + "vllm-provider": "@openclaw/vllm-provider", + "voice-call": "@openclaw/voice-call", + volcengine: "@openclaw/volcengine-provider", + "volcengine-provider": "@openclaw/volcengine-provider", + voyage: "@openclaw/voyage-provider", + "voyage-provider": "@openclaw/voyage-provider", + vydra: "@openclaw/vydra-provider", + "vydra-provider": "@openclaw/vydra-provider", + "web-readability": "@openclaw/web-readability-plugin", + "web-readability-plugin": "@openclaw/web-readability-plugin", + webhooks: "@openclaw/webhooks", + whatsapp: "@openclaw/whatsapp", + xai: "@openclaw/xai-plugin", + "xai-plugin": "@openclaw/xai-plugin", + xiaomi: "@openclaw/xiaomi-provider", + "xiaomi-provider": "@openclaw/xiaomi-provider", + zai: "@openclaw/zai-provider", + "zai-provider": "@openclaw/zai-provider", + zalo: "@openclaw/zalo", + zalouser: "@openclaw/zalouser", +}; +export function getOpenClawExtensionPackageName(slug) { + const normalizedSlug = slug.trim().toLowerCase(); + if (!normalizedSlug) + return null; + return (OPENCLAW_EXTENSION_SLUG_TO_PACKAGE[normalizedSlug] ?? null); +} +export function getOpenClawPackageCandidateNames(requestedName) { + if (requestedName.includes("/")) + return [requestedName]; + const officialPackageName = getOpenClawExtensionPackageName(requestedName); + return Array.from(new Set([officialPackageName, `@openclaw/${requestedName}`, requestedName].filter((name) => typeof name === "string" && name.length > 0))); +} +//# sourceMappingURL=openClawExtensionSlugs.js.map \ No newline at end of file diff --git a/packages/schema/dist/openClawExtensionSlugs.js.map b/packages/schema/dist/openClawExtensionSlugs.js.map new file mode 100644 index 00000000..8b5be1af --- /dev/null +++ b/packages/schema/dist/openClawExtensionSlugs.js.map @@ -0,0 +1 @@ +{"version":3,"file":"openClawExtensionSlugs.js","sourceRoot":"","sources":["../src/openClawExtensionSlugs.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/packages/schema/src/index.ts b/packages/schema/src/index.ts index 85f6fa8f..af5ec8e8 100644 --- a/packages/schema/src/index.ts +++ b/packages/schema/src/index.ts @@ -5,6 +5,7 @@ export * from "./catalogMetadata.js"; export * from "./docsLinks.js"; export * from "./license.js"; export * from "./openclawContract.js"; +export * from "./openClawExtensionSlugs.js"; export * from "./packages.js"; export * from "./pluginCategories.js"; export { ApiRoutes, LegacyApiRoutes } from "./routes.js"; diff --git a/packages/schema/src/openClawExtensionSlugs.ts b/packages/schema/src/openClawExtensionSlugs.ts new file mode 100644 index 00000000..877bb3e2 --- /dev/null +++ b/packages/schema/src/openClawExtensionSlugs.ts @@ -0,0 +1,214 @@ +export const OPENCLAW_EXTENSION_SLUG_TO_PACKAGE = { + acpx: "@openclaw/acpx", + "active-memory": "@openclaw/active-memory", + alibaba: "@openclaw/alibaba-provider", + "alibaba-provider": "@openclaw/alibaba-provider", + "amazon-bedrock": "@openclaw/amazon-bedrock-provider", + "amazon-bedrock-mantle": "@openclaw/amazon-bedrock-mantle-provider", + "amazon-bedrock-mantle-provider": "@openclaw/amazon-bedrock-mantle-provider", + "amazon-bedrock-provider": "@openclaw/amazon-bedrock-provider", + anthropic: "@openclaw/anthropic-provider", + "anthropic-provider": "@openclaw/anthropic-provider", + "anthropic-vertex": "@openclaw/anthropic-vertex-provider", + "anthropic-vertex-provider": "@openclaw/anthropic-vertex-provider", + arcee: "@openclaw/arcee-provider", + "arcee-provider": "@openclaw/arcee-provider", + "azure-speech": "@openclaw/azure-speech", + bluebubbles: "@openclaw/bluebubbles", + bonjour: "@openclaw/bonjour", + brave: "@openclaw/brave-plugin", + "brave-plugin": "@openclaw/brave-plugin", + browser: "@openclaw/browser-plugin", + "browser-plugin": "@openclaw/browser-plugin", + byteplus: "@openclaw/byteplus-provider", + "byteplus-provider": "@openclaw/byteplus-provider", + cerebras: "@openclaw/cerebras-provider", + "cerebras-provider": "@openclaw/cerebras-provider", + chutes: "@openclaw/chutes-provider", + "chutes-provider": "@openclaw/chutes-provider", + "cloudflare-ai-gateway": "@openclaw/cloudflare-ai-gateway-provider", + "cloudflare-ai-gateway-provider": "@openclaw/cloudflare-ai-gateway-provider", + codex: "@openclaw/codex", + comfy: "@openclaw/comfy-provider", + "comfy-provider": "@openclaw/comfy-provider", + "copilot-proxy": "@openclaw/copilot-proxy", + deepgram: "@openclaw/deepgram-provider", + "deepgram-provider": "@openclaw/deepgram-provider", + deepinfra: "@openclaw/deepinfra-provider", + "deepinfra-provider": "@openclaw/deepinfra-provider", + deepseek: "@openclaw/deepseek-provider", + "deepseek-provider": "@openclaw/deepseek-provider", + "device-pair": "@openclaw/device-pair", + "diagnostics-otel": "@openclaw/diagnostics-otel", + "diagnostics-prometheus": "@openclaw/diagnostics-prometheus", + diffs: "@openclaw/diffs", + "diffs-language-pack": "@openclaw/diffs-language-pack", + discord: "@openclaw/discord", + "document-extract": "@openclaw/document-extract-plugin", + "document-extract-plugin": "@openclaw/document-extract-plugin", + duckduckgo: "@openclaw/duckduckgo-plugin", + "duckduckgo-plugin": "@openclaw/duckduckgo-plugin", + elevenlabs: "@openclaw/elevenlabs-speech", + "elevenlabs-speech": "@openclaw/elevenlabs-speech", + exa: "@openclaw/exa-plugin", + "exa-plugin": "@openclaw/exa-plugin", + fal: "@openclaw/fal-provider", + "fal-provider": "@openclaw/fal-provider", + feishu: "@openclaw/feishu", + "file-transfer": "@openclaw/file-transfer", + firecrawl: "@openclaw/firecrawl-plugin", + "firecrawl-plugin": "@openclaw/firecrawl-plugin", + fireworks: "@openclaw/fireworks-provider", + "fireworks-provider": "@openclaw/fireworks-provider", + "github-copilot": "@openclaw/github-copilot-provider", + "github-copilot-provider": "@openclaw/github-copilot-provider", + google: "@openclaw/google-plugin", + "google-meet": "@openclaw/google-meet", + "google-plugin": "@openclaw/google-plugin", + googlechat: "@openclaw/googlechat", + gradium: "@openclaw/gradium-speech", + "gradium-speech": "@openclaw/gradium-speech", + groq: "@openclaw/groq-provider", + "groq-provider": "@openclaw/groq-provider", + huggingface: "@openclaw/huggingface-provider", + "huggingface-provider": "@openclaw/huggingface-provider", + imessage: "@openclaw/imessage", + inworld: "@openclaw/inworld-speech", + "inworld-speech": "@openclaw/inworld-speech", + irc: "@openclaw/irc", + kilocode: "@openclaw/kilocode-provider", + "kilocode-provider": "@openclaw/kilocode-provider", + kimi: "@openclaw/kimi-provider", + "kimi-coding": "@openclaw/kimi-provider", + "kimi-provider": "@openclaw/kimi-provider", + line: "@openclaw/line", + litellm: "@openclaw/litellm-provider", + "litellm-provider": "@openclaw/litellm-provider", + "llm-task": "@openclaw/llm-task", + lmstudio: "@openclaw/lmstudio-provider", + "lmstudio-provider": "@openclaw/lmstudio-provider", + lobster: "@openclaw/lobster", + matrix: "@openclaw/matrix", + mattermost: "@openclaw/mattermost", + "memory-core": "@openclaw/memory-core", + "memory-lancedb": "@openclaw/memory-lancedb", + "memory-wiki": "@openclaw/memory-wiki", + microsoft: "@openclaw/microsoft-speech", + "microsoft-foundry": "@openclaw/microsoft-foundry", + "microsoft-speech": "@openclaw/microsoft-speech", + "migrate-claude": "@openclaw/migrate-claude", + "migrate-hermes": "@openclaw/migrate-hermes", + minimax: "@openclaw/minimax-provider", + "minimax-provider": "@openclaw/minimax-provider", + mistral: "@openclaw/mistral-provider", + "mistral-provider": "@openclaw/mistral-provider", + moonshot: "@openclaw/moonshot-provider", + "moonshot-provider": "@openclaw/moonshot-provider", + msteams: "@openclaw/msteams", + "nextcloud-talk": "@openclaw/nextcloud-talk", + nostr: "@openclaw/nostr", + nvidia: "@openclaw/nvidia-provider", + "nvidia-provider": "@openclaw/nvidia-provider", + ollama: "@openclaw/ollama-provider", + "ollama-provider": "@openclaw/ollama-provider", + "open-prose": "@openclaw/open-prose", + openai: "@openclaw/openai-provider", + "openai-provider": "@openclaw/openai-provider", + opencode: "@openclaw/opencode-provider", + "opencode-go": "@openclaw/opencode-go-provider", + "opencode-go-provider": "@openclaw/opencode-go-provider", + "opencode-provider": "@openclaw/opencode-provider", + openrouter: "@openclaw/openrouter-provider", + "openrouter-provider": "@openclaw/openrouter-provider", + openshell: "@openclaw/openshell-sandbox", + "openshell-sandbox": "@openclaw/openshell-sandbox", + perplexity: "@openclaw/perplexity-plugin", + "perplexity-plugin": "@openclaw/perplexity-plugin", + "phone-control": "@openclaw/phone-control", + "qa-channel": "@openclaw/qa-channel", + "qa-lab": "@openclaw/qa-lab", + "qa-matrix": "@openclaw/qa-matrix", + qianfan: "@openclaw/qianfan-provider", + "qianfan-provider": "@openclaw/qianfan-provider", + qqbot: "@openclaw/qqbot", + qwen: "@openclaw/qwen-provider", + "qwen-provider": "@openclaw/qwen-provider", + runway: "@openclaw/runway-provider", + "runway-provider": "@openclaw/runway-provider", + searxng: "@openclaw/searxng-plugin", + "searxng-plugin": "@openclaw/searxng-plugin", + senseaudio: "@openclaw/senseaudio-provider", + "senseaudio-provider": "@openclaw/senseaudio-provider", + sglang: "@openclaw/sglang-provider", + "sglang-provider": "@openclaw/sglang-provider", + signal: "@openclaw/signal", + "skill-workshop": "@openclaw/skill-workshop", + slack: "@openclaw/slack", + stepfun: "@openclaw/stepfun-provider", + "stepfun-provider": "@openclaw/stepfun-provider", + "synology-chat": "@openclaw/synology-chat", + synthetic: "@openclaw/synthetic-provider", + "synthetic-provider": "@openclaw/synthetic-provider", + "talk-voice": "@openclaw/talk-voice", + tavily: "@openclaw/tavily-plugin", + "tavily-plugin": "@openclaw/tavily-plugin", + telegram: "@openclaw/telegram", + tencent: "@openclaw/tencent-provider", + "tencent-provider": "@openclaw/tencent-provider", + "thread-ownership": "@openclaw/thread-ownership", + tlon: "@openclaw/tlon", + together: "@openclaw/together-provider", + "together-provider": "@openclaw/together-provider", + tokenjuice: "@openclaw/tokenjuice", + "tts-local-cli": "@openclaw/tts-local-cli", + twitch: "@openclaw/twitch", + venice: "@openclaw/venice-provider", + "venice-provider": "@openclaw/venice-provider", + "vercel-ai-gateway": "@openclaw/vercel-ai-gateway-provider", + "vercel-ai-gateway-provider": "@openclaw/vercel-ai-gateway-provider", + vllm: "@openclaw/vllm-provider", + "vllm-provider": "@openclaw/vllm-provider", + "voice-call": "@openclaw/voice-call", + volcengine: "@openclaw/volcengine-provider", + "volcengine-provider": "@openclaw/volcengine-provider", + voyage: "@openclaw/voyage-provider", + "voyage-provider": "@openclaw/voyage-provider", + vydra: "@openclaw/vydra-provider", + "vydra-provider": "@openclaw/vydra-provider", + "web-readability": "@openclaw/web-readability-plugin", + "web-readability-plugin": "@openclaw/web-readability-plugin", + webhooks: "@openclaw/webhooks", + whatsapp: "@openclaw/whatsapp", + xai: "@openclaw/xai-plugin", + "xai-plugin": "@openclaw/xai-plugin", + xiaomi: "@openclaw/xiaomi-provider", + "xiaomi-provider": "@openclaw/xiaomi-provider", + zai: "@openclaw/zai-provider", + "zai-provider": "@openclaw/zai-provider", + zalo: "@openclaw/zalo", + zalouser: "@openclaw/zalouser", +} as const; + +export function getOpenClawExtensionPackageName(slug: string): string | null { + const normalizedSlug = slug.trim().toLowerCase(); + if (!normalizedSlug) return null; + + return ( + OPENCLAW_EXTENSION_SLUG_TO_PACKAGE[ + normalizedSlug as keyof typeof OPENCLAW_EXTENSION_SLUG_TO_PACKAGE + ] ?? null + ); +} + +export function getOpenClawPackageCandidateNames(requestedName: string): string[] { + if (requestedName.includes("/")) return [requestedName]; + + const officialPackageName = getOpenClawExtensionPackageName(requestedName); + return Array.from( + new Set( + [officialPackageName, `@openclaw/${requestedName}`, requestedName].filter( + (name): name is string => typeof name === "string" && name.length > 0, + ), + ), + ); +} diff --git a/specs/slug-routing.md b/specs/slug-routing.md index e2cac977..555eada8 100644 --- a/specs/slug-routing.md +++ b/specs/slug-routing.md @@ -25,6 +25,9 @@ Publisher profiles: - Canonical page: `/` - Legacy compatibility pages: `/user/`, `/p/`, `/u/`, and `/orgs/` redirect to `/` +- Existing publisher handles that predate an official OpenClaw alias remain + reachable at `/user/` so the alias can keep its canonical top-level + shortcut. Skills: @@ -111,6 +114,9 @@ This means official OpenClaw aliases are reserved before skills at the root. That is intentional: `https://clawhub.ai/codex` must show the official OpenClaw Codex plugin even if a skill named `codex` exists. +Publisher handle creation and rename paths reserve the full official alias set. +This prevents new profiles from taking over those top-level plugin shortcuts. + ## Collision policy Do not make every `/:owner/:slug` path a universal package route. Owners can diff --git a/src/__tests__/users-route-redirect.test.ts b/src/__tests__/users-route-redirect.test.ts index 73329834..7b267dc9 100644 --- a/src/__tests__/users-route-redirect.test.ts +++ b/src/__tests__/users-route-redirect.test.ts @@ -62,6 +62,23 @@ describe("users route redirect", () => { }); }); + it("keeps existing official-alias publishers reachable from the legacy profile route", async () => { + const route = (await import("../routes/user/$handle")).Route as unknown as { + __config: { + beforeLoad: (args: { + params: { handle: string }; + search: Record; + }) => unknown; + }; + }; + + redirectMock.mockClear(); + expect(() => + route.__config.beforeLoad({ params: { handle: "tencent" }, search: {} }), + ).not.toThrow(); + expect(redirectMock).not.toHaveBeenCalled(); + }); + it("redirects legacy org profile routes to publisher profiles", async () => { const route = (await import("../routes/orgs/$handle")).Route as unknown as { __config: { diff --git a/src/components/PublisherListItem.tsx b/src/components/PublisherListItem.tsx index 393e900e..10e78e9f 100644 --- a/src/components/PublisherListItem.tsx +++ b/src/components/PublisherListItem.tsx @@ -1,6 +1,7 @@ import { Link } from "@tanstack/react-router"; import { Download } from "lucide-react"; import { formatCompactStat } from "../lib/numberFormat"; +import { buildPublisherProfileHref } from "../lib/ownerRoute"; import { type PublicPublisherListItem, type PublicPublisherPublishedItem, @@ -41,8 +42,7 @@ export function PublisherListItem({ publisher, variant = "list" }: PublisherList return ( diff --git a/src/lib/openClawExtensionSlugs.ts b/src/lib/openClawExtensionSlugs.ts index 877f3ec5..944b381b 100644 --- a/src/lib/openClawExtensionSlugs.ts +++ b/src/lib/openClawExtensionSlugs.ts @@ -1,214 +1,5 @@ -const OPENCLAW_EXTENSION_SLUG_TO_PACKAGE = { - acpx: "@openclaw/acpx", - "active-memory": "@openclaw/active-memory", - alibaba: "@openclaw/alibaba-provider", - "alibaba-provider": "@openclaw/alibaba-provider", - "amazon-bedrock": "@openclaw/amazon-bedrock-provider", - "amazon-bedrock-mantle": "@openclaw/amazon-bedrock-mantle-provider", - "amazon-bedrock-mantle-provider": "@openclaw/amazon-bedrock-mantle-provider", - "amazon-bedrock-provider": "@openclaw/amazon-bedrock-provider", - anthropic: "@openclaw/anthropic-provider", - "anthropic-provider": "@openclaw/anthropic-provider", - "anthropic-vertex": "@openclaw/anthropic-vertex-provider", - "anthropic-vertex-provider": "@openclaw/anthropic-vertex-provider", - arcee: "@openclaw/arcee-provider", - "arcee-provider": "@openclaw/arcee-provider", - "azure-speech": "@openclaw/azure-speech", - bluebubbles: "@openclaw/bluebubbles", - bonjour: "@openclaw/bonjour", - brave: "@openclaw/brave-plugin", - "brave-plugin": "@openclaw/brave-plugin", - browser: "@openclaw/browser-plugin", - "browser-plugin": "@openclaw/browser-plugin", - byteplus: "@openclaw/byteplus-provider", - "byteplus-provider": "@openclaw/byteplus-provider", - cerebras: "@openclaw/cerebras-provider", - "cerebras-provider": "@openclaw/cerebras-provider", - chutes: "@openclaw/chutes-provider", - "chutes-provider": "@openclaw/chutes-provider", - "cloudflare-ai-gateway": "@openclaw/cloudflare-ai-gateway-provider", - "cloudflare-ai-gateway-provider": "@openclaw/cloudflare-ai-gateway-provider", - codex: "@openclaw/codex", - comfy: "@openclaw/comfy-provider", - "comfy-provider": "@openclaw/comfy-provider", - "copilot-proxy": "@openclaw/copilot-proxy", - deepgram: "@openclaw/deepgram-provider", - "deepgram-provider": "@openclaw/deepgram-provider", - deepinfra: "@openclaw/deepinfra-provider", - "deepinfra-provider": "@openclaw/deepinfra-provider", - deepseek: "@openclaw/deepseek-provider", - "deepseek-provider": "@openclaw/deepseek-provider", - "device-pair": "@openclaw/device-pair", - "diagnostics-otel": "@openclaw/diagnostics-otel", - "diagnostics-prometheus": "@openclaw/diagnostics-prometheus", - diffs: "@openclaw/diffs", - "diffs-language-pack": "@openclaw/diffs-language-pack", - discord: "@openclaw/discord", - "document-extract": "@openclaw/document-extract-plugin", - "document-extract-plugin": "@openclaw/document-extract-plugin", - duckduckgo: "@openclaw/duckduckgo-plugin", - "duckduckgo-plugin": "@openclaw/duckduckgo-plugin", - elevenlabs: "@openclaw/elevenlabs-speech", - "elevenlabs-speech": "@openclaw/elevenlabs-speech", - exa: "@openclaw/exa-plugin", - "exa-plugin": "@openclaw/exa-plugin", - fal: "@openclaw/fal-provider", - "fal-provider": "@openclaw/fal-provider", - feishu: "@openclaw/feishu", - "file-transfer": "@openclaw/file-transfer", - firecrawl: "@openclaw/firecrawl-plugin", - "firecrawl-plugin": "@openclaw/firecrawl-plugin", - fireworks: "@openclaw/fireworks-provider", - "fireworks-provider": "@openclaw/fireworks-provider", - "github-copilot": "@openclaw/github-copilot-provider", - "github-copilot-provider": "@openclaw/github-copilot-provider", - google: "@openclaw/google-plugin", - "google-meet": "@openclaw/google-meet", - "google-plugin": "@openclaw/google-plugin", - googlechat: "@openclaw/googlechat", - gradium: "@openclaw/gradium-speech", - "gradium-speech": "@openclaw/gradium-speech", - groq: "@openclaw/groq-provider", - "groq-provider": "@openclaw/groq-provider", - huggingface: "@openclaw/huggingface-provider", - "huggingface-provider": "@openclaw/huggingface-provider", - imessage: "@openclaw/imessage", - inworld: "@openclaw/inworld-speech", - "inworld-speech": "@openclaw/inworld-speech", - irc: "@openclaw/irc", - kilocode: "@openclaw/kilocode-provider", - "kilocode-provider": "@openclaw/kilocode-provider", - kimi: "@openclaw/kimi-provider", - "kimi-coding": "@openclaw/kimi-provider", - "kimi-provider": "@openclaw/kimi-provider", - line: "@openclaw/line", - litellm: "@openclaw/litellm-provider", - "litellm-provider": "@openclaw/litellm-provider", - "llm-task": "@openclaw/llm-task", - lmstudio: "@openclaw/lmstudio-provider", - "lmstudio-provider": "@openclaw/lmstudio-provider", - lobster: "@openclaw/lobster", - matrix: "@openclaw/matrix", - mattermost: "@openclaw/mattermost", - "memory-core": "@openclaw/memory-core", - "memory-lancedb": "@openclaw/memory-lancedb", - "memory-wiki": "@openclaw/memory-wiki", - microsoft: "@openclaw/microsoft-speech", - "microsoft-foundry": "@openclaw/microsoft-foundry", - "microsoft-speech": "@openclaw/microsoft-speech", - "migrate-claude": "@openclaw/migrate-claude", - "migrate-hermes": "@openclaw/migrate-hermes", - minimax: "@openclaw/minimax-provider", - "minimax-provider": "@openclaw/minimax-provider", - mistral: "@openclaw/mistral-provider", - "mistral-provider": "@openclaw/mistral-provider", - moonshot: "@openclaw/moonshot-provider", - "moonshot-provider": "@openclaw/moonshot-provider", - msteams: "@openclaw/msteams", - "nextcloud-talk": "@openclaw/nextcloud-talk", - nostr: "@openclaw/nostr", - nvidia: "@openclaw/nvidia-provider", - "nvidia-provider": "@openclaw/nvidia-provider", - ollama: "@openclaw/ollama-provider", - "ollama-provider": "@openclaw/ollama-provider", - "open-prose": "@openclaw/open-prose", - openai: "@openclaw/openai-provider", - "openai-provider": "@openclaw/openai-provider", - opencode: "@openclaw/opencode-provider", - "opencode-go": "@openclaw/opencode-go-provider", - "opencode-go-provider": "@openclaw/opencode-go-provider", - "opencode-provider": "@openclaw/opencode-provider", - openrouter: "@openclaw/openrouter-provider", - "openrouter-provider": "@openclaw/openrouter-provider", - openshell: "@openclaw/openshell-sandbox", - "openshell-sandbox": "@openclaw/openshell-sandbox", - perplexity: "@openclaw/perplexity-plugin", - "perplexity-plugin": "@openclaw/perplexity-plugin", - "phone-control": "@openclaw/phone-control", - "qa-channel": "@openclaw/qa-channel", - "qa-lab": "@openclaw/qa-lab", - "qa-matrix": "@openclaw/qa-matrix", - qianfan: "@openclaw/qianfan-provider", - "qianfan-provider": "@openclaw/qianfan-provider", - qqbot: "@openclaw/qqbot", - qwen: "@openclaw/qwen-provider", - "qwen-provider": "@openclaw/qwen-provider", - runway: "@openclaw/runway-provider", - "runway-provider": "@openclaw/runway-provider", - searxng: "@openclaw/searxng-plugin", - "searxng-plugin": "@openclaw/searxng-plugin", - senseaudio: "@openclaw/senseaudio-provider", - "senseaudio-provider": "@openclaw/senseaudio-provider", - sglang: "@openclaw/sglang-provider", - "sglang-provider": "@openclaw/sglang-provider", - signal: "@openclaw/signal", - "skill-workshop": "@openclaw/skill-workshop", - slack: "@openclaw/slack", - stepfun: "@openclaw/stepfun-provider", - "stepfun-provider": "@openclaw/stepfun-provider", - "synology-chat": "@openclaw/synology-chat", - synthetic: "@openclaw/synthetic-provider", - "synthetic-provider": "@openclaw/synthetic-provider", - "talk-voice": "@openclaw/talk-voice", - tavily: "@openclaw/tavily-plugin", - "tavily-plugin": "@openclaw/tavily-plugin", - telegram: "@openclaw/telegram", - tencent: "@openclaw/tencent-provider", - "tencent-provider": "@openclaw/tencent-provider", - "thread-ownership": "@openclaw/thread-ownership", - tlon: "@openclaw/tlon", - together: "@openclaw/together-provider", - "together-provider": "@openclaw/together-provider", - tokenjuice: "@openclaw/tokenjuice", - "tts-local-cli": "@openclaw/tts-local-cli", - twitch: "@openclaw/twitch", - venice: "@openclaw/venice-provider", - "venice-provider": "@openclaw/venice-provider", - "vercel-ai-gateway": "@openclaw/vercel-ai-gateway-provider", - "vercel-ai-gateway-provider": "@openclaw/vercel-ai-gateway-provider", - vllm: "@openclaw/vllm-provider", - "vllm-provider": "@openclaw/vllm-provider", - "voice-call": "@openclaw/voice-call", - volcengine: "@openclaw/volcengine-provider", - "volcengine-provider": "@openclaw/volcengine-provider", - voyage: "@openclaw/voyage-provider", - "voyage-provider": "@openclaw/voyage-provider", - vydra: "@openclaw/vydra-provider", - "vydra-provider": "@openclaw/vydra-provider", - "web-readability": "@openclaw/web-readability-plugin", - "web-readability-plugin": "@openclaw/web-readability-plugin", - webhooks: "@openclaw/webhooks", - whatsapp: "@openclaw/whatsapp", - xai: "@openclaw/xai-plugin", - "xai-plugin": "@openclaw/xai-plugin", - xiaomi: "@openclaw/xiaomi-provider", - "xiaomi-provider": "@openclaw/xiaomi-provider", - zai: "@openclaw/zai-provider", - "zai-provider": "@openclaw/zai-provider", - zalo: "@openclaw/zalo", - zalouser: "@openclaw/zalouser", -} as const; - -export function getOpenClawExtensionPackageName(slug: string): string | null { - const normalizedSlug = slug.trim().toLowerCase(); - if (!normalizedSlug) return null; - - return ( - OPENCLAW_EXTENSION_SLUG_TO_PACKAGE[ - normalizedSlug as keyof typeof OPENCLAW_EXTENSION_SLUG_TO_PACKAGE - ] ?? null - ); -} - -export function getOpenClawPackageCandidateNames(requestedName: string): string[] { - if (requestedName.includes("/")) return [requestedName]; - - const officialPackageName = getOpenClawExtensionPackageName(requestedName); - return Array.from( - new Set( - [officialPackageName, `@openclaw/${requestedName}`, requestedName].filter( - (name): name is string => typeof name === "string" && name.length > 0, - ), - ), - ); -} +export { + getOpenClawExtensionPackageName, + getOpenClawPackageCandidateNames, + OPENCLAW_EXTENSION_SLUG_TO_PACKAGE, +} from "clawhub-schema"; diff --git a/src/lib/ownerRoute.test.ts b/src/lib/ownerRoute.test.ts index 759ae7ba..39e1288a 100644 --- a/src/lib/ownerRoute.test.ts +++ b/src/lib/ownerRoute.test.ts @@ -1,11 +1,17 @@ import { describe, expect, it } from "vitest"; import { + buildPublisherProfileHref, isOwnerRouteHandleOrIdSegment, isOwnerRouteHandleSegment, isOwnerRouteScopeSegment, } from "./ownerRoute"; describe("owner route segments", () => { + it("uses the legacy profile route for official alias collisions", () => { + expect(buildPublisherProfileHref("tencent")).toBe("/user/tencent"); + expect(buildPublisherProfileHref("steipete")).toBe("/steipete"); + }); + it("accepts npm-compatible publisher handle characters", () => { expect(isOwnerRouteHandleSegment("example.tools")).toBe(true); expect(isOwnerRouteHandleSegment("lab_1")).toBe(true); diff --git a/src/lib/ownerRoute.ts b/src/lib/ownerRoute.ts index 687dac02..dd2d3d80 100644 --- a/src/lib/ownerRoute.ts +++ b/src/lib/ownerRoute.ts @@ -1,3 +1,5 @@ +import { getOpenClawExtensionPackageName } from "./openClawExtensionSlugs"; + const OWNER_ROUTE_HANDLE_PATTERN = /^[a-zA-Z0-9](?:[a-zA-Z0-9._-]{0,38}[a-zA-Z0-9])?$/; const OWNER_ROUTE_SCOPE_PATTERN = /^@[a-zA-Z0-9](?:[a-zA-Z0-9._-]{0,38}[a-zA-Z0-9])?$/; @@ -22,7 +24,13 @@ function routeSegment(value: string) { } export function buildPublisherProfileHref(handle: string) { - return `/${routeSegment(handle)}`; + return isLegacyPublisherProfileHandle(handle) + ? `/user/${routeSegment(handle)}` + : `/${routeSegment(handle)}`; +} + +export function isLegacyPublisherProfileHandle(handle: string) { + return Boolean(getOpenClawExtensionPackageName(handle)); } export function buildSkillDetailHref(owner: string, slug: string) { diff --git a/src/lib/slugRoute.test.ts b/src/lib/slugRoute.test.ts index 49556e76..4b6300d4 100644 --- a/src/lib/slugRoute.test.ts +++ b/src/lib/slugRoute.test.ts @@ -92,4 +92,15 @@ describe("slug route resolution", () => { }); expect(fetchSkillPageDataMock).not.toHaveBeenCalled(); }); + + it("keeps official OpenClaw aliases ahead of colliding publisher handles", async () => { + queryMock.mockResolvedValue({ _id: "publishers:tencent", handle: "tencent" }); + + await expect(resolveTopLevelSlugRoute("tencent")).resolves.toEqual({ + kind: "plugin", + name: "@openclaw/tencent-provider", + href: "/openclaw/plugins/tencent-provider", + }); + expect(fetchSkillPageDataMock).not.toHaveBeenCalled(); + }); }); diff --git a/src/routes/user/$handle.tsx b/src/routes/user/$handle.tsx index 49dba223..0702fe0c 100644 --- a/src/routes/user/$handle.tsx +++ b/src/routes/user/$handle.tsx @@ -14,7 +14,7 @@ import { Card, CardContent } from "../../components/ui/card"; import { Skeleton } from "../../components/ui/skeleton"; import { formatCompactStat } from "../../lib/numberFormat"; import { buildPublisherMeta } from "../../lib/og"; -import { buildPublisherProfileHref } from "../../lib/ownerRoute"; +import { buildPublisherProfileHref, isLegacyPublisherProfileHandle } from "../../lib/ownerRoute"; import type { PublicPublisher, PublicPublisherCatalogDisplay, @@ -25,6 +25,8 @@ import { readPublicDownloadCount } from "../../lib/publicUser"; export const Route = createFileRoute("/user/$handle")({ beforeLoad: ({ params }) => { + if (isLegacyPublisherProfileHandle(params.handle)) return; + throw redirect({ href: buildPublisherProfileHref(params.handle), replace: true,