feat: 旧任务继续会话——终端任务带新消息 + 全部选项同一会话续跑
server:POST /tasks/{id}/continue(终端非 merged 任务重排队,
resume_message 列存本轮消息,不传选项沿用原值,plan/goal 冲突
与新建一致,goal_mode=1 时转新 goal 语义;CAS 防并发改状态,
审计 task_continued);tasks.resume_message 三处迁移 + PG 建表。
worker:认领后本轮 prompt 换成续跑消息(只换内存副本),
goal 续跑走 goal-resume 而不是 goal,跑完清 resume_message
(进程被杀时不清,下次认领重跑同一条消息)。
host:goal-resume --goal-text(旧任务接续 goal 模式:跳过状态
与记录守卫,旧线程上从第 1 轮开新目标循环,新语义不限轮次;
无线程时拒绝)。
sandbox:Local/Docker goal_resume_task + popen goal-resume kind;
附带 popen goal 命令改成新语义(无验收不传 --verify,轮次 0
原样传,此前 goal 任务到本地 worker 会直接报错)。
console:详情页终端非 merged 任务出继续会话表单(消息 +
模型/强度/权限/计划/goal,模型下拉复用中转列表)。
测试:新增 test_task_continue.py 11 个 + test_host_continue.py 2 个。
This commit is contained in:
1 parent
cf293914d9
commit
3a4997d346
9 files changed
+823
-20
No files matched your search
+50
-9
@@ -247,6 +247,8 @@ def _task_from_row(row):
|
||||
"forked_from": _col("forked_from"),
|
||||
# 环境:老行缺列时 None = 不指定环境,走项目仓库路径。
|
||||
"environment_id": _col("environment_id"),
|
||||
# 续跑消息:老行缺列时 None = 没有待续跑消息。
|
||||
"resume_message": _col("resume_message"),
|
||||
}
|
||||
|
||||
|
||||
@@ -580,7 +582,8 @@ class Worker:
|
||||
"approval": "TEXT",
|
||||
"plan": "INTEGER",
|
||||
"forked_from": "TEXT",
|
||||
"environment_id": "TEXT"}.items():
|
||||
"environment_id": "TEXT",
|
||||
"resume_message": "TEXT"}.items():
|
||||
if name not in cols:
|
||||
try:
|
||||
conn.execute(
|
||||
@@ -1018,7 +1021,7 @@ class Worker:
|
||||
"host_status": None}
|
||||
fake = SimpleNamespace(returncode=proc.returncode,
|
||||
stdout=out or "", stderr=err or "")
|
||||
if kind == "goal" and hasattr(
|
||||
if kind in ("goal", "goal-resume") and hasattr(
|
||||
self.executor, "_goal_result_from_runner"):
|
||||
return self.executor._goal_result_from_runner(task_id, fake)
|
||||
if hasattr(self.executor, "_result_from_runner"):
|
||||
@@ -1634,8 +1637,14 @@ class Worker:
|
||||
effective_docker_timeout = self.task_timeout
|
||||
try:
|
||||
if self._use_popen():
|
||||
if goal_mode and task.get("_resume"):
|
||||
popen_kind = "goal-resume"
|
||||
elif goal_mode:
|
||||
popen_kind = "goal"
|
||||
else:
|
||||
popen_kind = "dispatch"
|
||||
result = self._execute_via_process(
|
||||
task, kind="goal" if goal_mode else "dispatch",
|
||||
task, kind=popen_kind,
|
||||
deadline_seconds=self.task_timeout,
|
||||
seed_host_dir=seed_host_dir)
|
||||
combined = (result.get("stdout", "") or "") + \
|
||||
@@ -1663,12 +1672,20 @@ class Worker:
|
||||
"stderr": ""},
|
||||
prep_err)
|
||||
if goal_mode:
|
||||
result = self.executor.goal_task(
|
||||
task, timeout=effective_docker_timeout,
|
||||
extra_mounts=tenant_mounts,
|
||||
network=tenant_network,
|
||||
extra_env=proxy_env,
|
||||
seed_host_dir=seed_host_dir)
|
||||
if task.get("_resume"):
|
||||
result = self.executor.goal_resume_task(
|
||||
task, task["prompt"],
|
||||
timeout=effective_docker_timeout,
|
||||
extra_mounts=tenant_mounts,
|
||||
network=tenant_network,
|
||||
extra_env=proxy_env)
|
||||
else:
|
||||
result = self.executor.goal_task(
|
||||
task, timeout=effective_docker_timeout,
|
||||
extra_mounts=tenant_mounts,
|
||||
network=tenant_network,
|
||||
extra_env=proxy_env,
|
||||
seed_host_dir=seed_host_dir)
|
||||
else:
|
||||
result = self.executor.dispatch_task(
|
||||
task, timeout=effective_docker_timeout,
|
||||
@@ -1836,6 +1853,15 @@ class Worker:
|
||||
return {"dispatched": False, "reason": "already_claimed",
|
||||
"task_id": task_id}
|
||||
task = self.get_task(task_id) or task
|
||||
# 续跑消息:旧任务继续会话时本轮要说的话——本轮 prompt 换
|
||||
# 成它(只换内存里的副本,原 prompt 不动);_resume 标记
|
||||
# 让 _run_attempt 在 goal 模式下走 goal-resume 而不是 goal
|
||||
# (goal 会撞已存在,只有 goal-resume 能在旧线程上开新循环)。
|
||||
resume_message = task.get("resume_message")
|
||||
if resume_message:
|
||||
task = dict(task)
|
||||
task["prompt"] = resume_message
|
||||
task["_resume"] = True
|
||||
self._audit(task["project_id"], "worker_claim",
|
||||
"%s executor=%s" % (task_id, self._executor_name()))
|
||||
# log_event 只接受 task_id/project_id/status/message 四个
|
||||
@@ -2153,6 +2179,21 @@ class Worker:
|
||||
# 补一条 worker_completed/failed 审计误导排查。
|
||||
final = "interrupted"
|
||||
self._audit(task["project_id"], "worker_interrupted", task_id)
|
||||
if resume_message:
|
||||
# 本轮已跑完(无论成败):续跑消息已消费,清掉;进程
|
||||
# 中途被杀时清不到,下次认领会重跑同一条消息——可接受
|
||||
# 的续跑语义,不会静默丢消息。清失败不挡结果返回。
|
||||
try:
|
||||
conn = _db_connect(self.db_path)
|
||||
try:
|
||||
conn.execute(
|
||||
"UPDATE tasks SET resume_message = NULL"
|
||||
" WHERE id = ?", (task_id,))
|
||||
conn.commit()
|
||||
finally:
|
||||
conn.close()
|
||||
except Exception:
|
||||
pass
|
||||
return {"dispatched": True, "task_id": task_id,
|
||||
"host_name": self.executor._host_name(task)
|
||||
if hasattr(self.executor, "_host_name") else task_id,
|
||||
|
||||
Reference in new issue
Block a user