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:
@@ -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=True:task 入口 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"}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user