diff --git a/backend/app/teams/wakeup.py b/backend/app/teams/wakeup.py index 7b13a8f0d..66967e619 100644 --- a/backend/app/teams/wakeup.py +++ b/backend/app/teams/wakeup.py @@ -1210,6 +1210,48 @@ def _team_progress_message(db: Session, run: TeamRun) -> Message | None: return None +_TASK_STATUS_LABELS = { + "pending": "待开始", + "bidding": "竞标中", + "in_progress": "进行中", + "review": "已提交,待验收", + "rework": "返工中", + "done": "已完成", + "cancelled": "已取消", + "escalated": "已升级,需人工处理", +} + + +def _task_status_label(task: TeamTask) -> str: + report = task.report_json if isinstance(task.report_json, dict) else {} + if task.status == "escalated" and bool(report.get("needs_input")): + return "等待人工补充信息" + return _TASK_STATUS_LABELS.get(task.status, task.status) + + +def _member_task_status_entries( + db: Session, tasks: list[TeamTask] +) -> list[dict[str, str | None]]: + agent_ids = {task.assignee_agent_id for task in tasks if task.assignee_agent_id} + names: dict[str, str] = {} + if agent_ids: + profiles = db.exec( + select(AgentProfile).where(AgentProfile.id.in_(agent_ids)) + ).all() + names = {profile.id: profile.name for profile in profiles} + return [ + { + "task_id": task.id, + "title": task.title, + "assignee_agent_id": task.assignee_agent_id, + "assignee_name": names.get(task.assignee_agent_id or ""), + "status": task.status, + "status_label": _task_status_label(task), + } + for task in tasks + ] + + def _update_team_run_progress_message( db: Session, run: TeamRun, @@ -1261,18 +1303,27 @@ def _update_team_run_progress_message( ) if current_phase == "completed" and resolved_phase != "completed": return message + task_entries = _member_task_status_entries(db, tasks) + if resolved_phase in {"collecting", "synthesizing"} and task_entries: + # 逐成员状态:让交付经理在团队会话里直接看到谁完成了、谁卡在验收或等人 (#229) + status_lines = [ + f"- {entry['assignee_name'] or '未指派'}|{entry['title']}:{entry['status_label']}" + for entry in task_entries + ] + content = "\n".join([content, *status_lines]) metadata["team_progress"] = { "phase": resolved_phase, "completed_tasks": completed, "total_tasks": total, "status_text": status_text, + "tasks": task_entries, } message.content = content message.metadata_json = metadata db.add(message) session = db.get(ChatSession, run.tl_session_id) if session is not None: - session.summary = f"最近回复:{content[:120]}" + session.summary = f"最近回复:{content.splitlines()[0][:120]}" session.updated_at = utc_now() db.add(session) return message diff --git a/backend/tests/test_teams_api.py b/backend/tests/test_teams_api.py index 747225783..c1896bab9 100644 --- a/backend/tests/test_teams_api.py +++ b/backend/tests/test_teams_api.py @@ -695,13 +695,89 @@ def test_team_progress_message_updates_after_each_member_result() -> None: db.commit() db.refresh(progress_message) - assert progress_message.content == "已收到 1/2 项成员回复。" - assert progress_message.metadata_json["team_progress"] == { - "phase": "collecting", - "completed_tasks": 1, - "total_tasks": 2, - "status_text": "正在等待其他成员完成", - } + assert progress_message.content == ( + "已收到 1/2 项成员回复。\n" + "- Worker|查询请假制度:已完成\n" + "- Worker2|查询办公用品制度:进行中" + ) + progress = progress_message.metadata_json["team_progress"] + assert progress["phase"] == "collecting" + assert progress["completed_tasks"] == 1 + assert progress["total_tasks"] == 2 + assert progress["status_text"] == "正在等待其他成员完成" + assert [ + (entry["assignee_name"], entry["status"], entry["status_label"]) + for entry in progress["tasks"] + ] == [ + ("Worker", "done", "已完成"), + ("Worker2", "in_progress", "进行中"), + ] + + +def test_team_progress_message_labels_review_and_needs_input_tasks() -> None: + with _test_session() as db: + team = _seed_team(db) + tl_session = ChatSession( + id="session-progress-review-tl", + tenant_id=team.tenant_id, + user_id="user_admin", + agent_id="agent_tl", + team_id=team.id, + title=f"团队 {team.name} · TL 对话", + ) + run = TeamRun( + id="team-run-progress-review", + team_id=team.id, + tenant_id=team.tenant_id, + tl_session_id=tl_session.id, + source_turn_id="source-turn-progress-review", + created_by_user_id="user_admin", + status="running", + ) + progress_message = Message( + id="message-progress-review", + tenant_id=team.tenant_id, + session_id=tl_session.id, + role="assistant", + content="已完成 2 个团队任务的拆分与派发。", + metadata_json={"team_run_id": run.id}, + ) + tasks = [ + TeamTask( + team_id=team.id, + tenant_id=team.tenant_id, + team_run_id=run.id, + title="输出迁移方案", + status="review", + assignee_agent_id="agent_worker", + ), + TeamTask( + team_id=team.id, + tenant_id=team.tenant_id, + team_run_id=run.id, + title="核对合同条款", + status="escalated", + assignee_agent_id="agent_worker2", + report_json={"needs_input": True}, + ), + ] + db.add(tl_session) + db.add(run) + db.add(progress_message) + db.add_all(tasks) + db.commit() + + wakeup._update_team_run_progress_message(db, run, tasks) + db.commit() + + db.refresh(progress_message) + assert progress_message.content == ( + "已收到 1/2 项成员回复。\n" + "- Worker|输出迁移方案:已提交,待验收\n" + "- Worker2|核对合同条款:等待人工补充信息" + ) + db.refresh(tl_session) + assert tl_session.summary == "最近回复:已收到 1/2 项成员回复。" def test_tl_chat_rejects_cyclic_dependency_graph(monkeypatch: pytest.MonkeyPatch) -> None: @@ -953,13 +1029,15 @@ def test_planner_member_completion_skips_review_and_enqueues_one_synthesis( assert task.report_json["full_reply"] == "市场分析完成" assert run.status == "synthesizing" db.refresh(progress_message) - assert progress_message.content == "已收到全部 1 项成员回复。" - assert progress_message.metadata_json["team_progress"] == { - "phase": "synthesizing", - "completed_tasks": 1, - "total_tasks": 1, - "status_text": "正在整理答案", - } + assert progress_message.content == ( + "已收到全部 1 项成员回复。\n- Worker|完成市场分析:已完成" + ) + progress = progress_message.metadata_json["team_progress"] + assert progress["phase"] == "synthesizing" + assert progress["completed_tasks"] == 1 + assert progress["total_tasks"] == 1 + assert progress["status_text"] == "正在整理答案" + assert [entry["status_label"] for entry in progress["tasks"]] == ["已完成"] assert db.exec( select(TeamWakeEvent).where(TeamWakeEvent.trigger_type == "task_report") ).all() == []