fix(skill): hide toggle and rescan on virtual MCP/ACP skill cards (#83)

This commit is contained in:
matevip 2026-05-09 11:32:56 +08:00
parent d5969bb32c
commit 77456d26f8
3 changed files with 46 additions and 1 deletions

View File

@ -2656,6 +2656,8 @@ export default {
empty: 'No skills found',
emptyDesc: 'Add skills to enhance your agents\' capabilities',
noDescription: 'No description',
// Issue #83: shown on the padlock that replaces the toggle for MCP/ACP virtual skills.
virtualReadonlyHint: 'MCP/ACP-derived skills can\'t be toggled here — manage the underlying service in Settings ▸ Connections.',
modal: {
configureTitle: 'Configure Skill',
newTitle: 'New Skill',

View File

@ -2660,6 +2660,8 @@ export default {
empty: '暂无技能',
emptyDesc: '添加技能以增强 Agent 的能力',
noDescription: '暂无描述',
// Issue #83: shown on the padlock that replaces the toggle for MCP/ACP virtual skills.
virtualReadonlyHint: 'MCP / ACP 衍生技能不能在此切换,请到 Settings ▸ 连接页面操作对应的服务',
modal: {
configureTitle: '配置技能',
newTitle: '新建技能',

View File

@ -98,10 +98,25 @@
<!-- RFC-042 §2.2.4 slug under display name when they differ -->
<div v-if="hasI18nName(skill)" class="skill-slug">{{ skill.name }}</div>
</div>
<label class="toggle-switch" @click.stop>
<!-- Issue #83: virtual MCP/ACP skills are view-only mirrors of the
underlying MCP/ACP server row, with no mate_skill row to flip.
Hiding the toggle here matches how the configure / delete
buttons are gated below; users enable/disable from the
Settings MCP connection page instead. -->
<label v-if="!isSkillRowVirtual(skill)" class="toggle-switch" @click.stop>
<input type="checkbox" :checked="skill.enabled" @change="toggleSkill(skill)" />
<span class="toggle-slider"></span>
</label>
<span
v-else
class="virtual-toggle-hint"
:title="$t('skills.virtualReadonlyHint')"
>
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<rect x="3" y="11" width="18" height="11" rx="2" ry="2"/>
<path d="M7 11V7a5 5 0 0 1 10 0v4"/>
</svg>
</span>
</div>
<p class="skill-desc">{{ skill.description || t('skills.noDescription') }}</p>
@ -467,7 +482,12 @@
<span v-if="detailSkill.securityScanTime" class="scan-findings-time">
· {{ formatScanTime(detailSkill.securityScanTime) }}
</span>
<!-- Issue #83 follow-up: virtual MCP/ACP skills can't be rescanned
backend rejects with err.skill.virtual_readonly. Hide the button
so users don't trigger a guaranteed 4xx; the readonly banner
already tells them where to go. -->
<button
v-if="!isVirtualSkill"
class="scan-rescan-btn"
:disabled="rescanning[String(detailSkill.id)]"
@click="rescanSkill(detailSkill)"
@ -1197,6 +1217,14 @@ async function deleteSkill(idOrSkill: string | number | Skill) {
}
async function toggleSkill(skill: Skill) {
// Issue #83: short-circuit if a programmatic caller reaches this for a
// virtual skill (the UI hides the toggle, but defense-in-depth keeps the
// toast accurate when the backend would otherwise return err.skill.not_found
// on builds that pre-date the rejectVirtualSkillMutation guard).
if (isSkillRowVirtual(skill)) {
ElMessage.warning(t('skills.virtualReadonlyHint'))
return
}
try {
await skillApi.toggle(skill.id, !skill.enabled)
await loadAll()
@ -1746,6 +1774,19 @@ html.dark .scan-finding-item { background: rgba(255, 255, 255, 0.05); }
.toggle-slider::before { content: ''; position: absolute; width: 14px; height: 14px; left: 3px; top: 3px; background: var(--mc-bg-elevated); border-radius: 50%; transition: 0.2s; }
.toggle-switch input:checked + .toggle-slider { background: var(--mc-primary); }
.toggle-switch input:checked + .toggle-slider::before { transform: translateX(16px); }
/* Issue #83: padlock placeholder where the toggle would be on virtual MCP/ACP rows. */
.virtual-toggle-hint {
display: inline-flex;
align-items: center;
justify-content: center;
width: 36px;
height: 20px;
color: var(--mc-text-quaternary);
cursor: help;
flex-shrink: 0;
border-radius: 6px;
}
.virtual-toggle-hint:hover { color: var(--mc-text-tertiary); background: var(--mc-bg-sunken); }
.skill-desc { font-size: 13px; color: var(--mc-text-secondary); margin: 0 0 10px; line-height: 1.5; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; flex: 1; }
/* Runtime Status */