feat: 任务间互发消息(API + agent 标记块,不中断任务)

同项目任务互发消息、对方做完可回消息:API POST /tasks/<目标>/messages
(from_task_id,目标项目 create_task + 发送项目 read 鉴权)与 agent
回合输出标记块(worker 回合后解析投递,不用联网、不用凭证,本地
与 Docker 都通)。投递不中断任务:对方运行中/排队中排成后续消息
(轮次边界执行,direct 优先);对方终态则续跑复活(resume_message +
queued),A→B→A 往返不受回合时序影响。拒绝项:自己、跨项目、goal
目标、已合并、复活位被占;每轮最多 5 块,仅 completed 轮次投递;
目标执行时配额门照常生效。控制台后续消息列显示发送方,新增给
其他任务发消息框(落已代理的 /tasks 下,不用改 nginx)。

测试:新增 test_peer_messages(15)、test_peer_messages_server(8)、
test_worker_peer(4);全量 cloud 654、host 64、deploy 19 全过(cloud 4 跳过)。
This commit is contained in:
host committed 2026-10-27 14:12:28 +08:00
1 parent 4901a1090b
commit ef30558f7c
11 files changed
+1171 -23

No files matched your search

+55 -2
View File
@@ -137,6 +137,9 @@ from cloud.cost import CostStore, estimate_tokens, price_usd_micros, sum_usage
from cloud.db import connect as _db_connect
from cloud.logging_config import get_logger, log_event, setup_logging
from cloud.models import ModelRouter, is_fallbackable, record_fallback
from cloud.peer_messages import deliver_peer_message as _deliver_peer_message
from cloud.peer_messages import peer_message_prefix as _peer_prefix
from cloud.peer_messages import row_get as _row_get
from cloud.pr import PrSkipped, PrStepError
from cloud.pr_exec import (claim_pr_job, execute_task_pr, finish_pr_job)
from cloud.queue import budget_exceeded, ready_tasks
@@ -1672,7 +1675,8 @@ class Worker:
if cur.rowcount != 1:
return None
return {"id": row["id"], "body": row["body"],
"mode": row["mode"]}
"mode": row["mode"],
"sender_task_id": _row_get(row, "sender_task_id")}
except Exception:
return None
finally:
@@ -1707,6 +1711,46 @@ class Worker:
except Exception:
pass
def _deliver_peer_messages(self, task, result):
"""投递本轮 agent 输出里的互发消息标记块(见 peer_messages)。
只投递 completed 轮次的块:失败 / 超时 / 取消 / 配额中断
的轮次不投递;任务执行期间被取消时也不投递。投递本身
不中断任何任务:对方运行中/排队中排成后续消息(轮次边
界执行),对方终态则续跑复活;拒绝(自己、跨项目、
goal 目标、已合并、复活位被占等)只记审计,不炸 run_one。
投递不查发送方配额(发送本身不花钱),目标任务执行时
的配额门照常生效——互发消息不构成配额绕过。"""
if not result or result.get("status") != "completed":
return
if self._was_cancelled(task["id"]):
return
messages = result.get("task_messages") or []
if not messages:
return
delivered = 0
rejected = []
for msg in messages:
try:
conn = _db_connect(self.db_path)
try:
status, detail = _deliver_peer_message(
conn, task["id"], msg["to_task_id"],
msg["body"], msg["mode"])
finally:
conn.close()
except Exception as exc:
status, detail = "rejected", "delivery error: %s" % exc
if status in ("delivered", "revived"):
delivered += 1
else:
rejected.append("%s: %s" % (msg["to_task_id"], detail))
detail = "%s: delivered=%d rejected=%d" % (
task["id"], delivered, len(rejected))
if rejected:
detail += " (" + "; ".join(rejected) + ")"
self._audit(task["project_id"], "worker_peer_messages", detail)
def _run_attempt(self, task, quota_check=None):
"""单次执行尝试(不含认领、key 门、重试、成本与最终审计)。
@@ -2121,6 +2165,7 @@ class Worker:
result, error = self._run_fork(task, quota_check=_quota_hit)
usage_total = sum_usage(usage_total,
(result or {}).get("usage"))
self._deliver_peer_messages(task, result)
if (result or {}).get("status") == "quota_exceeded":
quota_error = self._quota_exceeded(task, usage_total) or \
"quota_exceeded: 本月配额已用完"
@@ -2137,6 +2182,7 @@ class Worker:
break
result, error = self._run_attempt(task, quota_check=_quota_hit)
usage_total = sum_usage(usage_total, (result or {}).get("usage"))
self._deliver_peer_messages(task, result)
if (result or {}).get("status") == "quota_exceeded":
# 轮询掐断:进程已被杀,配额原因以现查为准。
quota_error = self._quota_exceeded(task, usage_total) or \
@@ -2188,6 +2234,7 @@ class Worker:
result, error = self._run_attempt(task, quota_check=_quota_hit)
usage_total = sum_usage(usage_total,
(result or {}).get("usage"))
self._deliver_peer_messages(task, result)
if (result or {}).get("status") == "quota_exceeded":
quota_error = self._quota_exceeded(task, usage_total) or \
"quota_exceeded: 本月配额已用完"
@@ -2239,11 +2286,17 @@ class Worker:
quota_stop = True
break
fu_task = dict(task)
fu_task["prompt"] = fu["body"]
# 任务间消息的接收方提示:带发送方 id,接收方
# agent 凭此知道回给哪个任务;用户后续消息无前缀。
fu_prompt = fu["body"]
if fu.get("sender_task_id"):
fu_prompt = _peer_prefix(fu["sender_task_id"]) + fu_prompt
fu_task["prompt"] = fu_prompt
fu_result, fu_error = self._run_attempt(
fu_task, quota_check=_quota_hit)
usage_total = sum_usage(
usage_total, (fu_result or {}).get("usage"))
self._deliver_peer_messages(task, fu_result)
if (fu_result or {}).get("status") == "quota_exceeded":
self._restore_followup(fu["id"])
quota_error = self._quota_exceeded(task, usage_total) or \