feat: 移除backend-old目录中的废弃文件

移除以下文件:
- .dockerignore 和 .gitignore 配置文件
- A2A_Multiagent_change.md 设计文档
- COMMUNICATION.md 通讯信息文档
- Dockerfile 构建配置
- LICENSE 许可证文件

这些文件属于旧版本后端代码,不再需要维护。
This commit is contained in:
2026-05-14 17:19:25 +08:00
parent ebfa242862
commit b59968167e
312 changed files with 0 additions and 53485 deletions

View File

@ -1,262 +0,0 @@
"""Import no-credential Hermes Agent skills into Beaver."""
from __future__ import annotations
from dataclasses import dataclass, field
from datetime import datetime, timezone
import json
import re
import shutil
from pathlib import Path
from typing import Any
from beaver.skills.catalog.utils import parse_frontmatter, strip_frontmatter
from beaver.skills.specs import SkillSpec, SkillSpecStore, SkillVersion
from beaver.skills.specs.serialization import canonical_hash, normalize_frontmatter, summarize_skill_content
HERMES_REPO_URL = "https://github.com/NousResearch/hermes-agent"
_CREDENTIAL_PATTERNS = [
re.compile(pattern, re.IGNORECASE)
for pattern in [
r"\bapi[_ -]?key\b",
r"\boauth\b",
r"\bbearer\s+token\b",
r"\baccess[_ -]?token\b",
r"\bclient[_ -]?secret\b",
r"\bsecret\b",
r"\bcredential",
r"\bspotify\b",
r"\bdiscord\b",
r"\bfeishu\b",
r"\bhome\s*assistant\b",
r"\bfal\b",
r"\bopenrouter\b",
r"\bwandb\b",
]
]
@dataclass(slots=True)
class HermesMigrationService:
store: SkillSpecStore
manifest_path: Path | None = None
included_tools: list[dict[str, Any]] = field(default_factory=list)
skipped_tools: list[dict[str, Any]] = field(default_factory=list)
def migrate(
self,
repo_path: str | Path,
*,
include_optional: bool = True,
dry_run: bool = False,
) -> dict[str, Any]:
repo = Path(repo_path)
if not repo.exists():
raise ValueError(f"Hermes repository not found: {repo}")
skill_files = self._discover_skill_files(repo, include_optional=include_optional)
included: list[dict[str, Any]] = []
skipped: list[dict[str, Any]] = []
for skill_file in skill_files:
result = self._migrate_skill(repo, skill_file, dry_run=dry_run)
if result["status"] in {"included", "unchanged"}:
included.append(result)
else:
skipped.append(result)
manifest = {
"source": "hermes-agent",
"repo_url": HERMES_REPO_URL,
"repo_path": str(repo),
"generated_at": datetime.now(timezone.utc).isoformat(),
"dry_run": dry_run,
"included": included,
"skipped": skipped,
"tools": self._tool_manifest(),
}
path = self.manifest_path or (self.store.workspace / "hermes_migration_manifest.json")
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(json.dumps(manifest, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
return manifest
def _discover_skill_files(self, repo: Path, *, include_optional: bool) -> list[Path]:
roots = [repo / "skills"]
if include_optional:
roots.append(repo / "optional-skills")
files: list[Path] = []
for root in roots:
if root.exists():
files.extend(sorted(root.glob("**/SKILL.md")))
return files
def _migrate_skill(self, repo: Path, skill_file: Path, *, dry_run: bool) -> dict[str, Any]:
relative = skill_file.relative_to(repo)
content = skill_file.read_text(encoding="utf-8")
frontmatter, body = parse_frontmatter(content)
skill_name = _safe_skill_name(str(frontmatter.get("name") or skill_file.parent.name))
if not skill_name:
return _skip(relative, "unsafe_skill_name")
credential_reason = _credential_reason(content)
if credential_reason:
return _skip(relative, credential_reason, skill_name=skill_name)
normalized = normalize_frontmatter(
{
**frontmatter,
"name": skill_name,
"description": frontmatter.get("description") or skill_name,
}
)
rendered = _render_skill_content(normalized, body)
content_hash = canonical_hash(rendered)
existing = self.store.read_published_skill(skill_name)
existing_spec = self.store.get_skill_spec(skill_name)
if existing is not None and existing.version.content_hash == content_hash:
return {
"status": "unchanged",
"skill_name": skill_name,
"version": existing.version.version,
"path": str(relative),
"reason": "same_content_hash",
}
next_version = self._next_version(skill_name)
if dry_run:
return {
"status": "included",
"skill_name": skill_name,
"version": next_version,
"path": str(relative),
"dry_run": True,
}
now = datetime.now(timezone.utc).isoformat()
skill_version = SkillVersion(
skill_name=skill_name,
version=next_version,
content_hash=content_hash,
summary_hash=canonical_hash(strip_frontmatter(rendered).strip()),
created_at=now,
created_by="hermes_migration",
change_reason=f"Import Hermes skill {relative}",
parent_version=existing.version.version if existing is not None else None,
review_state="published",
frontmatter=normalized,
summary=summarize_skill_content(body),
tool_hints=self.store._extract_tool_hints(normalized),
provenance={
"source": "hermes-agent",
"repo_url": HERMES_REPO_URL,
"repo_path": str(repo),
"relative_path": str(relative),
},
)
self.store.write_skill_version(skill_version, rendered)
self._copy_supporting_files(skill_file.parent, skill_name, next_version)
spec = existing_spec or SkillSpec(
name=skill_name,
display_name=skill_name,
description=str(normalized.get("description") or skill_name),
created_at=now,
updated_at=now,
current_version=next_version,
status="active",
tags=[],
owners=["hermes-agent"],
source_kind="hermes-agent",
lineage=[],
)
spec.current_version = next_version
spec.updated_at = now
spec.status = "active"
spec.source_kind = "hermes-agent"
if "hermes-agent" not in spec.owners:
spec.owners.append("hermes-agent")
self.store.write_skill_spec(spec)
self.store.set_current_version(skill_name, next_version)
published = self.store.read_index("published")
if skill_name not in published:
published.append(skill_name)
self.store.update_index("published", published)
return {
"status": "included",
"skill_name": skill_name,
"version": next_version,
"path": str(relative),
}
def _copy_supporting_files(self, source_dir: Path, skill_name: str, version: str) -> None:
target_root = self.store.root / skill_name / "versions" / version
for source in sorted(source_dir.rglob("*")):
if not source.is_file() or source.name == "SKILL.md" or source.is_symlink():
continue
relative = source.relative_to(source_dir)
if any(part in {"", ".", ".."} for part in relative.parts):
continue
target = target_root / relative
target.parent.mkdir(parents=True, exist_ok=True)
shutil.copyfile(source, target)
def _next_version(self, skill_name: str) -> str:
versions = [item for item in self.store.list_versions(skill_name) if item.startswith("v")]
numbers = [int(item[1:]) for item in versions if item[1:].isdigit()]
return f"v{(max(numbers) if numbers else 0) + 1:04d}"
def _tool_manifest(self) -> dict[str, list[dict[str, Any]]]:
included = self.included_tools or [
{"name": "todo", "reason": "implemented_builtin_no_api"},
{"name": "clarify", "reason": "implemented_builtin_no_api"},
{"name": "delegate", "reason": "implemented_builtin_no_api"},
{"name": "spawn", "reason": "implemented_builtin_no_api"},
{"name": "skills_list", "reason": "implemented_builtin_no_api"},
{"name": "skill_manage", "reason": "implemented_builtin_no_api"},
{"name": "terminal", "reason": "implemented_builtin_no_api"},
{"name": "process", "reason": "implemented_builtin_no_api"},
{"name": "patch", "reason": "implemented_builtin_no_api"},
{"name": "write_file", "reason": "implemented_builtin_no_api"},
{"name": "web_fetch", "reason": "implemented_builtin_no_api"},
{"name": "web_search", "reason": "implemented_builtin_no_api"},
{"name": "execute_code", "reason": "implemented_builtin_no_api"},
]
skipped = self.skipped_tools or [
{"name": "spotify", "reason": "requires_oauth"},
{"name": "discord", "reason": "requires_external_token"},
{"name": "feishu", "reason": "requires_external_token"},
{"name": "home_assistant", "reason": "requires_external_service_credentials"},
{"name": "fal_image_generation", "reason": "requires_api_key"},
{"name": "remote_web_providers", "reason": "requires_api_key_or_oauth"},
]
return {"included": included, "skipped": skipped}
def _credential_reason(content: str) -> str | None:
for pattern in _CREDENTIAL_PATTERNS:
if pattern.search(content):
return "requires_external_credentials"
return None
def _safe_skill_name(value: str) -> str:
cleaned = value.strip().replace(" ", "-")
if not cleaned or cleaned in {".", ".."} or "/" in cleaned or "\\" in cleaned:
return ""
if not re.fullmatch(r"[A-Za-z0-9_.-]+", cleaned):
return ""
return cleaned
def _skip(relative: Path, reason: str, *, skill_name: str | None = None) -> dict[str, Any]:
result = {"status": "skipped", "path": str(relative), "reason": reason}
if skill_name:
result["skill_name"] = skill_name
return result
def _render_skill_content(frontmatter: dict[str, Any], body: str) -> str:
lines = ["---"]
for key, value in normalize_frontmatter(frontmatter).items():
if isinstance(value, list):
lines.append(f"{key}:")
for item in value:
lines.append(f" - {item}")
else:
lines.append(f"{key}: {value}")
lines.extend(["---", "", body.strip()])
return "\n".join(lines).rstrip() + "\n"

View File

@ -1,208 +0,0 @@
"""Import legacy and staged skills into the Beaver SkillSpecStore."""
from __future__ import annotations
from dataclasses import dataclass
from datetime import datetime, timezone
import io
import json
import re
import zipfile
from pathlib import Path
from typing import Any
from beaver.skills.catalog.utils import parse_frontmatter, strip_frontmatter
from beaver.skills.specs import SkillSpec, SkillSpecStore, SkillVersion
from beaver.skills.specs.serialization import canonical_hash, normalize_frontmatter, summarize_skill_content
@dataclass(slots=True)
class SkillMigrationService:
store: SkillSpecStore
repo_root: Path | None = None
def migrate_all(self) -> dict[str, Any]:
included: list[dict[str, Any]] = []
skipped: list[dict[str, Any]] = []
for path in self._backend_old_skills():
self._migrate_skill_file(path, "backend-old", included, skipped)
for path in self._staged_skills():
self._migrate_skill_file(path, "stevenli-staged", included, skipped)
for path in self._skill_zips():
self._migrate_zip(path, included, skipped)
manifest = {
"generated_at": _now(),
"workspace": str(self.store.workspace),
"included": included,
"skipped": skipped,
}
manifest_path = self.store.workspace / "skill_migration_manifest.json"
manifest_path.write_text(json.dumps(manifest, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
return manifest
def _backend_old_skills(self) -> list[Path]:
root = self._repo_root() / "app-instance" / "backend-old" / "nanobot" / "skills"
if not root.exists():
return []
return sorted(root.glob("*/SKILL.md"))
def _staged_skills(self) -> list[Path]:
root = self.store.workspace / "state" / "skill-reviews"
if not root.exists():
return []
return sorted(root.glob("*/staged/*/SKILL.md"))
def _skill_zips(self) -> list[Path]:
root = self.store.workspace / "skills"
if not root.exists():
return []
return sorted(root.glob("*.zip"))
def _repo_root(self) -> Path:
if self.repo_root is not None:
return self.repo_root
return Path(__file__).resolve().parents[4]
def _migrate_skill_file(self, path: Path, source: str, included: list[dict[str, Any]], skipped: list[dict[str, Any]]) -> None:
try:
content = path.read_text(encoding="utf-8")
result = self._publish_content(content, source=source, source_path=str(path))
included.append(result)
except Exception as exc:
skipped.append({"source": source, "source_path": str(path), "reason": str(exc)})
def _migrate_zip(self, path: Path, included: list[dict[str, Any]], skipped: list[dict[str, Any]]) -> None:
try:
with zipfile.ZipFile(io.BytesIO(path.read_bytes()), "r") as archive:
entries = [info for info in archive.infolist() if not info.is_dir()]
skill_entry = _find_skill_entry(entries)
content = archive.read(skill_entry).decode("utf-8", errors="replace")
result = self._publish_content(content, source="stevenli-zip", source_path=str(path))
skill_name = result["skill_name"]
version = result["version"]
top = Path(skill_entry).parts[0] if len(Path(skill_entry).parts) == 2 else ""
for info in entries:
raw = info.filename.replace("\\", "/")
if raw == skill_entry or raw.startswith("/") or "__MACOSX" in Path(raw).parts:
continue
parts = Path(raw).parts
rel_parts = parts[1:] if top and parts and parts[0] == top else parts
if not rel_parts or any(part in {"", ".", ".."} for part in rel_parts):
continue
target = self.store.root / skill_name / "versions" / version / "/".join(rel_parts)
target.parent.mkdir(parents=True, exist_ok=True)
target.write_bytes(archive.read(info))
included.append(result)
except Exception as exc:
skipped.append({"source": "stevenli-zip", "source_path": str(path), "reason": str(exc)})
def _publish_content(self, content: str, *, source: str, source_path: str) -> dict[str, Any]:
frontmatter, body = parse_frontmatter(content)
skill_name = _safe_name(str(frontmatter.get("name") or Path(source_path).parent.name))
if not skill_name:
raise ValueError("unsafe or missing skill name")
normalized = normalize_frontmatter(
{
**frontmatter,
"name": skill_name,
"description": frontmatter.get("description") or skill_name,
}
)
rendered = _render_skill_content(normalized, body)
content_hash = canonical_hash(rendered)
existing = self.store.read_published_skill(skill_name)
if existing is not None and existing.version.content_hash == content_hash:
return {
"status": "unchanged",
"skill_name": skill_name,
"version": existing.version.version,
"source": source,
"source_path": source_path,
}
version_id = self._next_version(skill_name)
now = _now()
skill_version = SkillVersion(
skill_name=skill_name,
version=version_id,
content_hash=content_hash,
summary_hash=canonical_hash(strip_frontmatter(rendered).strip()),
created_at=now,
created_by="migration",
change_reason=f"Import skill from {source}",
parent_version=existing.version.version if existing is not None else None,
review_state="published",
frontmatter=normalized,
summary=summarize_skill_content(body),
tool_hints=self.store._extract_tool_hints(normalized),
provenance={"source": source, "source_path": source_path, "imported_at": now},
)
self.store.write_skill_version(skill_version, rendered)
spec = self.store.get_skill_spec(skill_name) or SkillSpec(
name=skill_name,
display_name=skill_name,
description=str(normalized.get("description") or skill_name),
created_at=now,
updated_at=now,
current_version=version_id,
status="active",
tags=[],
owners=["migration"],
source_kind=source,
lineage=[],
)
spec.current_version = version_id
spec.updated_at = now
spec.status = "active"
spec.source_kind = source
if "migration" not in spec.owners:
spec.owners.append("migration")
self.store.write_skill_spec(spec)
self.store.set_current_version(skill_name, version_id)
published = self.store.read_index("published")
if skill_name not in published:
published.append(skill_name)
self.store.update_index("published", published)
return {"status": "included", "skill_name": skill_name, "version": version_id, "source": source, "source_path": source_path}
def _next_version(self, skill_name: str) -> str:
versions = [item for item in self.store.list_versions(skill_name) if item.startswith("v")]
numbers = [int(item[1:]) for item in versions if item[1:].isdigit()]
return f"v{(max(numbers) if numbers else 0) + 1:04d}"
def _find_skill_entry(entries: list[zipfile.ZipInfo]) -> str:
candidates = []
for info in entries:
raw = info.filename.replace("\\", "/")
parts = Path(raw).parts
if raw.startswith("/") or any(part in {"", ".", ".."} for part in parts):
raise ValueError(f"unsafe archive entry: {info.filename}")
if parts and parts[-1] == "SKILL.md" and len(parts) in (1, 2):
candidates.append(raw)
if not candidates:
raise ValueError("zip has no root SKILL.md")
return candidates[0]
def _safe_name(value: str) -> str:
cleaned = value.strip().replace(" ", "-")
if not cleaned or cleaned in {".", ".."} or "/" in cleaned or "\\" in cleaned:
return ""
return cleaned if re.fullmatch(r"[A-Za-z0-9_.-]+", cleaned) else ""
def _render_skill_content(frontmatter: dict[str, Any], body: str) -> str:
lines = ["---"]
for key, value in normalize_frontmatter(frontmatter).items():
if isinstance(value, list):
lines.append(f"{key}:")
for item in value:
lines.append(f" - {item}")
else:
lines.append(f"{key}: {value}")
lines.extend(["---", "", body.strip()])
return "\n".join(lines).rstrip() + "\n"
def _now() -> str:
return datetime.now(timezone.utc).isoformat()