feat: 新增工具插件、系统日志、workflow配置及前端优化

1. 新增工具插件(edit_file, python_executor, search_file, shell_executor, write_file)
2. 新增系统事件日志模块和API
3. 新增workflow配置文件和详情API
4. 前端增加SSE、错误边界、设置引导等组件
5. 优化认证加密、速率限制、配置加载等工具模块
6. 删除废弃的cluster和health API
7. 补充单元测试和集成测试

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-03 07:34:43 +00:00
parent f04fef916f
commit a53ffebe0e
57 changed files with 2804 additions and 271 deletions
+86 -6
View File
@@ -66,7 +66,23 @@ async def get_workflow_list(
@workflow_router.get("/sse/{trace_id}")
async def get_workflow_sse(trace_id: str, request: Request):
async def get_workflow_sse(
trace_id: str,
request: Request,
token_data: TokenData = Depends(Accessor.get_current_user),
):
"""SSE 事件流。
鉴权走标准 ``Authorization: Bearer`` 头(前端用 fetch-based SSE
token 不进 URL)。校验该 trace_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")
global_workflow_manager = ray_actor_hook(
"global_workflow_manager"
).global_workflow_manager
@@ -88,7 +104,18 @@ async def get_workflow_sse(trace_id: str, request: Request):
@workflow_router.post("/reply/{trace_id}")
async def post_workflow_reply(trace_id: str, request: Request):
async def post_workflow_reply(
trace_id: str,
request: Request,
token_data: TokenData = Depends(Accessor.get_current_user),
):
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")
data = await request.json()
reply_msg = data.get("message", "")
global_workflow_manager = ray_actor_hook(
@@ -106,10 +133,24 @@ async def get_workflow_detail(
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")
context = await postgres_database.get_workflow_context.remote(trace_id)
steps = context.work_link if context and hasattr(context, "work_link") else []
work_link = (
context.work_link if context and hasattr(context, "work_link") else []
)
workflow_log = (
context.workflow_log if context and hasattr(context, "workflow_log") else []
)
workflow_pointer = (
context.workflow_pointer
if context and getattr(context, "workflow_pointer", None) is not None
else 0
)
steps = _merge_runtime_status(work_link, workflow_log)
return {
"trace_id": trace_id,
@@ -117,10 +158,49 @@ async def get_workflow_detail(
"status": wf.status,
"command": wf.command,
"steps": steps,
"current_step": workflow_pointer,
"context_blackboard": context.blackboard if context else {},
}
def _merge_runtime_status(work_link: list, workflow_log: list) -> list:
"""把运行期状态从 ``workflow_log`` 反推并 merge 到每个静态 step 上。
``work_link`` 是 step 的**静态定义**(名字 / node 类型 / action),不含运行期
状态;运行期状态散落在 ``workflow_log`` 里——其结构为::
[{"<step_index>": [timestamp, status, message]}, ...]
同一 step 可能出现多条(working → completed),取**最后一条**的 status 作为
该 step 当前状态。没有日志记录的 step 视为 ``pending``。
前端 ``WorkflowDiagram`` 依赖每个 step 的 ``status`` 字段着色,这个拼装让
后端真正把运行期状态喂过去。
"""
# step_index -> 最新 status
latest_status: dict[int, str] = {}
for entry in workflow_log or []:
if not isinstance(entry, dict):
continue
for key, payload in entry.items():
try:
idx = int(key)
except (ValueError, TypeError):
continue
if isinstance(payload, (list, tuple)) and len(payload) >= 2:
latest_status[idx] = payload[1]
merged = []
for i, step in enumerate(work_link or []):
step_copy = dict(step) if isinstance(step, dict) else {}
# step 自带的 step 字段优先,否则用位置索引
step_idx = step_copy.get("step")
lookup_idx = (step_idx - 1) if isinstance(step_idx, int) else i
step_copy["status"] = latest_status.get(lookup_idx, "pending")
merged.append(step_copy)
return merged
@workflow_router.post("/{trace_id}/resume")
async def resume_workflow(
trace_id: str,
@@ -151,9 +231,9 @@ async def resume_workflow(
from kilostar.core.work.workflow.workflow_engine import run_workflow_task
# workflow_data 在 resume 路径上不会被使用(hydrate 会走 resume 分支),
# 这里给个空 dict 占位即可
run_workflow_task.remote({}, trace_id)
# resume_only=Truetask 入口 hydrate 失败会 fail-fast,绝不 fall through
# 到"全新模式空跑"。workflow_data 在 resume 路径上不会被使用,传空 dict 占位
run_workflow_task.remote({}, trace_id, resume_only=True)
return {"trace_id": trace_id, "status": "resuming"}