mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-09-06 17:47:14 +00:00
refactor(skill): move orchestration into core
This commit is contained in:
@@ -352,15 +352,12 @@ SkillStore
|
||||
└─ 支持 source_subdir / target_suffix(commit 1aa043f)
|
||||
```
|
||||
|
||||
GitHub 安装路径由 Core HTTP 层下载归档,再交给 SkillRepository。Skill 文件位于独立的 `skills.root`,执行时由 Core 组装成通用只读 `BoxMountSpec` 并挂载到 `/workspace/.skills/`。Box 的正常模型、客户端和 Runtime 不包含 `skill_name`、Skill CRUD、revision 或 `SKILL.md` 语义。
|
||||
GitHub 安装路径由 Core HTTP 层下载归档,再交给 SkillRepository。Skill 文件位于独立的 `skills.root`,执行时由 Core 组装成通用只读 `BoxMountSpec` 并挂载到 `/workspace/.skills/`。Box 的模型、客户端和 Runtime 不包含 `skill_name`、Skill CRUD、revision 或 `SKILL.md` 语义。Core 与 Box Runtime SDK 按同一发布单元同步升级;Box 不保留旧 Skill RPC,也会拒绝旧的 Skill-aware payload 字段。
|
||||
|
||||
滚动升级只保留一个隔离桥:`box/legacy_skill_compat.py` 让旧 Core 暂时调用新 Box,并把旧 `skill_name` 转为普通只读 mount。部署顺序必须先升级 Box、再升级 Core。该模块有 `TODO(next-major)`,下一大版本删除;正常架构不依赖它。
|
||||
仍保留的兼容仅用于已有数据的在线升级,不用于混版本协议:
|
||||
|
||||
下一大版本的删除清单(当前均有 `TODO(next-major)`):
|
||||
|
||||
1. 删除 SDK `box/legacy_skill_compat.py` 及 Server 中唯一的注册/转换钩子。
|
||||
2. 删除 Core 对 `box.local.skills_root` 的配置 fallback。
|
||||
3. 新安装默认从历史 `./data/box/skills` 切到 `./data/skills`;届时 Box 部署只需只读访问 Core 明确下发的通用 artifact root。
|
||||
1. Core 暂时读取 `box.local.skills_root`,兼容尚未生成 `skills.root` 的持久化配置。
|
||||
2. 新安装默认暂时沿用历史 `./data/box/skills`,避免升级时搬迁已安装 Skill;下一大版本切换到 `./data/skills`。
|
||||
|
||||
### 3.8 Security (`box/security.py`, 52 行)
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import os
|
||||
import weakref
|
||||
|
||||
from langbot_plugin.skill_store import (
|
||||
SkillRevisionMismatchError,
|
||||
@@ -23,7 +24,14 @@ class SkillRepository:
|
||||
self._local_config = (config.get('box') or {}).get('local') or {}
|
||||
self._skills_config = config.get('skills') or {}
|
||||
self._store = SkillStore(self._skills_root())
|
||||
self._lock = asyncio.Lock()
|
||||
self._locks: weakref.WeakValueDictionary[str, asyncio.Lock] = weakref.WeakValueDictionary()
|
||||
|
||||
def _workspace_lock(self, namespace: str) -> asyncio.Lock:
|
||||
lock = self._locks.get(namespace)
|
||||
if lock is None:
|
||||
lock = asyncio.Lock()
|
||||
self._locks[namespace] = lock
|
||||
return lock
|
||||
|
||||
def _host_root(self) -> str:
|
||||
configured = str(self._local_config.get('host_root') or './data/box').strip()
|
||||
@@ -101,7 +109,7 @@ class SkillRepository:
|
||||
method = getattr(self._store.scoped(namespace), method_name)
|
||||
return method(*args, **kwargs)
|
||||
|
||||
async with self._lock:
|
||||
async with self._workspace_lock(namespace):
|
||||
with blocking_work_scope(f'skill:{namespace}'):
|
||||
return await run_blocking_atomic(invoke)
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ def _repository(tmp_path) -> SkillRepository:
|
||||
'local': {
|
||||
'host_root': str(tmp_path / 'box'),
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
),
|
||||
)
|
||||
@@ -47,6 +47,14 @@ def test_repository_prefers_standalone_skill_root(tmp_path):
|
||||
assert repository._store.root == str((tmp_path / 'skill-store').resolve())
|
||||
|
||||
|
||||
def test_repository_locks_are_workspace_scoped(tmp_path):
|
||||
repository = _repository(tmp_path)
|
||||
|
||||
first = repository._workspace_lock('workspace-a')
|
||||
assert repository._workspace_lock('workspace-a') is first
|
||||
assert repository._workspace_lock('workspace-b') is not first
|
||||
|
||||
|
||||
def test_repository_keeps_old_box_root_only_for_online_upgrade(tmp_path):
|
||||
app = SimpleNamespace(
|
||||
workspace_service=SimpleNamespace(get_execution_binding=_binding),
|
||||
@@ -89,7 +97,7 @@ async def test_repository_crud_and_reads_do_not_require_box(tmp_path):
|
||||
|
||||
skill = await repository.get_skill(_CONTEXT, 'docs-only', snapshot=True)
|
||||
assert skill is not None
|
||||
assert skill['revision'].startswith('sha256:')
|
||||
assert skill['revision'].startswith('stat-v1:')
|
||||
assert [item['name'] for item in await repository.list_skills(_CONTEXT)] == ['docs-only']
|
||||
|
||||
listed = await repository.list_skill_resources(
|
||||
|
||||
Reference in New Issue
Block a user