任务列表 task-list 标记 + 首轮项目上下文注入

This commit is contained in:
host committed 2026-10-30 05:11:06 +08:00
1 parent 73288f08db
commit 2fd73c1121
5 files changed
+510 -13

No files matched your search

+138 -10
View File
@@ -154,6 +154,7 @@ from cloud.marker_tools import check_control_gate as _marker_gate
from cloud.marker_tools import count_direct_spawn_children as _marker_count_direct
from cloud.marker_tools import fetch_task_snapshot as _marker_snapshot
from cloud.marker_tools import format_snapshot as _marker_format
from cloud.marker_tools import format_task as _format_task
from cloud.marker_tools import format_todo as _format_todo
from cloud.marker_tools import marker_report_header as _marker_header
from cloud.todos import create_todo as _todo_create_db
@@ -1741,6 +1742,80 @@ class Worker:
task["id"])
return new_task
def _apply_project_injection(self, task):
"""首轮注入项目上下文:只改内存副本,原 prompt/goal 不落库。
跳过:协调员任务(不跑模型回合)、续跑与断点续跑(已有
会话历史)。goal 任务不跳过——项目上下文是数据不是目标,
与自动记忆同款同时改内存里的 goal(执行器取 goal 优先
于 prompt,只改 prompt 的话 goal 任务看不到)。
贴三行数据:项目名、仓库(未绑定时明写未绑定,不编一个
地址)、环境(有 environment_id 时写环境名/分支/状态,
否则写无);再加一行 task-list 格式示例,让模型在不知道
任务 id 时也能查本项目任务。项目行查不到、查询失败时
原样返回,不贴半截上下文、不炸认领。"""
if task.get("coordinator_parts"):
return task
if task.get("_resume") or task.get("_goal_resume_breakpoint"):
return task
try:
conn = _db_connect(self.db_path)
try:
row = conn.execute(
"SELECT name, repo_url FROM projects WHERE id = ?",
(task["project_id"],)).fetchone()
finally:
conn.close()
except Exception as exc:
try:
self.logger.warning("project injection failed: %s", exc)
except Exception:
pass
return task
if row is None:
return task
try:
project_name = row["name"] or task["project_id"]
except (KeyError, IndexError, TypeError):
project_name = task["project_id"]
try:
repo_url = row["repo_url"] or None
except (KeyError, IndexError, TypeError):
repo_url = None
env_line = "无(使用默认仓库)"
env_id = task.get("environment_id")
if env_id:
env = self._fetch_environment(env_id)
if env is not None:
env_line = "%s(分支 %s,状态 %s)" % (
env.get("name") or env_id,
env.get("branch") or "未指定",
env.get("status") or "未知")
if env.get("repo_url"):
repo_url = env["repo_url"]
else:
env_line = "%s(查不到该环境)" % env_id
lines = ["[项目上下文]",
"项目:%s(id=%s)" % (project_name,
task["project_id"]),
"仓库:%s" % (repo_url or "未绑定"),
"环境:%s" % env_line,
"查本项目任务用 task-list 标记块,例如:",
"```task-list",
'{"status": "failed"}',
"```",
"(不传 status 时查全部状态并附各状态计数。)"]
prefix = "\n".join(lines)
new_task = dict(task)
new_task["prompt"] = prefix + "\n\n" + (task.get("prompt") or "")
if task.get("goal_mode") or task.get("verify_command"):
new_task["goal"] = prefix + "\n\n" + (
task.get("goal") or task.get("prompt") or "")
new_task["_project_injected"] = True
self._audit(task["project_id"], "worker_project_injected",
task["id"])
return new_task
def process_auto_memories(self, now=None, force=False,
owner_id=None):
"""扫 completed 任务并提取记忆(run_loop 轮次边界调用)。
@@ -3297,12 +3372,13 @@ class Worker:
只执行 completed 轮次的块;goal 任务与协调员任务不解析
(只收不发,与 peer 同款);父任务被取消时不执行。
执行顺序:创建 → 取消 → 重试 → 待办创建 → 待办更新 →
待办列表 → 轮询 → 等待——创建在前让同一轮刚创建的任务
立刻能被轮询,控制操作在快照之前让轮询/等待读到取消
或重试后的新状态;待办列表放在待办写操作之后,让同
一轮刚建/刚改的待办立刻能在列表里看到。全部报告拼
成一条系统后续消息排给父任务自己,由排空循环在轮次
边界跑下一轮;拒绝只记报告与审计,不炸 run_one。"""
待办列表 → 任务列表 → 轮询 → 等待——创建在前让同一轮
刚创建的任务立刻能被轮询,控制操作在快照之前让轮询/
等待读到取消或重试后的新状态;待办列表放在待办写操作
之后,让同一轮刚建/刚改的待办立刻能在列表里看到。
全部报告拼成一条系统后续消息排给父任务自己,由排空
循环在轮次边界跑下一轮;拒绝只记报告与审计,不炸
run_one。"""
if not result or result.get("status") != "completed":
return
if self._was_cancelled(task["id"]):
@@ -3319,8 +3395,10 @@ class Worker:
todo_lists = result.get("todo_lists") or []
todo_creates = result.get("todo_creates") or []
todo_updates = result.get("todo_updates") or []
task_lists = result.get("task_lists") or []
if not (creates or polls or waits or cancels or retries
or todo_lists or todo_creates or todo_updates):
or todo_lists or todo_creates or todo_updates
or task_lists):
return
reports = []
created = 0
@@ -3503,7 +3581,7 @@ class Worker:
# 一律拒绝,不静默放宽。跨项目待办按“不存在”拒绝;
# 关联任务 id 跨项目时同样拒绝,避免把别的项目的任务
# 挂到本项目待办上。
if todo_lists or todo_creates or todo_updates:
if todo_lists or todo_creates or todo_updates or task_lists:
read_ok, _read_reason = self._marker_auth(task, "read")
write_ok, _write_reason = self._marker_auth(
task, "create_task")
@@ -3623,6 +3701,52 @@ class Worker:
lines.append("(还有 %d 条未列出。)"
% (len(todos) - 20))
reports.append("\n".join(lines))
for block in task_lists:
if read_ok is not True:
reports.append("查任务列表:拒绝(发送方创建"
"人无 read 权限)。")
rejected += 1
continue
try:
conn = _db_connect(self.db_path)
try:
rows = conn.execute(
"SELECT * FROM tasks"
" WHERE project_id = ?"
" ORDER BY created_at DESC",
(task["project_id"],)).fetchall()
finally:
conn.close()
except Exception as exc:
reports.append("查任务列表失败:%s" % exc)
rejected += 1
continue
tasks = [_task_from_row(row) if not isinstance(
row, dict) else row for row in rows]
counts = {}
for t in tasks:
counts[t.get("status")] = counts.get(
t.get("status"), 0) + 1
if block.get("status"):
tasks = [t for t in tasks
if t.get("status") == block["status"]]
if block.get("q"):
ql = block["q"].lower()
tasks = [t for t in tasks
if ql in (t.get("prompt") or "").lower()]
if not tasks:
reports.append("任务列表:无匹配项。")
else:
lines = ["任务列表(共 %d 条,状态计数:%s):"
% (len(tasks), ", ".join(
"%s=%d" % kv for kv in sorted(
counts.items())))]
for t in tasks[:block["limit"]]:
lines.append(_format_task(t))
if len(tasks) > block["limit"]:
lines.append("(还有 %d 条未列出。)"
% (len(tasks) - block["limit"]))
reports.append("\n".join(lines))
for poll in polls:
try:
reports.append(self._poll_task_snapshot(task, poll))
@@ -3639,11 +3763,11 @@ class Worker:
self._queue_self_followup(task, body)
detail = ("%s: spawned=%d rejected=%d polls=%d waits=%d "
"cancels=%d retries=%d todo_lists=%d "
"todo_creates=%d todo_updates=%d"
"todo_creates=%d todo_updates=%d task_lists=%d"
% (task["id"], created, rejected, len(polls),
len(waits), len(cancels), len(retries),
len(todo_lists), len(todo_creates),
len(todo_updates)))
len(todo_updates), len(task_lists)))
self._audit(task["project_id"], "worker_marker_tools", detail)
def _deliver_peer_messages(self, task, result):
@@ -4896,6 +5020,10 @@ class Worker:
# 待办注入:首轮才注入未完成待办,见
# _apply_todo_injection 的跳过规则;只改内存副本。
task = self._apply_todo_injection(task)
# 项目注入:首轮才注入项目上下文,见
# _apply_project_injection 的跳过规则;只改内存副本;
# 最后贴,排在待办与记忆之前(最通用上下文在最前)。
task = self._apply_project_injection(task)
self._audit(task["project_id"], "worker_claim",
"%s executor=%s" % (task_id, self._executor_name()))
# log_event 只接受 task_id/project_id/status/message 四个