mirror of
https://github.com/HKUDS/nanobot.git
synced 2026-08-04 08:28:36 +00:00
fix(webui): harden skill management
This commit is contained in:
parent
d276fe1386
commit
8a56eb06ad
@ -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")
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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()) == []
|
||||
|
||||
@ -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(
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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 l’agent à 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 l’espace 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 l’espace 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 d’installer cette compétence.",
|
||||
"marketplaceSearchPlaceholder": "Rechercher des compétences",
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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": "スキルを検索",
|
||||
|
||||
@ -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": "스킬 검색",
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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": "搜索技能",
|
||||
|
||||
@ -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": "搜尋技能",
|
||||
|
||||
@ -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",
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user