From 61a78087465ce6215b74b15395a1dab938adc07a Mon Sep 17 00:00:00 2001 From: kapelame Date: Thu, 13 Aug 2026 11:23:11 +0800 Subject: [PATCH 1/5] fix(web): remove stale capabilities from agent wizard --- apps/web/src/i18n/locales/en-US/admin.json | 2 - apps/web/src/i18n/locales/zh-CN/admin.json | 2 - .../web/src/pages/admin/CreateAgentDialog.tsx | 70 +++++-------------- 3 files changed, 18 insertions(+), 56 deletions(-) diff --git a/apps/web/src/i18n/locales/en-US/admin.json b/apps/web/src/i18n/locales/en-US/admin.json index 1b3892e..2c633f3 100644 --- a/apps/web/src/i18n/locales/en-US/admin.json +++ b/apps/web/src/i18n/locales/en-US/admin.json @@ -1668,8 +1668,6 @@ "noTagsMember": "This workspace has no reusable capability tags yet.", "goCapabilities": "Go to Capabilities →", "noCapabilityVersion": "No version", - "deprecatedCapabilityBadge": "Deprecated", - "deprecatedCapabilityTooltip": "This capability has been deprecated. Existing bindings keep working; once you uncheck it you cannot re-add it.", "capabilityTypeTabs": { "all": "All", "mcp": "MCP", diff --git a/apps/web/src/i18n/locales/zh-CN/admin.json b/apps/web/src/i18n/locales/zh-CN/admin.json index c6e813c..c2e3da3 100644 --- a/apps/web/src/i18n/locales/zh-CN/admin.json +++ b/apps/web/src/i18n/locales/zh-CN/admin.json @@ -1668,8 +1668,6 @@ "noTagsMember": "当前工作区还没有可复用的能力标签。", "goCapabilities": "去能力页 →", "noCapabilityVersion": "无可用版本", - "deprecatedCapabilityBadge": "已下架", - "deprecatedCapabilityTooltip": "该能力已下架,保留旧绑定可继续使用;取消勾选后无法再选回。", "capabilityTypeTabs": { "all": "全部", "mcp": "MCP", diff --git a/apps/web/src/pages/admin/CreateAgentDialog.tsx b/apps/web/src/pages/admin/CreateAgentDialog.tsx index 55bbc72..1f2ea51 100644 --- a/apps/web/src/pages/admin/CreateAgentDialog.tsx +++ b/apps/web/src/pages/admin/CreateAgentDialog.tsx @@ -322,8 +322,6 @@ export function CreateAgentDialog({ const selectedModelID = modelID || (mode === "create" ? firstModelID : "") const selectedModel = useMemo(() => activeModels.find((m) => m.id === selectedModelID) ?? null, [activeModels, selectedModelID]) const capabilityOptions = useMemo(() => { - // `type: ""` is a sentinel for ghost rows (deprecated bindings whose real - // type is unknown); downstream filters treat it as wildcard. type PickerOption = { id: string name: string @@ -331,7 +329,6 @@ export function CreateAgentDialog({ description: string latestVersionID: string latestVersion: string - deprecated: boolean section: "workspace" | "marketplace" requiredCredentials: RequiredCredential[] } @@ -345,7 +342,6 @@ export function CreateAgentDialog({ description: cap.description ?? "", latestVersionID: cap.latest_version_id ?? "", latestVersion: cap.latest_version ?? cap.latest_published_version ?? "", - deprecated: false, section: "workspace", requiredCredentials: cap.required_credentials ?? [], })) @@ -356,43 +352,15 @@ export function CreateAgentDialog({ description: cap.description ?? "", latestVersionID: cap.latest_version_id ?? "", latestVersion: cap.latest_version ?? "", - deprecated: false, section: "marketplace", requiredCredentials: cap.required_credentials ?? [], })) marketplace.sort((a, b) => a.name.localeCompare(b.name)) - const live: PickerOption[] = [...workspace, ...marketplace] - // Ghost bindings (edit mode): when an admin deprecates a capability the - // agent still binds, ListCapabilities hides it and the row would silently - // vanish from the picker. Merge it back as a disabled row so the user can - // deliberately unbind. The agent profile only stores names (not types), - // so type is left empty and treated as wildcard downstream. - if (mode === "edit") { - const known = new Set(live.map((c) => c.name)) - for (const name of capabilities) { - if (!known.has(name)) { - live.push({ - id: `ghost:${name}`, - name, - type: "", - description: "", - latestVersionID: "", - latestVersion: "", - deprecated: true, - section: "workspace", - requiredCredentials: [], - }) - } - } - } - return live - }, [capabilitiesQ.data, mode, capabilities]) + return [...workspace, ...marketplace] + }, [capabilitiesQ.data]) const capabilityTypeCounts = useMemo(() => { - // Ghost rows have unknown type, so they're excluded from per-type tallies - // (still count toward "all"). const counts = { all: capabilityOptions.length, mcp: 0, skill: 0 } for (const cap of capabilityOptions) { - if (cap.deprecated) continue if (cap.type === "mcp") counts.mcp++ else if (cap.type === "skill") counts.skill++ } @@ -401,9 +369,7 @@ export function CreateAgentDialog({ const visibleCapabilityOptions = useMemo( () => capabilityTypeFilter === "all" ? capabilityOptions - // Ghost rows surface under every type tab; hiding them on a non-matching - // tab would resurrect the "binding seems to have vanished" footgun. - : capabilityOptions.filter((cap) => cap.deprecated || cap.type === capabilityTypeFilter), + : capabilityOptions.filter((cap) => cap.type === capabilityTypeFilter), [capabilityOptions, capabilityTypeFilter] ) // Models the current engine can't drive (wrong wire protocol). Kept in the @@ -779,11 +745,14 @@ export function CreateAgentDialog({ // the user a clearer error tied to the input instead of a stream error. return } - if (mode === "create" && allCapabilitiesQ.isLoading) return + if (!allCapabilitiesQ.isSuccess) return const selectedCapabilities = mode === "create" ? allCapabilitiesPool.filter((cap) => selectedCapabilityIDs.includes(cap.id) && cap.latest_version_id) : [] - const capabilityNames = mode === "create" ? selectedCapabilities.map((cap) => cap.name) : capabilities + const selectableCapabilityNames = new Set(allCapabilitiesPool.map((cap) => cap.name)) + const capabilityNames = mode === "create" + ? selectedCapabilities.map((cap) => cap.name) + : capabilities.filter((name) => selectableCapabilityNames.has(name)) // initialCapabilities carries the per-binding pin choice. Empty // versionID falls back to the capability's latest_version_id so the // server's NOT NULL capability_version_id constraint is satisfied @@ -960,7 +929,7 @@ export function CreateAgentDialog({ hasRequiredModel && (connector !== "agent_daemon" || executionMode !== "local_device" || deviceID !== "") && workDirValid && - (mode !== "create" || !allCapabilitiesQ.isLoading) && + allCapabilitiesQ.isSuccess && (aggregatedRequiredKinds.length === 0 || allCredentialsSatisfied) const step1Valid = @@ -1504,11 +1473,9 @@ export function CreateAgentDialog({ const index = rowCounter++ const checked = mode === "create" ? selectedCapabilityIDs.includes(cap.id) : capabilities.includes(cap.name) const lockedNoVersion = mode === "create" && !cap.latestVersionID - const lockedDeprecatedAndUnchecked = cap.deprecated && !checked - const disabled = lockedNoVersion || lockedDeprecatedAndUnchecked - const ghostTitle = cap.deprecated ? t("agents.form.deprecatedCapabilityTooltip") : undefined + const disabled = lockedNoVersion return ( -