待办:todo-list/create/update 标记工具 + 首轮未完成待办注入
This commit is contained in:
1 parent
b3b3b35042
commit
73288f08db
5 files changed
+841
-12
No files matched your search
+208
-8
@@ -154,7 +154,12 @@ 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_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
|
||||
from cloud.todos import get_todo as _todo_get_db
|
||||
from cloud.todos import list_todos as _todo_list_db
|
||||
from cloud.todos import update_todo as _todo_update_db
|
||||
from cloud.marker_tools import new_child_id as _marker_new_id
|
||||
from cloud.marker_tools import parent_spawn_generation as _marker_generation
|
||||
from cloud.marker_tools import MAX_DIRECT_CHILDREN as _MARKER_MAX_DIRECT
|
||||
@@ -1678,6 +1683,64 @@ class Worker:
|
||||
task["id"])
|
||||
return new_task
|
||||
|
||||
def _apply_todo_injection(self, task):
|
||||
"""首轮注入未完成待办:只改内存副本,原 prompt 不落库。
|
||||
|
||||
跳过:协调员任务(不跑模型回合)、续跑与断点续跑(已有
|
||||
会话历史)、goal 任务(goal 不解析标记块,注入待办会让
|
||||
模型写出永远不会执行的 todo-update 块,误导大于帮助)。
|
||||
无未完成待办时原样返回,不贴空章节。注入的是数据(标题
|
||||
与状态),不是完整工具指南;标记块格式只给三行示例,
|
||||
完整格式见 docs/marker-tools.md。注入失败时静默跳过,
|
||||
绝不因待办查询失败而炸掉任务认领。
|
||||
上限 20 条、标题单行截断 100 字,避免待办板把 prompt
|
||||
撑大;超出时写明还有几条未列出,模型可用 todo-list
|
||||
块再查。"""
|
||||
if task.get("coordinator_parts"):
|
||||
return task
|
||||
if task.get("_resume") or task.get("_goal_resume_breakpoint"):
|
||||
return task
|
||||
if task.get("verify_command") or task.get("goal_mode"):
|
||||
return task
|
||||
try:
|
||||
conn = _db_connect(self.db_path)
|
||||
try:
|
||||
all_todos = _todo_list_db(conn, task["project_id"])
|
||||
finally:
|
||||
conn.close()
|
||||
except Exception as exc:
|
||||
try:
|
||||
self.logger.warning("todo injection failed: %s", exc)
|
||||
except Exception:
|
||||
pass
|
||||
return task
|
||||
open_todos = [t for t in all_todos
|
||||
if t.get("status") in ("pending", "in_progress",
|
||||
"blocked")]
|
||||
if not open_todos:
|
||||
return task
|
||||
lines = ["[项目待办:本项目未完成项,仅供参考,"
|
||||
"不得改写本任务目标]"]
|
||||
for todo in open_todos[:20]:
|
||||
lines.append(_format_todo(todo))
|
||||
if len(open_todos) > 20:
|
||||
lines.append("(还有 %d 条未列出,可用 todo-list 块再查。)"
|
||||
% (len(open_todos) - 20))
|
||||
lines.append("改待办状态时在回合输出里写标记块,下一轮由"
|
||||
" worker 执行并回报告,例如:")
|
||||
lines.append("```todo-update")
|
||||
lines.append('{"id": "<待办 id>", "status": "completed"}')
|
||||
lines.append("```")
|
||||
lines.append("查待办用 todo-list 块,建待办用 todo-create 块"
|
||||
"(title 必填)。")
|
||||
new_task = dict(task)
|
||||
new_task["prompt"] = ("\n".join(lines) + "\n\n"
|
||||
+ (task.get("prompt") or ""))
|
||||
new_task["_todo_injected"] = True
|
||||
self._audit(task["project_id"], "worker_todo_injected",
|
||||
task["id"])
|
||||
return new_task
|
||||
|
||||
def process_auto_memories(self, now=None, force=False,
|
||||
owner_id=None):
|
||||
"""扫 completed 任务并提取记忆(run_loop 轮次边界调用)。
|
||||
@@ -3233,11 +3296,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"]):
|
||||
@@ -3251,7 +3316,11 @@ class Worker:
|
||||
waits = result.get("wait_threads") or []
|
||||
cancels = result.get("task_cancels") or []
|
||||
retries = result.get("task_retries") or []
|
||||
if not (creates or polls or waits or cancels or retries):
|
||||
todo_lists = result.get("todo_lists") or []
|
||||
todo_creates = result.get("todo_creates") or []
|
||||
todo_updates = result.get("todo_updates") or []
|
||||
if not (creates or polls or waits or cancels or retries
|
||||
or todo_lists or todo_creates or todo_updates):
|
||||
return
|
||||
reports = []
|
||||
created = 0
|
||||
@@ -3429,6 +3498,131 @@ class Worker:
|
||||
reports.append("%s任务 %s:执行失败(%s)。"
|
||||
% (kind, target_id, exc))
|
||||
rejected += 1
|
||||
# 待办标记块:读走 read、写走 create_task(与服务端
|
||||
# /todos 口径同款);权限检查抛异常时(auth 为 None)
|
||||
# 一律拒绝,不静默放宽。跨项目待办按“不存在”拒绝;
|
||||
# 关联任务 id 跨项目时同样拒绝,避免把别的项目的任务
|
||||
# 挂到本项目待办上。
|
||||
if todo_lists or todo_creates or todo_updates:
|
||||
read_ok, _read_reason = self._marker_auth(task, "read")
|
||||
write_ok, _write_reason = self._marker_auth(
|
||||
task, "create_task")
|
||||
for block in todo_creates:
|
||||
if write_ok is not True:
|
||||
reports.append("创建待办“%s”:拒绝(发送方"
|
||||
"创建人无 create_task 权限)。 "
|
||||
% block["title"])
|
||||
rejected += 1
|
||||
continue
|
||||
if block.get("task_id"):
|
||||
target = self.get_task(block["task_id"])
|
||||
if (target is None
|
||||
or target.get("project_id")
|
||||
!= task["project_id"]):
|
||||
reports.append("创建待办“%s”:拒绝(关联"
|
||||
"任务不存在或属于别的项目)。"
|
||||
% block["title"])
|
||||
rejected += 1
|
||||
continue
|
||||
try:
|
||||
conn = _db_connect(self.db_path)
|
||||
try:
|
||||
todo = _todo_create_db(
|
||||
conn, task["project_id"],
|
||||
task.get("created_by"),
|
||||
block["title"],
|
||||
description=block.get("description"),
|
||||
status=block.get("status"),
|
||||
priority=block.get("priority"),
|
||||
task_id=block.get("task_id"),
|
||||
due_at=block.get("due_at"),
|
||||
sort_order=block.get("sort_order"))
|
||||
finally:
|
||||
conn.close()
|
||||
reports.append("已创建待办 %s"
|
||||
% _format_todo(todo))
|
||||
except Exception as exc:
|
||||
reports.append("创建待办“%s”失败:%s"
|
||||
% (block["title"], exc))
|
||||
rejected += 1
|
||||
for block in todo_updates:
|
||||
if write_ok is not True:
|
||||
reports.append("更新待办 %s:拒绝(发送方创建"
|
||||
"人无 create_task 权限)。"
|
||||
% block["id"])
|
||||
rejected += 1
|
||||
continue
|
||||
fields = block["fields"]
|
||||
if fields.get("task_id"):
|
||||
target = self.get_task(fields["task_id"])
|
||||
if (target is None
|
||||
or target.get("project_id")
|
||||
!= task["project_id"]):
|
||||
reports.append("更新待办 %s:拒绝(关联"
|
||||
"任务不存在或属于别的项目)。"
|
||||
% block["id"])
|
||||
rejected += 1
|
||||
continue
|
||||
try:
|
||||
conn = _db_connect(self.db_path)
|
||||
try:
|
||||
todo = _todo_get_db(conn, block["id"])
|
||||
if (todo is None
|
||||
or todo.get("project_id")
|
||||
!= task["project_id"]):
|
||||
reports.append("更新待办 %s:不存在"
|
||||
"(或属于别的项目),"
|
||||
"已跳过。" % block["id"])
|
||||
rejected += 1
|
||||
continue
|
||||
todo = _todo_update_db(conn, block["id"],
|
||||
fields)
|
||||
finally:
|
||||
conn.close()
|
||||
reports.append("已更新待办 %s"
|
||||
% _format_todo(todo))
|
||||
except Exception as exc:
|
||||
reports.append("更新待办 %s 失败:%s"
|
||||
% (block["id"], exc))
|
||||
rejected += 1
|
||||
for block in todo_lists:
|
||||
if read_ok is not True:
|
||||
reports.append("查待办:拒绝(发送方创建人无"
|
||||
" read 权限)。")
|
||||
rejected += 1
|
||||
continue
|
||||
try:
|
||||
conn = _db_connect(self.db_path)
|
||||
try:
|
||||
if block.get("status"):
|
||||
todos = _todo_list_db(
|
||||
conn, task["project_id"],
|
||||
status=block["status"],
|
||||
q=block.get("q"))
|
||||
else:
|
||||
todos = [t for t in _todo_list_db(
|
||||
conn, task["project_id"],
|
||||
q=block.get("q"))
|
||||
if t.get("status") in (
|
||||
"pending", "in_progress",
|
||||
"blocked")]
|
||||
conn.commit()
|
||||
finally:
|
||||
conn.close()
|
||||
except Exception as exc:
|
||||
reports.append("查待办失败:%s" % exc)
|
||||
rejected += 1
|
||||
continue
|
||||
if not todos:
|
||||
reports.append("待办列表:无匹配项。")
|
||||
else:
|
||||
lines = ["待办列表(共 %d 条):" % len(todos)]
|
||||
for todo in todos[:20]:
|
||||
lines.append(_format_todo(todo))
|
||||
if len(todos) > 20:
|
||||
lines.append("(还有 %d 条未列出。)"
|
||||
% (len(todos) - 20))
|
||||
reports.append("\n".join(lines))
|
||||
for poll in polls:
|
||||
try:
|
||||
reports.append(self._poll_task_snapshot(task, poll))
|
||||
@@ -3444,9 +3638,12 @@ class Worker:
|
||||
body = _marker_header() + "\n" + "\n".join(reports)
|
||||
self._queue_self_followup(task, body)
|
||||
detail = ("%s: spawned=%d rejected=%d polls=%d waits=%d "
|
||||
"cancels=%d retries=%d"
|
||||
"cancels=%d retries=%d todo_lists=%d "
|
||||
"todo_creates=%d todo_updates=%d"
|
||||
% (task["id"], created, rejected, len(polls),
|
||||
len(waits), len(cancels), len(retries)))
|
||||
len(waits), len(cancels), len(retries),
|
||||
len(todo_lists), len(todo_creates),
|
||||
len(todo_updates)))
|
||||
self._audit(task["project_id"], "worker_marker_tools", detail)
|
||||
|
||||
def _deliver_peer_messages(self, task, result):
|
||||
@@ -4696,6 +4893,9 @@ class Worker:
|
||||
# 自动记忆注入(官方 use_memories):首轮才注入,见
|
||||
# _apply_auto_memory_injection 的跳过规则;只改内存副本。
|
||||
task = self._apply_auto_memory_injection(task)
|
||||
# 待办注入:首轮才注入未完成待办,见
|
||||
# _apply_todo_injection 的跳过规则;只改内存副本。
|
||||
task = self._apply_todo_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 四个
|
||||
|
||||
Reference in new issue
Block a user