fix(webui): harden skill management

This commit is contained in:
Xubin Ren 2026-07-28 19:49:55 +08:00
parent d276fe1386
commit 8a56eb06ad
16 changed files with 422 additions and 8 deletions

View File

@ -10,6 +10,7 @@ from typing import Any
from nanobot.agent.skills import SkillsLoader
from nanobot.config.loader import load_config, save_config
from nanobot.security.workspace_policy import WorkspaceBoundaryError, require_path_within
class SkillManagementError(Exception):
@ -100,7 +101,15 @@ def delete_webui_skill(
if entry.get("source") != "workspace":
raise SkillManagementError("built-in skills cannot be deleted", status=403)
skills_root = (workspace_path.expanduser().resolve() / "skills").resolve()
workspace = workspace_path.expanduser().resolve()
try:
skills_root = require_path_within(
workspace / "skills",
workspace,
message="skills directory must stay inside the workspace",
)
except WorkspaceBoundaryError as exc:
raise SkillManagementError(str(exc), status=403) from exc
target = skills_root / name
if target.parent != skills_root:
raise SkillManagementError("invalid skill name")

View File

@ -19,6 +19,7 @@ import httpx
from nanobot.agent.skills import SkillsLoader
from nanobot.security.network import PinnedDNSAsyncTransport
from nanobot.security.workspace_policy import WorkspaceBoundaryError, require_path_within
_PROVIDER_ALL = "all"
_PROVIDER_SKILLS_SH = "skills_sh"
@ -353,6 +354,17 @@ async def _install_skills_sh_skill(
if skill_id in existing:
return {"installed": True, "already_installed": True, "name": skill_id}
workspace = workspace_path.expanduser().resolve()
workspace.mkdir(parents=True, exist_ok=True)
try:
require_path_within(
workspace / "skills",
workspace,
message="skills directory must stay inside the workspace",
)
except WorkspaceBoundaryError as exc:
raise SkillsMarketplaceError(str(exc), status=403) from exc
npx = shutil.which("npx")
if npx is None:
raise SkillsMarketplaceError(
@ -360,8 +372,6 @@ async def _install_skills_sh_skill(
status=503,
)
workspace = workspace_path.expanduser().resolve()
workspace.mkdir(parents=True, exist_ok=True)
env = os.environ.copy()
env["DISABLE_TELEMETRY"] = "1"
command = (
@ -439,7 +449,15 @@ async def _install_skillhub_skill(
}
workspace = workspace_path.expanduser().resolve()
skills_root = workspace / "skills"
workspace.mkdir(parents=True, exist_ok=True)
try:
skills_root = require_path_within(
workspace / "skills",
workspace,
message="skills directory must stay inside the workspace",
)
except WorkspaceBoundaryError as exc:
raise SkillsMarketplaceError(str(exc), status=403) from exc
skills_root.mkdir(parents=True, exist_ok=True)
target = skills_root / skill_id

View File

@ -126,3 +126,27 @@ def test_delete_webui_skill_only_deletes_workspace_skills(
with pytest.raises(SkillManagementError) as exc_info:
delete_webui_skill(tmp_path, "cron", disabled_skills=disabled)
assert exc_info.value.status == 403
def test_delete_webui_skill_rejects_symlinked_skills_root(
tmp_path: Path,
) -> None:
workspace = tmp_path / "workspace"
outside = tmp_path / "outside"
workspace.mkdir()
directory = outside / "custom-skill"
directory.mkdir(parents=True)
(directory / "SKILL.md").write_text(
"---\nname: custom-skill\n---\n",
encoding="utf-8",
)
try:
(workspace / "skills").symlink_to(outside, target_is_directory=True)
except OSError as exc:
pytest.skip(f"directory symlink unavailable: {exc}")
with pytest.raises(SkillManagementError) as exc_info:
delete_webui_skill(workspace, "custom-skill", disabled_skills=set())
assert exc_info.value.status == 403
assert (outside / "custom-skill" / "SKILL.md").is_file()

View File

@ -534,3 +534,29 @@ async def test_install_marketplace_skill_is_idempotent(
"already_installed": True,
"name": "already-here",
}
@pytest.mark.asyncio
async def test_install_marketplace_skill_rejects_symlinked_skills_root(
tmp_path: Path,
) -> None:
workspace = tmp_path / "workspace"
outside = tmp_path / "outside"
workspace.mkdir()
outside.mkdir()
try:
(workspace / "skills").symlink_to(outside, target_is_directory=True)
except OSError as exc:
pytest.skip(f"directory symlink unavailable: {exc}")
with pytest.raises(SkillsMarketplaceError) as exc_info:
await install_marketplace_skill(
"",
"ima-skills",
workspace,
provider="skillhub",
version="1.1.8",
)
assert exc_info.value.status == 403
assert list(outside.iterdir()) == []

View File

@ -485,10 +485,17 @@ function SkillDetailSheet({
type="button"
role="switch"
aria-checked={enabled}
aria-label={t("settings.skills.toggleEnabled", {
name: activeSkill.name,
defaultValue: enabled ? "Disable {{name}}" : "Enable {{name}}",
})}
aria-label={
enabled
? t("settings.skills.disableSkill", {
name: activeSkill.name,
defaultValue: "Disable {{name}}",
})
: t("settings.skills.enableSkill", {
name: activeSkill.name,
defaultValue: "Enable {{name}}",
})
}
disabled={actionBusy}
onClick={() => void toggleEnabled()}
className={cn(

View File

@ -791,6 +791,36 @@
"views": "Skills views",
"installedTab": "Installed",
"discoverTab": "Discover",
"customGroup": "Custom",
"builtinGroup": "Built-in",
"otherGroup": "Other",
"searchInstalled": "Search installed skills",
"filterAll": "All",
"filterEnabled": "Enabled",
"filterDisabled": "Disabled",
"noMatching": "No matching skills.",
"statusDisabled": "Disabled",
"statusEnabled": "Enabled",
"statusNeedsSetup": "Needs setup",
"showLess": "Show less",
"showMore": "Show more",
"enabledControl": "Use this skill",
"enabledDescription": "Allow the agent to load this skill when its requirements are ready.",
"enableSkill": "Enable {{name}}",
"disableSkill": "Disable {{name}}",
"updateFailed": "Could not update this skill.",
"deleteTitle": "Delete skill",
"deleteDescription": "Remove this skill from the current workspace.",
"deleteAction": "Delete",
"deleteFailed": "Could not delete this skill.",
"deleteConfirmTitle": "Delete {{name}}?",
"deleteConfirmDescription": "This removes the skill files from the current workspace. This action cannot be undone.",
"deleteConfirmAction": "Delete skill",
"instructionsTitle": "Skill instructions",
"setupRequired": "Setup required",
"setupDescription": "Install the missing dependency on the machine running nanobot, then check again.",
"copySetupCommand": "Copy setup command",
"checkAgain": "Check again",
"marketplaceSearchFailed": "Could not search skill marketplaces.",
"marketplaceInstallFailed": "Could not install this skill.",
"marketplaceSearchPlaceholder": "Search skills",

View File

@ -778,6 +778,36 @@
"views": "Vistas de habilidades",
"installedTab": "Instaladas",
"discoverTab": "Descubrir",
"customGroup": "Personalizadas",
"builtinGroup": "Integradas",
"otherGroup": "Otras",
"searchInstalled": "Buscar skills instaladas",
"filterAll": "Todas",
"filterEnabled": "Activadas",
"filterDisabled": "Desactivadas",
"noMatching": "No hay skills coincidentes.",
"statusDisabled": "Desactivada",
"statusEnabled": "Activada",
"statusNeedsSetup": "Requiere configuración",
"showLess": "Mostrar menos",
"showMore": "Mostrar más",
"enabledControl": "Usar esta skill",
"enabledDescription": "Permite que el agente cargue esta skill cuando sus requisitos estén listos.",
"enableSkill": "Activar {{name}}",
"disableSkill": "Desactivar {{name}}",
"updateFailed": "No se pudo actualizar esta skill.",
"deleteTitle": "Eliminar skill",
"deleteDescription": "Elimina esta skill del espacio de trabajo actual.",
"deleteAction": "Eliminar",
"deleteFailed": "No se pudo eliminar esta skill.",
"deleteConfirmTitle": "¿Eliminar {{name}}?",
"deleteConfirmDescription": "Esto elimina los archivos de la skill del espacio de trabajo actual. Esta acción no se puede deshacer.",
"deleteConfirmAction": "Eliminar skill",
"instructionsTitle": "Instrucciones de la skill",
"setupRequired": "Requiere configuración",
"setupDescription": "Instala la dependencia que falta en el equipo donde se ejecuta nanobot y vuelve a comprobarlo.",
"copySetupCommand": "Copiar comando de configuración",
"checkAgain": "Comprobar de nuevo",
"marketplaceSearchFailed": "No se pudieron buscar los mercados de skills.",
"marketplaceInstallFailed": "No se pudo instalar este skill.",
"marketplaceSearchPlaceholder": "Buscar skills",

View File

@ -777,6 +777,36 @@
"views": "Vues des compétences",
"installedTab": "Installées",
"discoverTab": "Découvrir",
"customGroup": "Personnalisées",
"builtinGroup": "Intégrées",
"otherGroup": "Autres",
"searchInstalled": "Rechercher les compétences installées",
"filterAll": "Toutes",
"filterEnabled": "Activées",
"filterDisabled": "Désactivées",
"noMatching": "Aucune compétence correspondante.",
"statusDisabled": "Désactivée",
"statusEnabled": "Activée",
"statusNeedsSetup": "Configuration requise",
"showLess": "Afficher moins",
"showMore": "Afficher plus",
"enabledControl": "Utiliser cette compétence",
"enabledDescription": "Autorise lagent à charger cette compétence lorsque ses prérequis sont satisfaits.",
"enableSkill": "Activer {{name}}",
"disableSkill": "Désactiver {{name}}",
"updateFailed": "Impossible de mettre à jour cette compétence.",
"deleteTitle": "Supprimer la compétence",
"deleteDescription": "Supprime cette compétence de lespace de travail actuel.",
"deleteAction": "Supprimer",
"deleteFailed": "Impossible de supprimer cette compétence.",
"deleteConfirmTitle": "Supprimer {{name}} ?",
"deleteConfirmDescription": "Cette action supprime les fichiers de la compétence de lespace de travail actuel et ne peut pas être annulée.",
"deleteConfirmAction": "Supprimer la compétence",
"instructionsTitle": "Instructions de la compétence",
"setupRequired": "Configuration requise",
"setupDescription": "Installez la dépendance manquante sur la machine qui exécute nanobot, puis vérifiez à nouveau.",
"copySetupCommand": "Copier la commande de configuration",
"checkAgain": "Vérifier à nouveau",
"marketplaceSearchFailed": "Impossible de rechercher dans les catalogues de compétences.",
"marketplaceInstallFailed": "Impossible dinstaller cette compétence.",
"marketplaceSearchPlaceholder": "Rechercher des compétences",

View File

@ -777,6 +777,36 @@
"views": "Tampilan skill",
"installedTab": "Terpasang",
"discoverTab": "Temukan",
"customGroup": "Kustom",
"builtinGroup": "Bawaan",
"otherGroup": "Lainnya",
"searchInstalled": "Cari skill terpasang",
"filterAll": "Semua",
"filterEnabled": "Aktif",
"filterDisabled": "Nonaktif",
"noMatching": "Tidak ada skill yang cocok.",
"statusDisabled": "Nonaktif",
"statusEnabled": "Aktif",
"statusNeedsSetup": "Perlu penyiapan",
"showLess": "Tampilkan lebih sedikit",
"showMore": "Tampilkan lebih banyak",
"enabledControl": "Gunakan skill ini",
"enabledDescription": "Izinkan agen memuat skill ini saat persyaratannya terpenuhi.",
"enableSkill": "Aktifkan {{name}}",
"disableSkill": "Nonaktifkan {{name}}",
"updateFailed": "Skill ini tidak dapat diperbarui.",
"deleteTitle": "Hapus skill",
"deleteDescription": "Hapus skill ini dari workspace saat ini.",
"deleteAction": "Hapus",
"deleteFailed": "Skill ini tidak dapat dihapus.",
"deleteConfirmTitle": "Hapus {{name}}?",
"deleteConfirmDescription": "Tindakan ini menghapus file skill dari workspace saat ini dan tidak dapat dibatalkan.",
"deleteConfirmAction": "Hapus skill",
"instructionsTitle": "Petunjuk skill",
"setupRequired": "Perlu penyiapan",
"setupDescription": "Instal dependensi yang belum tersedia di mesin yang menjalankan nanobot, lalu periksa lagi.",
"copySetupCommand": "Salin perintah penyiapan",
"checkAgain": "Periksa lagi",
"marketplaceSearchFailed": "Tidak dapat mencari marketplace skill.",
"marketplaceInstallFailed": "Tidak dapat memasang skill ini.",
"marketplaceSearchPlaceholder": "Cari skill",

View File

@ -777,6 +777,36 @@
"views": "スキル表示",
"installedTab": "インストール済み",
"discoverTab": "見つける",
"customGroup": "カスタム",
"builtinGroup": "組み込み",
"otherGroup": "その他",
"searchInstalled": "インストール済みスキルを検索",
"filterAll": "すべて",
"filterEnabled": "有効",
"filterDisabled": "無効",
"noMatching": "一致するスキルがありません。",
"statusDisabled": "無効",
"statusEnabled": "有効",
"statusNeedsSetup": "セットアップが必要",
"showLess": "折りたたむ",
"showMore": "さらに表示",
"enabledControl": "このスキルを使用",
"enabledDescription": "必要条件が満たされている場合、エージェントがこのスキルを読み込めるようにします。",
"enableSkill": "{{name}} を有効にする",
"disableSkill": "{{name}} を無効にする",
"updateFailed": "このスキルを更新できませんでした。",
"deleteTitle": "スキルを削除",
"deleteDescription": "現在のワークスペースからこのスキルを削除します。",
"deleteAction": "削除",
"deleteFailed": "このスキルを削除できませんでした。",
"deleteConfirmTitle": "{{name}} を削除しますか?",
"deleteConfirmDescription": "現在のワークスペースからスキルファイルを削除します。この操作は元に戻せません。",
"deleteConfirmAction": "スキルを削除",
"instructionsTitle": "スキルの説明",
"setupRequired": "セットアップが必要",
"setupDescription": "nanobot を実行しているマシンに不足している依存関係をインストールしてから、再確認してください。",
"copySetupCommand": "セットアップコマンドをコピー",
"checkAgain": "再確認",
"marketplaceSearchFailed": "スキルマーケットを検索できませんでした。",
"marketplaceInstallFailed": "このスキルをインストールできませんでした。",
"marketplaceSearchPlaceholder": "スキルを検索",

View File

@ -777,6 +777,36 @@
"views": "스킬 보기",
"installedTab": "설치됨",
"discoverTab": "탐색",
"customGroup": "사용자 지정",
"builtinGroup": "기본 제공",
"otherGroup": "기타",
"searchInstalled": "설치된 스킬 검색",
"filterAll": "전체",
"filterEnabled": "활성화됨",
"filterDisabled": "비활성화됨",
"noMatching": "일치하는 스킬이 없습니다.",
"statusDisabled": "비활성화됨",
"statusEnabled": "활성화됨",
"statusNeedsSetup": "설정 필요",
"showLess": "접기",
"showMore": "더 보기",
"enabledControl": "이 스킬 사용",
"enabledDescription": "요구 사항이 준비되면 에이전트가 이 스킬을 불러오도록 허용합니다.",
"enableSkill": "{{name}} 활성화",
"disableSkill": "{{name}} 비활성화",
"updateFailed": "이 스킬을 업데이트할 수 없습니다.",
"deleteTitle": "스킬 삭제",
"deleteDescription": "현재 워크스페이스에서 이 스킬을 제거합니다.",
"deleteAction": "삭제",
"deleteFailed": "이 스킬을 삭제할 수 없습니다.",
"deleteConfirmTitle": "{{name}}을(를) 삭제하시겠습니까?",
"deleteConfirmDescription": "현재 워크스페이스에서 스킬 파일을 제거합니다. 이 작업은 되돌릴 수 없습니다.",
"deleteConfirmAction": "스킬 삭제",
"instructionsTitle": "스킬 안내",
"setupRequired": "설정 필요",
"setupDescription": "nanobot을 실행하는 컴퓨터에 누락된 종속성을 설치한 후 다시 확인하세요.",
"copySetupCommand": "설정 명령 복사",
"checkAgain": "다시 확인",
"marketplaceSearchFailed": "스킬 마켓을 검색할 수 없습니다.",
"marketplaceInstallFailed": "이 스킬을 설치할 수 없습니다.",
"marketplaceSearchPlaceholder": "스킬 검색",

View File

@ -791,6 +791,36 @@
"views": "Visualizações de skills",
"installedTab": "Instaladas",
"discoverTab": "Descobrir",
"customGroup": "Personalizadas",
"builtinGroup": "Integradas",
"otherGroup": "Outras",
"searchInstalled": "Buscar skills instaladas",
"filterAll": "Todas",
"filterEnabled": "Ativadas",
"filterDisabled": "Desativadas",
"noMatching": "Nenhuma skill correspondente.",
"statusDisabled": "Desativada",
"statusEnabled": "Ativada",
"statusNeedsSetup": "Requer configuração",
"showLess": "Mostrar menos",
"showMore": "Mostrar mais",
"enabledControl": "Usar esta skill",
"enabledDescription": "Permite que o agente carregue esta skill quando os requisitos estiverem prontos.",
"enableSkill": "Ativar {{name}}",
"disableSkill": "Desativar {{name}}",
"updateFailed": "Não foi possível atualizar esta skill.",
"deleteTitle": "Excluir skill",
"deleteDescription": "Remove esta skill do workspace atual.",
"deleteAction": "Excluir",
"deleteFailed": "Não foi possível excluir esta skill.",
"deleteConfirmTitle": "Excluir {{name}}?",
"deleteConfirmDescription": "Isso remove os arquivos da skill do workspace atual. Esta ação não pode ser desfeita.",
"deleteConfirmAction": "Excluir skill",
"instructionsTitle": "Instruções da skill",
"setupRequired": "Requer configuração",
"setupDescription": "Instale a dependência ausente na máquina que executa o nanobot e verifique novamente.",
"copySetupCommand": "Copiar comando de configuração",
"checkAgain": "Verificar novamente",
"marketplaceSearchFailed": "Não foi possível pesquisar nos mercados de skills.",
"marketplaceInstallFailed": "Não foi possível instalar esta skill.",
"marketplaceSearchPlaceholder": "Pesquisar skills",

View File

@ -777,6 +777,36 @@
"views": "Chế độ xem kỹ năng",
"installedTab": "Đã cài đặt",
"discoverTab": "Khám phá",
"customGroup": "Tùy chỉnh",
"builtinGroup": "Tích hợp sẵn",
"otherGroup": "Khác",
"searchInstalled": "Tìm skill đã cài đặt",
"filterAll": "Tất cả",
"filterEnabled": "Đã bật",
"filterDisabled": "Đã tắt",
"noMatching": "Không có skill phù hợp.",
"statusDisabled": "Đã tắt",
"statusEnabled": "Đã bật",
"statusNeedsSetup": "Cần thiết lập",
"showLess": "Thu gọn",
"showMore": "Hiển thị thêm",
"enabledControl": "Sử dụng skill này",
"enabledDescription": "Cho phép agent tải skill này khi các yêu cầu đã sẵn sàng.",
"enableSkill": "Bật {{name}}",
"disableSkill": "Tắt {{name}}",
"updateFailed": "Không thể cập nhật skill này.",
"deleteTitle": "Xóa skill",
"deleteDescription": "Xóa skill này khỏi workspace hiện tại.",
"deleteAction": "Xóa",
"deleteFailed": "Không thể xóa skill này.",
"deleteConfirmTitle": "Xóa {{name}}?",
"deleteConfirmDescription": "Thao tác này xóa các tệp skill khỏi workspace hiện tại và không thể hoàn tác.",
"deleteConfirmAction": "Xóa skill",
"instructionsTitle": "Hướng dẫn skill",
"setupRequired": "Cần thiết lập",
"setupDescription": "Cài đặt phần phụ thuộc còn thiếu trên máy chạy nanobot rồi kiểm tra lại.",
"copySetupCommand": "Sao chép lệnh thiết lập",
"checkAgain": "Kiểm tra lại",
"marketplaceSearchFailed": "Không thể tìm kiếm các kho kỹ năng.",
"marketplaceInstallFailed": "Không thể cài đặt kỹ năng này.",
"marketplaceSearchPlaceholder": "Tìm kiếm kỹ năng",

View File

@ -791,6 +791,36 @@
"views": "技能视图",
"installedTab": "已安装",
"discoverTab": "发现",
"customGroup": "自定义",
"builtinGroup": "内置",
"otherGroup": "其他",
"searchInstalled": "搜索已安装技能",
"filterAll": "全部",
"filterEnabled": "已启用",
"filterDisabled": "已停用",
"noMatching": "没有匹配的技能。",
"statusDisabled": "已停用",
"statusEnabled": "已启用",
"statusNeedsSetup": "需要设置",
"showLess": "收起",
"showMore": "展开",
"enabledControl": "使用此技能",
"enabledDescription": "当技能需求满足时,允许 agent 加载并使用它。",
"enableSkill": "启用 {{name}}",
"disableSkill": "停用 {{name}}",
"updateFailed": "无法更新此技能。",
"deleteTitle": "删除技能",
"deleteDescription": "从当前工作区移除此技能。",
"deleteAction": "删除",
"deleteFailed": "无法删除此技能。",
"deleteConfirmTitle": "删除 {{name}}",
"deleteConfirmDescription": "这会从当前工作区移除该技能的文件,且无法撤销。",
"deleteConfirmAction": "删除技能",
"instructionsTitle": "技能说明",
"setupRequired": "需要设置",
"setupDescription": "请在运行 nanobot 的设备上安装缺少的依赖,然后重新检查。",
"copySetupCommand": "复制设置命令",
"checkAgain": "重新检查",
"marketplaceSearchFailed": "暂时无法搜索技能市场。",
"marketplaceInstallFailed": "无法安装此技能。",
"marketplaceSearchPlaceholder": "搜索技能",

View File

@ -777,6 +777,36 @@
"views": "技能檢視",
"installedTab": "已安裝",
"discoverTab": "探索",
"customGroup": "自訂",
"builtinGroup": "內建",
"otherGroup": "其他",
"searchInstalled": "搜尋已安裝技能",
"filterAll": "全部",
"filterEnabled": "已啟用",
"filterDisabled": "已停用",
"noMatching": "沒有相符的技能。",
"statusDisabled": "已停用",
"statusEnabled": "已啟用",
"statusNeedsSetup": "需要設定",
"showLess": "收合",
"showMore": "展開",
"enabledControl": "使用此技能",
"enabledDescription": "當技能需求已滿足時,允許 agent 載入並使用它。",
"enableSkill": "啟用 {{name}}",
"disableSkill": "停用 {{name}}",
"updateFailed": "無法更新此技能。",
"deleteTitle": "刪除技能",
"deleteDescription": "從目前工作區移除此技能。",
"deleteAction": "刪除",
"deleteFailed": "無法刪除此技能。",
"deleteConfirmTitle": "刪除 {{name}}",
"deleteConfirmDescription": "這會從目前工作區移除該技能的檔案,且無法復原。",
"deleteConfirmAction": "刪除技能",
"instructionsTitle": "技能說明",
"setupRequired": "需要設定",
"setupDescription": "請在執行 nanobot 的裝置上安裝缺少的相依套件,然後重新檢查。",
"copySetupCommand": "複製設定指令",
"checkAgain": "重新檢查",
"marketplaceSearchFailed": "暫時無法搜尋技能市集。",
"marketplaceInstallFailed": "無法安裝此技能。",
"marketplaceSearchPlaceholder": "搜尋技能",

View File

@ -78,6 +78,36 @@ const LOCALIZED_SETTINGS_COPY_KEYS = [
"settings.skills.views",
"settings.skills.installedTab",
"settings.skills.discoverTab",
"settings.skills.customGroup",
"settings.skills.builtinGroup",
"settings.skills.otherGroup",
"settings.skills.searchInstalled",
"settings.skills.filterAll",
"settings.skills.filterEnabled",
"settings.skills.filterDisabled",
"settings.skills.noMatching",
"settings.skills.statusDisabled",
"settings.skills.statusEnabled",
"settings.skills.statusNeedsSetup",
"settings.skills.showLess",
"settings.skills.showMore",
"settings.skills.enabledControl",
"settings.skills.enabledDescription",
"settings.skills.enableSkill",
"settings.skills.disableSkill",
"settings.skills.updateFailed",
"settings.skills.deleteTitle",
"settings.skills.deleteDescription",
"settings.skills.deleteAction",
"settings.skills.deleteFailed",
"settings.skills.deleteConfirmTitle",
"settings.skills.deleteConfirmDescription",
"settings.skills.deleteConfirmAction",
"settings.skills.instructionsTitle",
"settings.skills.setupRequired",
"settings.skills.setupDescription",
"settings.skills.copySetupCommand",
"settings.skills.checkAgain",
"settings.skills.marketplaceSearchFailed",
"settings.skills.marketplaceInstallFailed",
"settings.skills.marketplaceSearchPlaceholder",