fix: delete agent 500 error + dynamic personality + deployment guide

- Fix delete agent 500: clean up FK records (agent_llm_logs, permissions,
  schedules, executions, team_members) and unbind goals/tasks before delete
- Remove hardcoded personality templates in Android, replace with dynamic
  system prompt generation from name + description
- Set promptSectionsEnabled=false to bypass PromptComposer for personality
- Add Tencent Cloud Linux deployment guide (Docker Compose)
- Accumulated backend service updates, frontend UI fixes, Android app changes

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-29 01:17:21 +08:00
parent 86b98865e3
commit beff3fac8d
1084 changed files with 117315 additions and 1281 deletions

View File

@@ -1,5 +1,7 @@
"""
Task API — 任务管理接口
Task API — 任务管理接口 (含原子认领 + 依赖图 + Agent 状态)
参考 Claude Code src/utils/tasks.ts 的 API 设计
"""
from fastapi import APIRouter, Depends, Query
from sqlalchemy.orm import Session
@@ -9,6 +11,7 @@ from datetime import datetime
import logging
from app.core.database import get_db
from app.core.task_system import TaskSystem, ClaimResult, AgentState, AgentStatus
from app.api.auth import get_current_user
from app.models.user import User
from app.services import goal_service
@@ -92,6 +95,42 @@ class TaskDependencyCheck(BaseModel):
pending_dependencies: List[str] = []
class ClaimTaskRequest(BaseModel):
agent_id: str = Field(..., description="认领任务的 Agent 标识")
check_busy: bool = Field(default=True, description="是否检查 Agent 忙碌状态")
class ClaimTaskResponse(BaseModel):
success: bool
reason: Optional[str] = None
task: Optional[TaskResponse] = None
busy_with_tasks: List[str] = []
blocked_by_tasks: List[str] = []
class BlockTaskRequest(BaseModel):
from_task_id: str = Field(..., description="阻塞方任务ID")
to_task_id: str = Field(..., description="被阻塞方任务ID")
class AgentStatusResponse(BaseModel):
agent_id: str
status: str # idle / busy
current_tasks: List[str] = []
class ReleaseTaskRequest(BaseModel):
agent_id: str = Field(..., description="释放任务的 Agent 标识")
class TaskCompleteRequest(BaseModel):
result: Optional[Dict[str, Any]] = None
class TaskFailRequest(BaseModel):
error_message: str = ""
# ──────────────────────────── Endpoints ────────────────────────────
@router.post("", response_model=TaskResponse, status_code=201)
@@ -307,3 +346,160 @@ async def retry_task(
error_message=str(e),
)
return goal_service.get_task(db, task_id)
# ════════════════════ 任务系统增强 (参考 Claude Code task_system) ════════════════════
def _get_task_system(db: Session) -> TaskSystem:
return TaskSystem(db)
@router.post("/{task_id}/claim", response_model=ClaimTaskResponse)
def claim_task(
task_id: str,
data: ClaimTaskRequest,
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db),
):
"""原子认领任务 (SELECT FOR UPDATE),检查依赖+Agent忙碌"""
ts = _get_task_system(db)
result = ts.claim_task(task_id=task_id, agent_id=data.agent_id, check_busy=data.check_busy)
return ClaimTaskResponse(
success=result.success,
reason=result.reason,
task=TaskResponse.model_validate(result.task) if result.task else None,
busy_with_tasks=result.busy_with_tasks,
blocked_by_tasks=result.blocked_by_tasks,
)
@router.post("/block", status_code=200)
def block_task(
data: BlockTaskRequest,
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db),
):
"""设置任务依赖: from_task 阻塞 to_task"""
ts = _get_task_system(db)
ok = ts.block_task(data.from_task_id, data.to_task_id)
if not ok:
from fastapi import HTTPException
raise HTTPException(404, "任务不存在")
return {"message": "ok", "from": data.from_task_id, "to": data.to_task_id}
@router.delete("/block", status_code=200)
def unblock_task(
data: BlockTaskRequest,
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db),
):
"""移除任务依赖"""
ts = _get_task_system(db)
ok = ts.unblock_task(data.from_task_id, data.to_task_id)
if not ok:
from fastapi import HTTPException
raise HTTPException(404, "任务不存在")
return {"message": "ok"}
@router.get("/agent/{agent_id}/status", response_model=AgentStatusResponse)
def get_agent_status(
agent_id: str,
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db),
):
"""获取 Agent 忙闲状态 (idle/busy)"""
ts = _get_task_system(db)
state = ts.get_agent_status(agent_id)
return AgentStatusResponse(
agent_id=state.agent_id,
status=state.status.value,
current_tasks=state.current_tasks,
)
@router.post("/{task_id}/release", status_code=200)
def release_task(
task_id: str,
data: ReleaseTaskRequest,
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db),
):
"""Agent 主动释放单个任务"""
ts = _get_task_system(db)
ok = ts.release_task(task_id, data.agent_id)
if not ok:
from fastapi import HTTPException
raise HTTPException(404, "任务不存在或不属于该 Agent")
return {"message": "ok", "task_id": task_id}
@router.post("/agent/{agent_id}/unassign", status_code=200)
def unassign_agent_tasks(
agent_id: str,
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db),
):
"""释放 Agent 所有未完成任务 (Agent 下线时调用)"""
ts = _get_task_system(db)
tasks = ts.unassign_agent_tasks(agent_id)
return {"message": "ok", "unassigned_count": len(tasks), "task_ids": [t.id for t in tasks]}
@router.post("/{task_id}/complete", response_model=TaskResponse)
def complete_task(
task_id: str,
data: TaskCompleteRequest = TaskCompleteRequest(),
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db),
):
"""标记任务完成 (自动检查被阻塞任务)"""
ts = _get_task_system(db)
task = ts.complete_task(task_id, result=data.result)
if not task:
from fastapi import HTTPException
raise HTTPException(404, "任务不存在")
return TaskResponse.model_validate(task)
@router.post("/{task_id}/fail", response_model=TaskResponse)
def fail_task(
task_id: str,
data: TaskFailRequest = TaskFailRequest(),
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db),
):
"""标记任务失败"""
ts = _get_task_system(db)
task = ts.fail_task(task_id, error_message=data.error_message)
if not task:
from fastapi import HTTPException
raise HTTPException(404, "任务不存在")
return TaskResponse.model_validate(task)
@router.get("/available/{goal_id}", response_model=List[TaskResponse])
def get_available_tasks(
goal_id: str,
limit: int = Query(default=10, ge=1, le=100),
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db),
):
"""获取目标下所有可执行任务 (依赖满足 + 未被认领)"""
ts = _get_task_system(db)
tasks = ts.get_next_available_tasks(goal_id, limit=limit)
return [TaskResponse.model_validate(t) for t in tasks]
@router.get("/{task_id}/blockers", response_model=List[TaskResponse])
def get_task_blockers(
task_id: str,
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db),
):
"""获取任务尚未完成的阻塞依赖"""
ts = _get_task_system(db)
blockers = ts.get_unresolved_blockers(task_id)
return [TaskResponse.model_validate(t) for t in blockers]