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:
2026-06-17 05:20:00 +00:00
parent 9b73ae4db4
commit 6d658b4f4d
74 changed files with 2591 additions and 1308 deletions
@@ -35,6 +35,8 @@ from kilostar.core.postgres_database.model.tool_config import ToolConfigModel
from kilostar.core.postgres_database.model.custom_toolset import CustomToolsetModel
from kilostar.core.postgres_database.model.system_event_log import SystemEventLog
from kilostar.core.postgres_database.model.persona_template import PersonaTemplate
from kilostar.core.postgres_database.model.org_task import OrgTask
from kilostar.core.postgres_database.model.org_task_event import OrgTaskEvent
# 兼容旧代码的别名
Provider = ProviderModel
@@ -65,5 +67,7 @@ __all__ = [
"CustomToolsetModel",
"SystemEventLog",
"PersonaTemplate",
"OrgTask",
"OrgTaskEvent",
"AgentType",
]
@@ -0,0 +1,38 @@
from sqlalchemy import String, DateTime, Integer, func, Text
from sqlalchemy.orm import Mapped, mapped_column
from sqlalchemy.dialects.postgresql import JSONB
from .base import BaseDataModel
class OrgTask(BaseDataModel):
__tablename__ = "org_task"
id: Mapped[int] = mapped_column(
Integer, primary_key=True, autoincrement=True
)
task_id: Mapped[str] = mapped_column(
String(64), unique=True, index=True, comment="外部任务 IDUUID"
)
org_name: Mapped[str] = mapped_column(
String(128), index=True, comment="所属组织/插件名"
)
status: Mapped[str] = mapped_column(
String(20), index=True, default="pending",
comment="pending/running/done/error"
)
description: Mapped[str] = mapped_column(
Text, comment="任务描述"
)
result: Mapped[str | None] = mapped_column(
Text, nullable=True, comment="最终结果"
)
context: Mapped[dict | None] = mapped_column(
JSONB, nullable=True, comment="调用上下文"
)
created_at: Mapped[str] = mapped_column(
DateTime(timezone=True), server_default=func.now(), index=True
)
updated_at: Mapped[str] = mapped_column(
DateTime(timezone=True), server_default=func.now(),
onupdate=func.now()
)
@@ -0,0 +1,25 @@
from sqlalchemy import String, DateTime, Integer, func, Text, ForeignKey
from sqlalchemy.orm import Mapped, mapped_column
from sqlalchemy.dialects.postgresql import JSONB
from .base import BaseDataModel
class OrgTaskEvent(BaseDataModel):
__tablename__ = "org_task_event"
id: Mapped[int] = mapped_column(
Integer, primary_key=True, autoincrement=True
)
task_id: Mapped[str] = mapped_column(
String(64), index=True, comment="关联的 org_task.task_id"
)
event_type: Mapped[str] = mapped_column(
String(30), index=True,
comment="log/step/artifact/approval_request/done/error"
)
payload: Mapped[dict | None] = mapped_column(
JSONB, nullable=True, comment="事件负载"
)
created_at: Mapped[str] = mapped_column(
DateTime(timezone=True), server_default=func.now(), index=True
)
@@ -0,0 +1,119 @@
from __future__ import annotations
from typing import List, Optional
from sqlalchemy import select, desc, update
from sqlalchemy.ext.asyncio import async_sessionmaker, AsyncSession
from kilostar.core.postgres_database.model.org_task import OrgTask
from kilostar.core.postgres_database.model.org_task_event import OrgTaskEvent
from kilostar.core.postgres_database.database_exception import database_exception
class OrgTaskDatabase:
def __init__(self, async_session_maker: async_sessionmaker[AsyncSession]):
self.async_session_maker = async_session_maker
@database_exception
async def create_task(
self,
task_id: str,
org_name: str,
description: str,
context: Optional[dict] = None,
) -> None:
async with self.async_session_maker() as session:
task = OrgTask(
task_id=task_id,
org_name=org_name,
description=description,
context=context,
status="pending",
)
session.add(task)
await session.commit()
@database_exception
async def update_status(
self, task_id: str, status: str, result: Optional[str] = None
) -> None:
async with self.async_session_maker() as session:
stmt = (
update(OrgTask)
.where(OrgTask.task_id == task_id)
.values(status=status, result=result)
)
await session.execute(stmt)
await session.commit()
@database_exception
async def get_task(self, task_id: str) -> Optional[dict]:
async with self.async_session_maker() as session:
stmt = select(OrgTask).where(OrgTask.task_id == task_id)
row = (await session.execute(stmt)).scalar_one_or_none()
if not row:
return None
return {
"task_id": row.task_id,
"org_name": row.org_name,
"status": row.status,
"description": row.description,
"result": row.result,
"context": row.context,
"created_at": str(row.created_at) if row.created_at else None,
"updated_at": str(row.updated_at) if row.updated_at else None,
}
@database_exception
async def list_tasks(
self, org_name: Optional[str] = None, limit: int = 50, offset: int = 0
) -> List[dict]:
async with self.async_session_maker() as session:
stmt = select(OrgTask).order_by(desc(OrgTask.created_at))
if org_name:
stmt = stmt.where(OrgTask.org_name == org_name)
stmt = stmt.offset(offset).limit(limit)
rows = (await session.execute(stmt)).scalars().all()
return [
{
"task_id": r.task_id,
"org_name": r.org_name,
"status": r.status,
"description": r.description,
"created_at": str(r.created_at) if r.created_at else None,
}
for r in rows
]
@database_exception
async def insert_event(
self, task_id: str, event_type: str, payload: Optional[dict] = None
) -> None:
async with self.async_session_maker() as session:
evt = OrgTaskEvent(
task_id=task_id, event_type=event_type, payload=payload
)
session.add(evt)
await session.commit()
@database_exception
async def query_events(
self, task_id: str, limit: int = 200
) -> List[dict]:
async with self.async_session_maker() as session:
stmt = (
select(OrgTaskEvent)
.where(OrgTaskEvent.task_id == task_id)
.order_by(OrgTaskEvent.created_at)
.limit(limit)
)
rows = (await session.execute(stmt)).scalars().all()
return [
{
"id": r.id,
"task_id": r.task_id,
"event_type": r.event_type,
"payload": r.payload,
"created_at": str(r.created_at) if r.created_at else None,
}
for r in rows
]
+30 -1
View File
@@ -15,7 +15,7 @@
import os
import asyncio
from kilostar.utils.standalone_proxy import actor_class
from kilostar.utils.ray_compat import actor_class
from kilostar.utils.settings import get_settings
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
from sqlalchemy.orm import sessionmaker
@@ -44,6 +44,8 @@ from kilostar.core.postgres_database.model.tool_config import ToolConfigModel
from kilostar.core.postgres_database.model.custom_toolset import CustomToolsetModel
from kilostar.core.postgres_database.model.system_event_log import SystemEventLog
from kilostar.core.postgres_database.model.persona_template import PersonaTemplate
from kilostar.core.postgres_database.model.org_task import OrgTask
from kilostar.core.postgres_database.model.org_task_event import OrgTaskEvent
from .module.individual import IndividualDatabase
from .module.user import AuthDatabase
@@ -56,6 +58,7 @@ from .module.tool_config import ToolConfigDatabase
from .module.custom_toolset import CustomToolsetDatabase
from .module.system_event_log import SystemEventLogDatabase
from .module.persona_template import PersonaTemplateDatabase
from .module.org_task import OrgTaskDatabase
@actor_class
@@ -89,6 +92,7 @@ class PostgresDatabase:
self._custom_toolset_database = CustomToolsetDatabase(self.async_session_maker)
self._system_event_log_database = SystemEventLogDatabase(self.async_session_maker)
self._persona_template_database = PersonaTemplateDatabase(self.async_session_maker)
self._org_task_database = OrgTaskDatabase(self.async_session_maker)
self.ready_event = asyncio.Event()
@@ -458,3 +462,28 @@ class PostgresDatabase:
async def delete_template(self, template_id: str):
await self.ready_event.wait()
return await self._persona_template_database.delete_template(template_id)
# Org Task Database Methods
async def create_org_task(self, task_id: str, org_name: str, description: str, context=None):
await self.ready_event.wait()
return await self._org_task_database.create_task(task_id, org_name, description, context)
async def update_org_task_status(self, task_id: str, status: str, result=None):
await self.ready_event.wait()
return await self._org_task_database.update_status(task_id, status, result)
async def get_org_task(self, task_id: str):
await self.ready_event.wait()
return await self._org_task_database.get_task(task_id)
async def list_org_tasks(self, org_name=None, limit=50, offset=0):
await self.ready_event.wait()
return await self._org_task_database.list_tasks(org_name, limit, offset)
async def insert_org_event(self, task_id: str, event_type: str, payload=None):
await self.ready_event.wait()
return await self._org_task_database.insert_event(task_id, event_type, payload)
async def query_org_events(self, task_id: str, limit=200):
await self.ready_event.wait()
return await self._org_task_database.query_events(task_id, limit)