From 3b0af173cc048539c9349cac39a39d35b1a1817a Mon Sep 17 00:00:00 2001
From: steven_li
Date: Thu, 14 May 2026 17:20:32 +0800
Subject: [PATCH] =?UTF-8?q?refactor(beaver):=20=E7=A7=BB=E9=99=A4Hermes?=
=?UTF-8?q?=E7=9B=B8=E5=85=B3=E5=BC=95=E7=94=A8=E5=92=8C=E8=BF=81=E7=A7=BB?=
=?UTF-8?q?=E4=BB=A3=E7=A0=81=EF=BC=8C=E5=AE=8C=E5=96=84Beaver=E5=90=8E?=
=?UTF-8?q?=E7=AB=AF=E4=B8=BB=E7=BA=BF=E5=AE=9E=E7=8E=B0?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
移除了所有Hermes相关的命名引用,包括:
- 从.gitignore中清理相关构建缓存文件
- 将README中的beaver-home路径配置更新
- 完善backend/README.md文档说明Beaver后端主线实现
- 移除Hermes风格的相关注释和兼容性代码
- 清理nanobot环境变量兼容性处理
- 删除技能迁移和服务迁移相关功能代码
- 更新测试用例中相关命名和函数名
BREAKING CHANGE: 移除了Hermes迁移相关API和CLI命令,不再支持nanobot环境变量兼容性
---
.gitignore | 23 +
README.md | 2 +-
app-instance/backend/README.md | 13 +-
.../backend/beaver/engine/context/builder.py | 10 +-
.../beaver/engine/providers/runtime.py | 4 +-
.../backend/beaver/engine/session/store.py | 2 +-
.../beaver/foundation/config/loader.py | 14 +-
.../backend/beaver/foundation/models/cron.py | 5 +-
.../beaver/integrations/outlook/__init__.py | 18 +-
.../backend/beaver/interfaces/cli/main.py | 24 -
.../beaver/interfaces/mcp/tools_server.py | 2 +-
.../backend/beaver/interfaces/web/app.py | 71 +-
.../backend/beaver/memory/curated/store.py | 2 +-
.../backend/beaver/services/cron_service.py | 7 +-
.../backend/beaver/services/memory_service.py | 2 +-
.../backend/beaver/skills/catalog/loader.py | 2 +-
.../backend/beaver/skills/catalog/utils.py | 6 +-
app-instance/backend/beaver/tools/base.py | 15 +-
.../backend/beaver/tools/builtins/cron.py | 2 +-
.../backend/beaver/tools/builtins/memory.py | 2 +-
.../beaver/tools/builtins/session_search.py | 4 +-
.../beaver/tools/builtins/skill_view.py | 2 +-
.../backend/tests/unit/test_config_loader.py | 34 +-
.../backend/tests/unit/test_cron_service.py | 2 +-
.../tests/unit/test_marketplace_and_mcp.py | 23 -
app-instance/frontend/README.md | 13 +-
.../notifications/[scheduledRunId]/page.tsx | 1 -
app-instance/frontend/app/(app)/page.tsx | 14 -
.../frontend/app/(app)/skills/page.tsx | 10 -
.../frontend/app/(app)/status/page.tsx | 82 +-
.../app/(app)/tasks/[taskId]/page.tsx | 55 +-
.../frontend/app/(app)/tasks/page.tsx | 4 +-
app-instance/frontend/app/handoff/page.tsx | 2 +-
.../frontend/components/AppRuntimeBridge.tsx | 12 +-
app-instance/frontend/components/Header.tsx | 18 +-
.../chat-workbench/AgentTeamBlock.tsx | 15 +-
.../chat-workbench/ChatWorkbench.tsx | 3 -
.../components/chat-workbench/MessageList.tsx | 3 -
.../task-runtime/TaskRuntimeShared.tsx | 26 +-
app-instance/frontend/lib/api.ts | 207 +-
app-instance/frontend/lib/i18n/common.ts | 12 +-
app-instance/frontend/lib/i18n/core.ts | 4 +-
app-instance/frontend/lib/store.ts | 10 +-
.../frontend/lib/task-runtime.test.ts | 130 +-
app-instance/frontend/lib/task-runtime.ts | 400 +--
app-instance/frontend/next.config.js | 8 -
app-instance/frontend/package-lock.json | 2888 +----------------
app-instance/frontend/package.json | 47 +-
app-instance/frontend/types/index.ts | 78 +-
app-instance/instance-registry.py | 2 -
.../src/app/api/runtime/login/route.ts | 2 +-
.../src/app/api/runtime/register/route.ts | 2 +-
auth-portal/src/lib/i18n/core.ts | 4 +-
auth-portal/src/package-lock.json | 4 +-
auth-portal/src/package.json | 3 +-
authz-service/README.md | 2 +-
deploy-control/server.py | 2 +-
57 files changed, 245 insertions(+), 4109 deletions(-)
diff --git a/.gitignore b/.gitignore
index d7b421f..76a33e2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,12 +1,35 @@
# Runtime data generated by local Docker deployment
authz-service/runtime/data/
+authz-service/src/data/
app-instance/runtime/instances/
app-instance/runtime/registry/
router-proxy/runtime/conf.d/
+runtime/
+sessions/
+**/sessions/state.db
+**/runtime/**/*.lock
# Local build / cache artifacts
**/__pycache__/
**/.pytest_cache/
**/node_modules/
**/.next/
+**/.next-dev/
+**/.turbo/
+**/.ruff_cache/
+**/.mypy_cache/
+**/.cache/
+**/.venv/
+**/dist/
+**/build/
+**/*.egg-info/
+**/tsconfig.tsbuildinfo
*.log
+*.tmp
+*.py[cod]
+
+# Local secrets / env files
+.env
+*.env
+*.pem
+app-instance/frontend/.env_prod
diff --git a/README.md b/README.md
index c945db8..7c5c634 100644
--- a/README.md
+++ b/README.md
@@ -377,7 +377,7 @@ http://alice.203.0.113.10.nip.io:8088
写到每个实例自己的:
```text
-app-instance/runtime/instances//nanobot-home/config.json
+app-instance/runtime/instances//beaver-home/config.json
```
不是写在 AuthZ 的某个 setting 里。
diff --git a/app-instance/backend/README.md b/app-instance/backend/README.md
index 8746639..c115a1d 100644
--- a/app-instance/backend/README.md
+++ b/app-instance/backend/README.md
@@ -1,8 +1,6 @@
# Beaver Backend
-这是新的 `Beaver` 后端。
-
-旧实现已保留在 [backend-old](/home/ivan/xuan/nano_project/app-instance/backend-old),新目录用于按 [change.md](/home/ivan/xuan/nano_project/app-instance/backend/change.md) 的蓝图逐步重建后端。
+这是 `Beaver` 后端。
当前已经落地的主线:
@@ -28,11 +26,4 @@
## 说明
-这个目录已经不是空骨架,但仍不等于完成迁移。
-
-后续迁移原则:
-
-1. 不再新增 `nanobot` 命名。
-2. 不在新目录中保留 `third_party/`。
-3. 所有 agent 最终都复用 `beaver.engine`。
-4. 高级 team 策略先编译成 Beaver 自有 `ExecutionGraph`,不直接暴露 swarms runtime。
+后端已切到 Beaver 主线,不再保留旧实现、vendored 第三方 runtime 或迁移期旧命名兼容入口。所有 agent 运行都复用 `beaver.engine`,多 agent 协调通过 Beaver 自有 coordinator 和 `ExecutionGraph` 表达。
diff --git a/app-instance/backend/beaver/engine/context/builder.py b/app-instance/backend/beaver/engine/context/builder.py
index c09e2d5..e365897 100644
--- a/app-instance/backend/beaver/engine/context/builder.py
+++ b/app-instance/backend/beaver/engine/context/builder.py
@@ -16,7 +16,7 @@
1. 先服务单 agent 主链
2. 先支持 frozen curated memory,而不是 live memory
-3. skills 按 Hermes 风格支持“显式激活消息注入”,不在这里做磁盘扫描
+3. skills 通过显式激活消息注入,不在这里做磁盘扫描
4. 为后续 channel / gateway / team metadata 预留注入位,但不提前做复杂逻辑
"""
@@ -45,7 +45,7 @@ class SkillContext:
- `name`:用于生成激活提示
- `content`:skill 的完整正文
- 注意:按当前 Hermes 风格实现,skill 正文不再塞进 system prompt,而是转成显式消息注入。
+ 注意:skill 正文不再塞进 system prompt,而是转成显式消息注入。
"""
name: str
@@ -151,7 +151,7 @@ class ContextBuilder:
- 身份与总规则要最靠前
- session/execution 是本轮运行语境,优先级高于长期记忆
- memory 必须是 frozen snapshot,避免中途写 memory 后 prompt 失真
- - activated skill 正文按 Hermes 风格放到显式消息里,避免 system prompt 持续膨胀
+ - activated skill 正文放到显式消息里,避免 system prompt 持续膨胀
"""
sections: list[str] = [BEAVER_USER_ASSISTANT_IDENTITY_PROMPT]
@@ -190,7 +190,7 @@ class ContextBuilder:
这里做三件事:
1. 先生成最终 system prompt
- 2. 按 Hermes 风格,把已激活 skill 的完整正文作为显式消息注入
+ 2. 把已激活 skill 的完整正文作为显式消息注入
3. 把历史消息按原顺序接到后面
4. 如果存在当前用户输入,则把本轮输入追加为最后一条 user message
@@ -348,7 +348,7 @@ class ContextBuilder:
return "# Current Session\n\n" + "\n".join(rows)
def build_skill_activation_messages(self, activated_skills: list[SkillContext]) -> list[dict[str, str]]:
- """按 Hermes 风格把已激活 skill 转成显式消息。
+ """把已激活 skill 转成显式消息。
关键区别:
- system prompt 只保留轻量 skills index
diff --git a/app-instance/backend/beaver/engine/providers/runtime.py b/app-instance/backend/beaver/engine/providers/runtime.py
index baf59b8..7ac8e3a 100644
--- a/app-instance/backend/beaver/engine/providers/runtime.py
+++ b/app-instance/backend/beaver/engine/providers/runtime.py
@@ -1,4 +1,4 @@
-"""Hermes 风格的 provider runtime resolution。"""
+"""Provider runtime resolution for Beaver."""
from __future__ import annotations
@@ -165,7 +165,7 @@ def resolve_fallback_runtime(
) -> ProviderRuntime | None:
"""把 fallback 配置解析成独立 runtime。
- Hermes 的 fallback 是“主 provider 失败后切换到另一个 provider:model”。
+ fallback 的语义是“主 provider 失败后切换到另一个 provider:model”。
这里先把 fallback 解析独立出来,具体何时激活交给上层 chain/factory。
"""
diff --git a/app-instance/backend/beaver/engine/session/store.py b/app-instance/backend/beaver/engine/session/store.py
index 853b1f5..68865fb 100644
--- a/app-instance/backend/beaver/engine/session/store.py
+++ b/app-instance/backend/beaver/engine/session/store.py
@@ -1,6 +1,6 @@
"""Beaver session 子系统的 SQLite 存储实现。
-设计来源主要参考 Hermes-agent:
+设计目标:
1. SQLite 作为统一 session/transcript backend
2. WAL 模式支持多读单写
3. FTS5 支持跨 session 文本检索
diff --git a/app-instance/backend/beaver/foundation/config/loader.py b/app-instance/backend/beaver/foundation/config/loader.py
index 5d40dec..ce75a8b 100644
--- a/app-instance/backend/beaver/foundation/config/loader.py
+++ b/app-instance/backend/beaver/foundation/config/loader.py
@@ -35,14 +35,12 @@ def default_config_path(*, workspace: str | Path | None = None) -> Path:
Priority:
1. `BEAVER_CONFIG_PATH`
- 2. `NANOBOT_CONFIG_PATH` for compatibility during migration
- 3. `BEAVER_HOME/config.json`
- 4. `NANOBOT_HOME/config.json` for migration compatibility
- 5. `/.beaver/config.json`
- 6. `./.beaver/config.json`
+ 2. `BEAVER_HOME/config.json`
+ 3. `/.beaver/config.json`
+ 4. `./.beaver/config.json`
"""
- explicit = os.getenv("BEAVER_CONFIG_PATH") or os.getenv("NANOBOT_CONFIG_PATH")
+ explicit = os.getenv("BEAVER_CONFIG_PATH")
if explicit:
return Path(explicit).expanduser()
@@ -50,10 +48,6 @@ def default_config_path(*, workspace: str | Path | None = None) -> Path:
if beaver_home:
return Path(beaver_home).expanduser() / "config.json"
- nanobot_home = os.getenv("NANOBOT_HOME")
- if nanobot_home:
- return Path(nanobot_home).expanduser() / "config.json"
-
root = Path(workspace).expanduser() if workspace is not None else Path.cwd()
return root / ".beaver" / "config.json"
diff --git a/app-instance/backend/beaver/foundation/models/cron.py b/app-instance/backend/beaver/foundation/models/cron.py
index f2ba924..4b9cf5d 100644
--- a/app-instance/backend/beaver/foundation/models/cron.py
+++ b/app-instance/backend/beaver/foundation/models/cron.py
@@ -1,8 +1,7 @@
"""Scheduled task models for Beaver cron.
-The scheduler borrows Hermes' durable JSON + explicit schedule parsing shape,
-but the execution target is Beaver Task mode: every trigger creates a normal
-Task run instead of a detached agent turn.
+Every trigger targets Beaver Task mode so scheduled work remains visible as a
+normal Task instead of a detached agent turn.
"""
from __future__ import annotations
diff --git a/app-instance/backend/beaver/integrations/outlook/__init__.py b/app-instance/backend/beaver/integrations/outlook/__init__.py
index c4f3b7a..8c2b6ca 100644
--- a/app-instance/backend/beaver/integrations/outlook/__init__.py
+++ b/app-instance/backend/beaver/integrations/outlook/__init__.py
@@ -19,7 +19,7 @@ from beaver.foundation.config import BeaverConfig
from beaver.integrations.authz import AuthzClient
-OUTLOOK_SERVER_ID = os.getenv("BEAVER_OUTLOOK_MCP_SERVER_ID") or os.getenv("NANOBOT_OUTLOOK_MCP_SERVER_ID", "outlook_mcp")
+OUTLOOK_SERVER_ID = os.getenv("BEAVER_OUTLOOK_MCP_SERVER_ID", "outlook_mcp")
OUTLOOK_OVERVIEW_MESSAGE_LIMIT = 8
OUTLOOK_OVERVIEW_EVENT_LIMIT = 20
OUTLOOK_MAX_PAGE_SIZE = 100
@@ -31,11 +31,11 @@ class OutlookIntegrationError(RuntimeError):
@dataclass(frozen=True)
class OutlookDefaults:
- domain: str = os.getenv("NANOBOT_OUTLOOK_DEFAULT_DOMAIN", "")
- service_endpoint: str = os.getenv("NANOBOT_OUTLOOK_DEFAULT_EWS_URL", "")
- server: str = os.getenv("NANOBOT_OUTLOOK_DEFAULT_EWS_SERVER", "")
- default_timezone: str = os.getenv("NANOBOT_OUTLOOK_DEFAULT_TIMEZONE", "Asia/Shanghai")
- autodiscover: bool = os.getenv("NANOBOT_OUTLOOK_DEFAULT_AUTODISCOVER", "0") == "1"
+ domain: str = os.getenv("BEAVER_OUTLOOK_DEFAULT_DOMAIN", "")
+ service_endpoint: str = os.getenv("BEAVER_OUTLOOK_DEFAULT_EWS_URL", "")
+ server: str = os.getenv("BEAVER_OUTLOOK_DEFAULT_EWS_SERVER", "")
+ default_timezone: str = os.getenv("BEAVER_OUTLOOK_DEFAULT_TIMEZONE", "Asia/Shanghai")
+ autodiscover: bool = os.getenv("BEAVER_OUTLOOK_DEFAULT_AUTODISCOVER", "0") == "1"
@dataclass(frozen=True)
@@ -71,7 +71,7 @@ OUTLOOK_TOOL_NAMES = [
def _call_timeout_seconds() -> float:
- raw = os.getenv("NANOBOT_OUTLOOK_MCP_CALL_TIMEOUT_SECONDS", "").strip()
+ raw = os.getenv("BEAVER_OUTLOOK_MCP_CALL_TIMEOUT_SECONDS", "").strip()
try:
return max(1.0, float(raw)) if raw else 10.0
except ValueError:
@@ -108,8 +108,8 @@ def outlook_defaults() -> dict[str, Any]:
return {
"provider": "ews",
"server_id": OUTLOOK_SERVER_ID,
- "mcp_command": os.getenv("NANOBOT_OUTLOOK_MCP_COMMAND", "bw-outlook-mcp"),
- "mcp_extra_args": shlex.split(os.getenv("NANOBOT_OUTLOOK_MCP_EXTRA_ARGS", "").strip()),
+ "mcp_command": os.getenv("BEAVER_OUTLOOK_MCP_COMMAND", "bw-outlook-mcp"),
+ "mcp_extra_args": shlex.split(os.getenv("BEAVER_OUTLOOK_MCP_EXTRA_ARGS", "").strip()),
"fields": asdict(OutlookDefaults()),
}
diff --git a/app-instance/backend/beaver/interfaces/cli/main.py b/app-instance/backend/beaver/interfaces/cli/main.py
index d9897ab..5a7d131 100644
--- a/app-instance/backend/beaver/interfaces/cli/main.py
+++ b/app-instance/backend/beaver/interfaces/cli/main.py
@@ -1,7 +1,5 @@
"""CLI entry for Beaver."""
-from pathlib import Path
-
try:
import typer
except ModuleNotFoundError: # pragma: no cover - fallback for skeleton-only environments
@@ -29,8 +27,6 @@ except ModuleNotFoundError: # pragma: no cover - fallback for skeleton-only env
typer = _FallbackTyper() # type: ignore[assignment]
from beaver.services.agent_service import AgentService
-from beaver.services.hermes_migration import HermesMigrationService
-from beaver.skills.specs import SkillSpecStore
app = typer.Typer(help="Beaver backend CLI") if hasattr(typer, "Typer") else typer
@@ -59,26 +55,6 @@ def run(
typer.echo(result.output_text)
-@app.command("migrate-hermes")
-def migrate_hermes(
- repo: str = typer.Option(..., "--repo", help="Local checkout of https://github.com/NousResearch/hermes-agent."),
- workspace: str | None = typer.Option(None, "--workspace", help="Workspace root to import skills into."),
- manifest: str | None = typer.Option(None, "--manifest", help="Path for hermes_migration_manifest.json."),
- dry_run: bool = typer.Option(False, "--dry-run", help="Only write the manifest without importing skills."),
-) -> None:
- """Import no-credential Hermes Agent skills and write a manifest."""
-
- service = AgentService(workspace=workspace)
- loaded = service.create_loop().boot()
- store = loaded.skill_spec_store or SkillSpecStore(loaded.workspace)
- migration = HermesMigrationService(store, manifest_path=Path(manifest) if manifest else None)
- result = migration.migrate(repo, dry_run=dry_run)
- typer.echo(
- f"Hermes migration complete: {len(result['included'])} included, "
- f"{len(result['skipped'])} skipped."
- )
-
-
def main() -> None:
"""Project script entrypoint."""
app()
diff --git a/app-instance/backend/beaver/interfaces/mcp/tools_server.py b/app-instance/backend/beaver/interfaces/mcp/tools_server.py
index 2d2446b..a333b10 100644
--- a/app-instance/backend/beaver/interfaces/mcp/tools_server.py
+++ b/app-instance/backend/beaver/interfaces/mcp/tools_server.py
@@ -58,7 +58,7 @@ LOCAL_TOOL_CATEGORIES = {
def _workspace_path(value: str | None = None) -> Path:
- raw = value or os.getenv("BEAVER_WORKSPACE") or os.getenv("NANOBOT_WORKSPACE")
+ raw = value or os.getenv("BEAVER_WORKSPACE")
if raw:
return Path(raw).expanduser().resolve()
return Path.cwd()
diff --git a/app-instance/backend/beaver/interfaces/web/app.py b/app-instance/backend/beaver/interfaces/web/app.py
index 46e1885..4568e5e 100644
--- a/app-instance/backend/beaver/interfaces/web/app.py
+++ b/app-instance/backend/beaver/interfaces/web/app.py
@@ -23,7 +23,6 @@ from beaver.foundation.models import CronExecutionResult, CronRunRecord
from beaver.integrations.mcp import MCPConnectionManager
from beaver.services.agent_service import NOTIFICATION_SESSION_ID, AgentService
from beaver.services.cron_service import CronService, schedule_from_api
-from beaver.services.skill_migration import SkillMigrationService
from beaver.services.skillhub_service import SkillHubService
from beaver.skills.learning import SkillLearningWorker, SkillLearningWorkerConfig
from beaver.skills.catalog.utils import parse_frontmatter
@@ -305,7 +304,7 @@ def create_app(
)
app.state.auth_tokens = {}
app.state.handoff_codes = {}
- app.state.auth_file = Path(os.getenv("BEAVER_AUTH_FILE") or os.getenv("NANOBOT_AUTH_FILE") or "")
+ app.state.auth_file = Path(os.getenv("BEAVER_AUTH_FILE") or "")
max_file_size = 50 * 1024 * 1024
@app.get("/api/ping", response_model=WebStatusResponse)
@@ -427,7 +426,6 @@ def create_app(
_clean_text(payload.get("base_url"))
or config.backend_identity.public_base_url
or os.getenv("BEAVER_FRONTEND_PUBLIC_BASE_URL")
- or os.getenv("NANOBOT_FRONTEND_PUBLIC_BASE_URL")
or str(request.base_url).rstrip("/")
)
frontend_base_url = _clean_text(payload.get("frontend_base_url")) or public_base_url
@@ -526,7 +524,7 @@ def create_app(
return {
"id": username,
"username": username,
- "email": os.getenv("BEAVER_BACKEND_IDENTITY__EMAIL") or os.getenv("NANOBOT_BACKEND_IDENTITY__EMAIL", ""),
+ "email": os.getenv("BEAVER_BACKEND_IDENTITY__EMAIL", ""),
"role": "owner",
"quota_tier": "single-user",
}
@@ -1184,30 +1182,6 @@ def create_app(
)
return result
- @app.get("/api/tools/servers")
- async def list_tool_servers(request: Request) -> list[dict[str, Any]]:
- return await list_mcp_servers(request)
-
- @app.get("/api/tools")
- async def list_tools(request: Request) -> dict[str, Any]:
- servers = await list_mcp_servers(request)
- tool_groups = await list_mcp_tools(request)
- server_map = {server["id"]: server for server in servers}
- grouped = {"local": [], "online": []}
- for group in tool_groups:
- server = server_map.get(group["server_id"], {})
- kind = str(server.get("kind") or "online")
- item = {
- **group,
- "server_name": server.get("name") or group["server_id"],
- "transport": server.get("transport"),
- "kind": kind,
- "category": server.get("category") or kind,
- "status": server.get("status"),
- }
- grouped["local" if kind == "local" else "online"].append(item)
- return {"servers": servers, "groups": grouped}
-
@app.get("/api/skills")
async def list_skills(request: Request) -> list[dict[str, Any]]:
loaded = get_agent_service(request).create_loop().boot()
@@ -1301,19 +1275,6 @@ def create_app(
raise HTTPException(status_code=400, detail=str(exc)) from exc
return draft
- @app.post("/api/skills/migrate")
- async def migrate_skills(request: Request) -> dict[str, Any]:
- loaded = get_agent_service(request).create_loop().boot()
- return SkillMigrationService(loaded.skill_spec_store).migrate_all() # type: ignore[arg-type]
-
- @app.get("/api/skills/migration-manifest")
- async def get_skill_migration_manifest(request: Request) -> dict[str, Any]:
- loaded = get_agent_service(request).create_loop().boot()
- path = loaded.workspace / "skill_migration_manifest.json"
- if not path.exists():
- return {"included": [], "skipped": []}
- return json.loads(path.read_text(encoding="utf-8"))
-
@app.get("/api/marketplaces/skills/search")
async def search_skillhub(
request: Request,
@@ -2482,7 +2443,7 @@ def _provider_enabled(provider_name: str, provider_cfg: Any) -> bool:
def _auth_file_path() -> Path:
- raw = os.getenv("BEAVER_AUTH_FILE") or os.getenv("NANOBOT_AUTH_FILE")
+ raw = os.getenv("BEAVER_AUTH_FILE")
if raw:
return Path(raw)
return Path.home() / ".beaver" / "web_auth_users.json"
@@ -2542,7 +2503,7 @@ def _issue_web_token(app: FastAPI, username: str) -> str:
def _handoff_ttl_seconds() -> int:
- raw = os.getenv("NANOBOT_HANDOFF_CODE_TTL_SECONDS", "90").strip()
+ raw = os.getenv("BEAVER_HANDOFF_CODE_TTL_SECONDS", "90").strip()
try:
return max(15, int(raw))
except ValueError:
@@ -2550,7 +2511,7 @@ def _handoff_ttl_seconds() -> int:
def _handoff_replay_window_seconds() -> int:
- raw = os.getenv("NANOBOT_HANDOFF_REPLAY_WINDOW_SECONDS", "15").strip()
+ raw = os.getenv("BEAVER_HANDOFF_REPLAY_WINDOW_SECONDS", "15").strip()
try:
return max(1, int(raw))
except ValueError:
@@ -2637,22 +2598,18 @@ def _require_web_user(app: FastAPI, authorization: str | None) -> str:
def _backend_connection_view(request: Request) -> dict[str, Any]:
public_base_url = (
os.getenv("BEAVER_BACKEND_IDENTITY__PUBLIC_BASE_URL")
- or os.getenv("NANOBOT_BACKEND_IDENTITY__PUBLIC_BASE_URL")
or os.getenv("BEAVER_FRONTEND_PUBLIC_BASE_URL")
- or os.getenv("NANOBOT_FRONTEND_PUBLIC_BASE_URL")
or str(request.base_url).rstrip("/")
)
backend_id = (
os.getenv("BEAVER_BACKEND_IDENTITY__BACKEND_ID")
- or os.getenv("NANOBOT_BACKEND_IDENTITY__BACKEND_ID")
or os.getenv("BEAVER_BACKEND_IDENTITY__CLIENT_ID")
- or os.getenv("NANOBOT_BACKEND_IDENTITY__CLIENT_ID")
)
- client_id = os.getenv("BEAVER_BACKEND_IDENTITY__CLIENT_ID") or os.getenv("NANOBOT_BACKEND_IDENTITY__CLIENT_ID") or backend_id
+ client_id = os.getenv("BEAVER_BACKEND_IDENTITY__CLIENT_ID") or backend_id
return {
"backend_id": backend_id,
"client_id": client_id,
- "name": os.getenv("BEAVER_BACKEND_IDENTITY__NAME") or os.getenv("NANOBOT_BACKEND_IDENTITY__NAME") or backend_id,
+ "name": os.getenv("BEAVER_BACKEND_IDENTITY__NAME") or backend_id,
"public_base_url": public_base_url,
"api_base_url": public_base_url,
"frontend_base_url": public_base_url,
@@ -2663,16 +2620,14 @@ def _backend_connection_view(request: Request) -> dict[str, Any]:
def _local_backend_view() -> dict[str, Any]:
return {
- "backend_id": os.getenv("BEAVER_BACKEND_IDENTITY__BACKEND_ID") or os.getenv("NANOBOT_BACKEND_IDENTITY__BACKEND_ID"),
- "client_id": os.getenv("BEAVER_BACKEND_IDENTITY__CLIENT_ID") or os.getenv("NANOBOT_BACKEND_IDENTITY__CLIENT_ID"),
- "name": os.getenv("BEAVER_BACKEND_IDENTITY__NAME") or os.getenv("NANOBOT_BACKEND_IDENTITY__NAME"),
+ "backend_id": os.getenv("BEAVER_BACKEND_IDENTITY__BACKEND_ID"),
+ "client_id": os.getenv("BEAVER_BACKEND_IDENTITY__CLIENT_ID"),
+ "name": os.getenv("BEAVER_BACKEND_IDENTITY__NAME"),
"public_base_url": os.getenv("BEAVER_BACKEND_IDENTITY__PUBLIC_BASE_URL")
- or os.getenv("NANOBOT_BACKEND_IDENTITY__PUBLIC_BASE_URL")
- or os.getenv("BEAVER_FRONTEND_PUBLIC_BASE_URL")
- or os.getenv("NANOBOT_FRONTEND_PUBLIC_BASE_URL"),
+ or os.getenv("BEAVER_FRONTEND_PUBLIC_BASE_URL"),
"authz": {
- "enabled": (os.getenv("BEAVER_AUTHZ__ENABLED") or os.getenv("NANOBOT_AUTHZ__ENABLED", "")).strip() in {"1", "true", "True"},
- "base_url": os.getenv("BEAVER_AUTHZ__BASE_URL") or os.getenv("NANOBOT_AUTHZ__BASE_URL"),
+ "enabled": os.getenv("BEAVER_AUTHZ__ENABLED", "").strip() in {"1", "true", "True"},
+ "base_url": os.getenv("BEAVER_AUTHZ__BASE_URL"),
},
}
diff --git a/app-instance/backend/beaver/memory/curated/store.py b/app-instance/backend/beaver/memory/curated/store.py
index a02616f..99c93e6 100644
--- a/app-instance/backend/beaver/memory/curated/store.py
+++ b/app-instance/backend/beaver/memory/curated/store.py
@@ -1,6 +1,6 @@
"""Beaver 的精炼长期记忆存储层。
-这个文件实现的是以 Hermes-agent 为基线的 curated memory 模型,目标不是
+这个文件实现的是 Beaver curated memory 模型,目标不是
“把所有历史都存下来”,而是只保存跨会话仍然值得保留的稳定事实。
核心设计:
diff --git a/app-instance/backend/beaver/services/cron_service.py b/app-instance/backend/beaver/services/cron_service.py
index d723697..21678a9 100644
--- a/app-instance/backend/beaver/services/cron_service.py
+++ b/app-instance/backend/beaver/services/cron_service.py
@@ -38,10 +38,9 @@ _MAX_HISTORY = 20
class CronService:
"""Persistent single-timer scheduler.
- Hermes' cron implementation stores jobs as JSON and ticks safely in the
- background. Beaver keeps that shape, but the callback is required to route
- agent work through Task mode so every scheduled trigger is visible as a
- normal Task.
+ Jobs are stored as JSON and ticked safely in the background. The callback
+ routes agent work through Task mode so every scheduled trigger is visible as
+ a normal Task.
"""
def __init__(self, store_path: str | Path, *, on_job: CronCallback | None = None) -> None:
diff --git a/app-instance/backend/beaver/services/memory_service.py b/app-instance/backend/beaver/services/memory_service.py
index 7133742..91dd5b8 100644
--- a/app-instance/backend/beaver/services/memory_service.py
+++ b/app-instance/backend/beaver/services/memory_service.py
@@ -43,7 +43,7 @@ class MemoryService:
def reload_for_new_run(self) -> None:
"""每次新 run 开始前刷新 live state。
- 这是 Hermes 风格 memory policy 的关键点:
+ 这是 Beaver memory policy 的关键点:
- 上一次会话中通过 tool 写入的持久记忆,下一次运行应该能看到
- 但同一次 run 中途写入的新记忆,不应反向修改当前 frozen snapshot
"""
diff --git a/app-instance/backend/beaver/skills/catalog/loader.py b/app-instance/backend/beaver/skills/catalog/loader.py
index 7d097cc..4c2b46f 100644
--- a/app-instance/backend/beaver/skills/catalog/loader.py
+++ b/app-instance/backend/beaver/skills/catalog/loader.py
@@ -237,7 +237,7 @@ class SkillsLoader:
def build_skills_summary(self) -> str:
"""构建可注入 system prompt 的 skills index。
- 虽然函数名还沿用 `summary`,但当前语义已经更接近 Hermes 的 skills index:
+ 虽然函数名还沿用 `summary`,但当前语义是轻量 skills index:
- 这里只告诉模型“系统里有哪些 skill 可用”
- 不负责把 skill 正文塞进 system prompt
- 真正激活的 skill 正文由 resolver/builder 走显式消息注入
diff --git a/app-instance/backend/beaver/skills/catalog/utils.py b/app-instance/backend/beaver/skills/catalog/utils.py
index 8d8ded3..4c1e75e 100644
--- a/app-instance/backend/beaver/skills/catalog/utils.py
+++ b/app-instance/backend/beaver/skills/catalog/utils.py
@@ -87,9 +87,7 @@ def strip_frontmatter(content: str) -> str:
def parse_skill_metadata_blob(raw: str) -> dict[str, Any]:
"""解析 metadata 字段里的 JSON 扩展配置。
- 为了兼容旧 nanobot 习惯,这里同时支持:
- - `nanobot`
- - `openclaw`
+ Supports plain metadata objects and the current `openclaw` namespace.
第一版主要关心的字段有:
- `always`
@@ -103,7 +101,7 @@ def parse_skill_metadata_blob(raw: str) -> dict[str, Any]:
if not isinstance(data, dict):
return {}
- nested = data.get("nanobot", data.get("openclaw", data))
+ nested = data.get("openclaw", data)
return nested if isinstance(nested, dict) else {}
diff --git a/app-instance/backend/beaver/tools/base.py b/app-instance/backend/beaver/tools/base.py
index 668bd39..bc4f1ed 100644
--- a/app-instance/backend/beaver/tools/base.py
+++ b/app-instance/backend/beaver/tools/base.py
@@ -20,6 +20,7 @@ from __future__ import annotations
from abc import ABC, abstractmethod
from dataclasses import dataclass, field
+import inspect
import json
from typing import Any
@@ -181,9 +182,21 @@ class ObjectBackedTool(BaseTool):
arguments["current_session_id"] = context.session_id
if "workspace" not in arguments and hasattr(self.backend, "workspace"):
arguments["workspace"] = context.workspace
- if "metadata" not in arguments:
+ if "metadata" not in arguments and self._backend_accepts_argument("metadata"):
arguments["metadata"] = context.metadata
+ def _backend_accepts_argument(self, name: str) -> bool:
+ try:
+ signature = inspect.signature(self.backend.execute)
+ except (TypeError, ValueError):
+ return False
+ for parameter in signature.parameters.values():
+ if parameter.kind == inspect.Parameter.VAR_KEYWORD:
+ return True
+ if parameter.name == name:
+ return True
+ return False
+
@staticmethod
def _normalize_output(content: Any) -> dict[str, Any]:
"""把后端工具返回值转成统一 success/content/error 语义。
diff --git a/app-instance/backend/beaver/tools/builtins/cron.py b/app-instance/backend/beaver/tools/builtins/cron.py
index a3834cf..e42eef5 100644
--- a/app-instance/backend/beaver/tools/builtins/cron.py
+++ b/app-instance/backend/beaver/tools/builtins/cron.py
@@ -33,7 +33,7 @@ CRON_TOOL_PARAMETERS: dict[str, Any] = {
},
"schedule": {
"type": "string",
- "description": "Hermes-style schedule, for example 'every 15m', '0 9 * * *', or an ISO datetime.",
+ "description": "Schedule expression, for example 'every 15m', '0 9 * * *', or an ISO datetime.",
},
"every_seconds": {
"type": "integer",
diff --git a/app-instance/backend/beaver/tools/builtins/memory.py b/app-instance/backend/beaver/tools/builtins/memory.py
index 9ed2c06..d1447a1 100644
--- a/app-instance/backend/beaver/tools/builtins/memory.py
+++ b/app-instance/backend/beaver/tools/builtins/memory.py
@@ -59,7 +59,7 @@ def memory_tool(
old_text: str | None = None,
store: MemoryStore | None = None,
) -> str:
- """分发 Hermes 风格的 CRUD memory API,并返回 JSON 字符串。
+ """分发 CRUD memory API,并返回 JSON 字符串。
这里统一采用 JSON 返回,是为了兼容常见 tool-calling 场景:
- LLM 更容易消费结构化结果
diff --git a/app-instance/backend/beaver/tools/builtins/session_search.py b/app-instance/backend/beaver/tools/builtins/session_search.py
index 3125cbe..9a227d5 100644
--- a/app-instance/backend/beaver/tools/builtins/session_search.py
+++ b/app-instance/backend/beaver/tools/builtins/session_search.py
@@ -1,6 +1,6 @@
"""Beaver 内置 session_search tool。
-这个工具对应 Hermes-agent 的跨会话检索能力,目标不是把所有历史内容塞回主上下文,
+这个工具提供跨会话检索能力,目标不是把所有历史内容塞回主上下文,
而是按需从过去的 session 中找回“之前发生过什么”。
当前实现保留了几个关键行为:
@@ -28,7 +28,7 @@ class SessionSearchDB(Protocol):
"""session_search 依赖的最小数据库契约。
这里没有直接绑定某个具体 SQLite 实现,而是先定义行为接口。
- 这样后面无论你接的是 Hermes 风格 state DB、还是 Beaver 自己的 transcript store,
+ 这样后面无论你接的是当前 SQLite state DB、还是其他 transcript store,
只要满足这些方法就能工作。
"""
diff --git a/app-instance/backend/beaver/tools/builtins/skill_view.py b/app-instance/backend/beaver/tools/builtins/skill_view.py
index 83a382b..18e3dea 100644
--- a/app-instance/backend/beaver/tools/builtins/skill_view.py
+++ b/app-instance/backend/beaver/tools/builtins/skill_view.py
@@ -1,6 +1,6 @@
"""Beaver 内置 skill_view tool。
-这个工具对应 Hermes 风格的显式 skill loading path:
+这个工具对应显式 skill loading path:
1. skill 正文默认不会长期塞进 system prompt
2. 模型若想查看某个 skill 的完整正文或支持文件,必须显式调用 `skill_view`
diff --git a/app-instance/backend/tests/unit/test_config_loader.py b/app-instance/backend/tests/unit/test_config_loader.py
index c353f1c..3d07234 100644
--- a/app-instance/backend/tests/unit/test_config_loader.py
+++ b/app-instance/backend/tests/unit/test_config_loader.py
@@ -1,5 +1,4 @@
import json
-from pathlib import Path
from beaver.engine import AgentLoop, EngineLoader
from beaver.engine.providers import make_provider_bundle
@@ -139,9 +138,36 @@ def test_openai_compatible_qwen_config_keeps_openai_provider() -> None:
assert bundle.main_provider._resolve_model("qwen-plus") == "openai/qwen-plus"
-def test_load_config_reads_stevenli_mcp_authz_identity() -> None:
- repo_root = Path(__file__).resolve().parents[4]
- config_path = repo_root / "app-instance" / "runtime" / "instances" / "stevenli" / "nanobot-home" / "config.json"
+def test_load_config_reads_mcp_authz_identity(tmp_path) -> None:
+ config_path = tmp_path / "beaver-home" / "config.json"
+ config_path.parent.mkdir()
+ config_path.write_text(
+ json.dumps(
+ {
+ "tools": {
+ "mcpServers": {
+ "outlook_mcp": {
+ "url": "http://10.6.80.29:8000/mcp",
+ "authMode": "oauth_backend_token",
+ "authAudience": "mcp:outlook_mcp",
+ "authScopes": ["list_tools", "tool:mail_list_messages"],
+ "toolTimeout": 60,
+ "sensitive": True,
+ }
+ }
+ },
+ "authz": {
+ "enabled": True,
+ "baseUrl": "http://nano-authz-service:19090",
+ },
+ "backend_identity": {
+ "backend_id": "stevenli",
+ "client_id": "stevenli",
+ },
+ }
+ ),
+ encoding="utf-8",
+ )
config = load_config(config_path=config_path)
server = config.tools.mcp_servers["outlook_mcp"]
diff --git a/app-instance/backend/tests/unit/test_cron_service.py b/app-instance/backend/tests/unit/test_cron_service.py
index 7584d69..2aeb6b6 100644
--- a/app-instance/backend/tests/unit/test_cron_service.py
+++ b/app-instance/backend/tests/unit/test_cron_service.py
@@ -6,7 +6,7 @@ from beaver.tools.builtins import CronTool
from beaver.services.cron_service import CronService, compute_next_run, parse_schedule, schedule_from_api
-def test_parse_hermes_style_schedules() -> None:
+def test_parse_schedule_expressions() -> None:
interval = parse_schedule("every 15m")
assert interval.kind == "every"
assert interval.every_ms == 15 * 60 * 1000
diff --git a/app-instance/backend/tests/unit/test_marketplace_and_mcp.py b/app-instance/backend/tests/unit/test_marketplace_and_mcp.py
index 3534727..e7a60ec 100644
--- a/app-instance/backend/tests/unit/test_marketplace_and_mcp.py
+++ b/app-instance/backend/tests/unit/test_marketplace_and_mcp.py
@@ -1,13 +1,11 @@
import asyncio
import io
-import json
import zipfile
from types import SimpleNamespace
import pytest
from beaver.interfaces.web.app import _create_skill_upload_draft
-from beaver.services.hermes_migration import HermesMigrationService
from beaver.services.skillhub_service import SkillHubService
from beaver.skills.drafts import DraftService
from beaver.skills.specs import SkillSpecStore
@@ -101,27 +99,6 @@ def test_upload_skill_zip_keeps_supporting_files_on_draft(tmp_path):
assert upload_dir.endswith(draft["draft_id"])
-def test_hermes_migration_manifest_includes_no_credential_skill_and_skips_api_skill(tmp_path):
- repo = tmp_path / "hermes"
- safe = repo / "skills" / "safe"
- unsafe = repo / "skills" / "unsafe"
- safe.mkdir(parents=True)
- unsafe.mkdir(parents=True)
- safe.joinpath("SKILL.md").write_text("---\nname: safe\n---\nUse local files only.\n", encoding="utf-8")
- unsafe.joinpath("SKILL.md").write_text("---\nname: unsafe\n---\nRequires API_KEY.\n", encoding="utf-8")
-
- store = SkillSpecStore(tmp_path / "workspace")
- manifest = HermesMigrationService(store).migrate(repo)
- included = {item["skill_name"] for item in manifest["included"]}
- skipped = {item.get("skill_name"): item["reason"] for item in manifest["skipped"]}
-
- assert "safe" in included
- assert skipped["unsafe"] == "requires_external_credentials"
- assert store.get_skill_spec("safe") is not None
- manifest_path = tmp_path / "workspace" / "hermes_migration_manifest.json"
- assert json.loads(manifest_path.read_text(encoding="utf-8"))["source"] == "hermes-agent"
-
-
def test_mcp_wrapper_metadata_preserves_server_id_with_underscores():
tool_def = SimpleNamespace(name="auth_status", description="Auth", inputSchema={"type": "object", "properties": {}})
diff --git a/app-instance/frontend/README.md b/app-instance/frontend/README.md
index 5877a95..c1f735a 100644
--- a/app-instance/frontend/README.md
+++ b/app-instance/frontend/README.md
@@ -53,10 +53,9 @@
| `/status` | 系统状态 |
| `/cron` | 定时任务 |
| `/skills` | 技能管理 |
-| `/plugins` | 插件管理 |
| `/agents` | 智能体管理 |
| `/mcp` | MCP 服务管理 |
-| `/marketplace` | 插件市场 |
+| `/marketplace` | 技能市场 |
| `/files` | 工作区文件管理 |
| `/help` | 帮助说明 |
@@ -240,15 +239,9 @@ docker build \
- 状态接口
- WebSocket 连接
-### 2. 命令名和目录名未做品牌迁移
+### 2. 技术标识
-当前仓库的部分技术标识仍沿用旧命名,例如:
-
-- `nanobot web`
-- `~/.beaver/plugins/`
-- 本地存储中的旧 token key
-
-这些属于兼容性和后端约定的一部分,前端展示品牌已替换为 `Boardware Genius`,但技术标识没有在这个仓库里强制迁移。
+当前前端使用 Beaver 技术命名,本地 token、语言和 handoff 状态都使用 `beaver_*` key。
### 3. 动态内容可能仍包含英文
diff --git a/app-instance/frontend/app/(app)/notifications/[scheduledRunId]/page.tsx b/app-instance/frontend/app/(app)/notifications/[scheduledRunId]/page.tsx
index 0d95b26..360076d 100644
--- a/app-instance/frontend/app/(app)/notifications/[scheduledRunId]/page.tsx
+++ b/app-instance/frontend/app/(app)/notifications/[scheduledRunId]/page.tsx
@@ -149,7 +149,6 @@ export default function NotificationDetailPage() {
processArtifacts={[]}
selectedRunId={null}
onSelectRun={() => {}}
- onCancelRun={() => {}}
onFeedback={() => {}}
/>
diff --git a/app-instance/frontend/app/(app)/page.tsx b/app-instance/frontend/app/(app)/page.tsx
index 744028c..415a337 100644
--- a/app-instance/frontend/app/(app)/page.tsx
+++ b/app-instance/frontend/app/(app)/page.tsx
@@ -7,7 +7,6 @@ import { Brain, Plus, Send, Trash2, X } from 'lucide-react';
import { ChatWorkbench } from '@/components/chat-workbench/ChatWorkbench';
import { ScrollArea } from '@/components/ui/scroll-area';
import {
- cancelDelegation,
archiveSession,
createSession,
getActiveTask,
@@ -464,18 +463,6 @@ export default function ChatPage() {
setSessionId(key);
};
- const handleCancelRun = useCallback(async (runId: string) => {
- try {
- await cancelDelegation(runId);
- } catch (err: any) {
- addMessage({
- role: 'assistant',
- content: pickAppText(locale, `取消任务 ${runId} 失败:${err.message || '未知错误'}`, `Failed to cancel task ${runId}: ${err.message || 'Unknown error'}`),
- timestamp: new Date().toISOString(),
- });
- }
- }, [addMessage, locale]);
-
const removePendingFile = useCallback((file: File) => {
setPendingFiles((prev) => prev.filter((item) => item.file !== file));
}, []);
@@ -566,7 +553,6 @@ export default function ChatPage() {
processArtifacts={sessionProcessArtifacts}
selectedRunId={selectedSessionRunId}
onSelectRun={(runId) => setSelectedRunId(selectedSessionRunId === runId ? null : runId)}
- onCancelRun={handleCancelRun}
onFeedback={handleFeedback}
/>
diff --git a/app-instance/frontend/app/(app)/skills/page.tsx b/app-instance/frontend/app/(app)/skills/page.tsx
index 8630c58..6a0ba78 100644
--- a/app-instance/frontend/app/(app)/skills/page.tsx
+++ b/app-instance/frontend/app/(app)/skills/page.tsx
@@ -40,7 +40,6 @@ import {
listSkillCandidates,
listSkillDrafts,
listSkills,
- migrateSkills,
publishSkillDraft,
regenerateSkillDraft,
rejectSkillDraft,
@@ -207,15 +206,6 @@ export default function SkillsPage() {
{t('上传技能', 'Upload skill')}
-
diff --git a/app-instance/frontend/app/(app)/status/page.tsx b/app-instance/frontend/app/(app)/status/page.tsx
index abada94..322b445 100644
--- a/app-instance/frontend/app/(app)/status/page.tsx
+++ b/app-instance/frontend/app/(app)/status/page.tsx
@@ -15,17 +15,7 @@ import {
Settings2,
ScrollText,
} from 'lucide-react';
-import { getStatus, restartSystem, updateProviderConfig } from '@/lib/api';
-import {
- AlertDialog,
- AlertDialogAction,
- AlertDialogCancel,
- AlertDialogContent,
- AlertDialogDescription,
- AlertDialogFooter,
- AlertDialogHeader,
- AlertDialogTitle,
-} from '@/components/ui/alert-dialog';
+import { getStatus, updateProviderConfig } from '@/lib/api';
import { Button } from '@/components/ui/button';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Badge } from '@/components/ui/badge';
@@ -57,9 +47,6 @@ export default function StatusPage() {
const [status, setStatus] = useState(null);
const [error, setError] = useState(null);
const [loading, setLoading] = useState(true);
- const [restartDialogOpen, setRestartDialogOpen] = useState(false);
- const [restarting, setRestarting] = useState(false);
- const [restartError, setRestartError] = useState(null);
const [selectedProvider, setSelectedProvider] = useState(null);
const [providerForm, setProviderForm] = useState(() => ({
enabled: false,
@@ -88,36 +75,6 @@ export default function StatusPage() {
loadStatus();
}, []);
- useEffect(() => {
- if (!restarting) {
- return;
- }
-
- const intervalId = window.setInterval(async () => {
- try {
- await getStatus();
- window.location.reload();
- } catch {
- // Ignore failures until the container is back.
- }
- }, 3000);
-
- return () => {
- window.clearInterval(intervalId);
- };
- }, [restarting]);
-
- const handleRestart = async () => {
- setRestartError(null);
- try {
- await restartSystem();
- setRestartDialogOpen(false);
- setRestarting(true);
- } catch (err: any) {
- setRestartError(err.message || pickAppText(locale, '重启失败', 'Restart failed'));
- }
- };
-
const openProviderDialog = (provider: ProviderStatus) => {
setSelectedProvider(provider);
setProviderError(null);
@@ -204,7 +161,7 @@ export default function StatusPage() {
)}
-