feat: 工具系统迁移 + 重型插件骨架 + 前端交互增强
- 工具系统从 kilostar/plugin/tool_plugin/ 迁移到 data/toolset/(manifest.json 声明式) - 新增 plugin_runtime 模块:BaseOrganization / GlobalPluginManager / loader / tool_bridge - 新增 org_task + org_task_event 表及 DAO(alembic 0009) - 新增 /api/v1/plugin 路由(submit/status/stream/install/reload) - 新增 data/plugin/example_dept 示例重型插件 - regulatory_node 支持聊天历史上下文注入 - send_file 改为 artifact 存盘 + SSE 推送下载链接 - 前端 WorkflowFileCard 组件 + ToolSettings README 渲染 - utils 整理:合并 access/role_check、standalone_proxy→ray_compat、删除废弃模块 - 项目结构文档移至 docs/STRUCTURE.md 并详细展开 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -17,10 +17,11 @@ from pydantic import BaseModel
|
||||
import viceroy
|
||||
from kilostar.utils.ray_hook import ray_actor_hook
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from kilostar.utils.access import TokenData
|
||||
from kilostar.utils.check_user.role_check import RoleChecker
|
||||
from fastapi.responses import FileResponse
|
||||
from kilostar.utils.access import TokenData, RoleChecker, Accessor
|
||||
from kilostar.core.postgres_database.model import UserAuthority
|
||||
from kilostar.utils.mcp_helper import list_mcp_tools_from_gsm
|
||||
from kilostar.utils.settings import get_artifact_dir
|
||||
|
||||
resource_router = APIRouter(prefix="/api/v1/resource")
|
||||
|
||||
@@ -48,13 +49,12 @@ class MCPServerConfig(BaseModel):
|
||||
async def install_skill(
|
||||
skill: Skill, _: TokenData = Depends(RoleChecker(allowed_roles=UserAuthority.USER))
|
||||
):
|
||||
"""通过 viceroy 把 skill 仓库克隆到 ``plugin/skill``,并在状态机中登记。"""
|
||||
"""通过 viceroy 把 skill 仓库克隆到 ``data/plugin/skill``,并在状态机中登记。"""
|
||||
global_state_machine = ray_actor_hook("global_state_machine").global_state_machine
|
||||
import os
|
||||
from kilostar.utils.settings import get_plugin_dir
|
||||
|
||||
skill_output_dir = os.path.abspath(
|
||||
os.path.join(os.path.dirname(__file__), "..", "plugin", "skill")
|
||||
)
|
||||
skill_output_dir = str(get_plugin_dir() / "skill")
|
||||
os.makedirs(skill_output_dir, exist_ok=True)
|
||||
await viceroy.install_skill_async(
|
||||
url=skill.repo_url, path=skill.path, output=skill_output_dir
|
||||
@@ -133,6 +133,78 @@ async def delete_mcp_server(
|
||||
return {"message": "success"}
|
||||
|
||||
|
||||
# ─── Workflow Artifact 下载(agent send_file 投递的文件)───
|
||||
|
||||
|
||||
@resource_router.get("/artifact/{trace_id}/{artifact_id}")
|
||||
async def download_artifact(
|
||||
trace_id: str,
|
||||
artifact_id: str,
|
||||
token_data: TokenData = Depends(Accessor.get_current_user),
|
||||
):
|
||||
"""下载某个 trace 名下的 agent 产物文件。
|
||||
|
||||
路径校验三件套:
|
||||
1. trace 必须存在且属于当前用户
|
||||
2. ``artifact_id`` 限定为 12 位 hex(uuid4 前缀),防止穿越
|
||||
3. 解析后的最终路径必须仍然落在 ``<artifact_dir>/<trace_id>/`` 之内
|
||||
"""
|
||||
if not artifact_id.isalnum() or len(artifact_id) > 32:
|
||||
raise HTTPException(status_code=400, detail="invalid artifact id")
|
||||
|
||||
postgres_database = ray_actor_hook("postgres_database").postgres_database
|
||||
wf = await postgres_database.get_workflow.remote(trace_id)
|
||||
if not wf:
|
||||
raise HTTPException(status_code=404, detail="Workflow not found")
|
||||
if getattr(wf, "user_id", None) != token_data.user_id:
|
||||
raise HTTPException(status_code=403, detail="Forbidden")
|
||||
|
||||
trace_dir = (get_artifact_dir() / trace_id).resolve()
|
||||
if not trace_dir.exists() or not trace_dir.is_dir():
|
||||
raise HTTPException(status_code=404, detail="Artifact not found")
|
||||
|
||||
matches = list(trace_dir.glob(f"{artifact_id}_*"))
|
||||
if not matches:
|
||||
raise HTTPException(status_code=404, detail="Artifact not found")
|
||||
|
||||
target = matches[0].resolve()
|
||||
if not str(target).startswith(str(trace_dir) + "/"):
|
||||
raise HTTPException(status_code=400, detail="invalid path")
|
||||
|
||||
filename = target.name.split("_", 1)[-1]
|
||||
return FileResponse(
|
||||
path=str(target),
|
||||
filename=filename,
|
||||
media_type="application/octet-stream",
|
||||
)
|
||||
|
||||
|
||||
# ─── Toolset Packages(磁盘工具包:插件单元)───
|
||||
|
||||
|
||||
@resource_router.get("/toolset-package")
|
||||
async def list_toolset_packages(
|
||||
_: TokenData = Depends(RoleChecker(allowed_roles=UserAuthority.USER)),
|
||||
):
|
||||
"""列出所有磁盘上的工具包(``data/toolset/<name>/`` 单元)。"""
|
||||
global_state_machine = ray_actor_hook("global_state_machine").global_state_machine
|
||||
packages = await global_state_machine.list_toolset_packages.remote()
|
||||
return {"packages": packages}
|
||||
|
||||
|
||||
@resource_router.get("/toolset-package/{name}/readme")
|
||||
async def get_toolset_package_readme(
|
||||
name: str,
|
||||
_: TokenData = Depends(RoleChecker(allowed_roles=UserAuthority.USER)),
|
||||
):
|
||||
"""返回指定工具包的 README.md 内容(markdown 文本)。"""
|
||||
global_state_machine = ray_actor_hook("global_state_machine").global_state_machine
|
||||
content = await global_state_machine.get_toolset_package_readme.remote(name)
|
||||
if content is None:
|
||||
raise HTTPException(status_code=404, detail="README not found")
|
||||
return {"name": name, "content": content}
|
||||
|
||||
|
||||
# ─── Tool Management ───
|
||||
|
||||
@resource_router.get("/tool")
|
||||
@@ -256,7 +328,7 @@ async def _assert_toolset_owner_or_admin(
|
||||
toolset: Dict[str, Any], token_data: TokenData
|
||||
) -> None:
|
||||
"""校验 toolset 归属:非 owner 且非管理员则抛 403。"""
|
||||
from kilostar.utils.check_user.role_check import get_authority
|
||||
from kilostar.utils.access import get_authority
|
||||
|
||||
if toolset.get("owner_id") == token_data.user_id:
|
||||
return
|
||||
@@ -294,7 +366,7 @@ async def list_custom_toolsets(
|
||||
token_data: TokenData = Depends(RoleChecker(allowed_roles=UserAuthority.USER)),
|
||||
):
|
||||
"""列出工具组:支持按 category 过滤。USER 只能看到自己的+系统的;ADMIN 看全部。"""
|
||||
from kilostar.utils.check_user.role_check import get_authority
|
||||
from kilostar.utils.access import get_authority
|
||||
|
||||
global_state_machine = ray_actor_hook("global_state_machine").global_state_machine
|
||||
toolsets = await global_state_machine.list_custom_toolsets.remote()
|
||||
|
||||
Reference in New Issue
Block a user