Files
codex-app-server/cloud/tests/test_mainline_goal_command.py
T
host 3096e04345 feat: 主线 /goal 命令自动建 goal
发给主线的一句话(含字面 /goal)即可自动建 goal,不用先
知道主线是进行中还是已终态,也不用勾选项:
- cloud/goal_command.py:server 与 worker 共用的纯文本解析,
  只认命令形状(/goal 开头、分隔符/目标标记、开启类动词),
  讨论里提到 /goal 不误触发;空目标返回空串供调用方 400。
- server followup/thread/message/continue 三入口同款:queued
  原地转 goal,已终态复活并转 goal,running 排队后由 worker
  在轮次边界转 goal;已是 goal 409、plan 400、merged 409。
- worker 排空:/goal 不当普通轮次跑,CAS 把 running 主线
  改回 queued 并写 goal/resume_message,下次认领走 goal-resume
  在同一 thread 上开新目标循环;取消/回收时 CAS 失败则把
  followup 放回 pending,绝不把取消过的任务改回 queued。
- 单测 14 项:解析、三态转换、拒绝口径、普通消息不受影响、
  continue/thread 同款、worker 轮次边界转换 + 下次 goal 跑通。
2026-10-30 07:15:05 +08:00

344 lines
13 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""主线 /goal 命令单测:发给主线的一句话自动建 goal。
不跑真实模型回合、不联网:server 侧走本地临时端口 + 临时
SQLite;worker 侧注入假 runner。覆盖:解析、followup 的
queued/running/已终态三态、已是 goal、空目标、plan 冲突、
普通消息不受影响、/continue 与 /thread/message 同款转换、
worker 在轮次边界把 running 主线转成 goal 并在下次认领走
goal-resume。
"""
import http.client
import json
import os
import sqlite3
import sys
import tempfile
import threading
import time
import unittest
from pathlib import Path
from types import SimpleNamespace
try:
from cloud.auth import AuthStore
from cloud.goal_command import parse_goal_command
from cloud.peer_messages import ensure_followups_table
from cloud.server import create_server, init_db
from cloud.worker import Worker
except ImportError: # discover -s cloud/tests 时把项目根目录补进 sys.path
sys.path.insert(0, str(Path(__file__).resolve().parents[2]))
from cloud.auth import AuthStore
from cloud.goal_command import parse_goal_command
from cloud.peer_messages import ensure_followups_table
from cloud.server import create_server, init_db
from cloud.worker import Worker
class ParserTestCase(unittest.TestCase):
def test_user_sentence(self):
self.assertEqual(
parse_goal_command("请在这条主线开启一个持久 /goal,目标是收xxx"),
"收xxx")
self.assertEqual(
parse_goal_command("请在这条主线开启一个持久 /goal,目标是收xxx。"),
"收xxx")
def test_slash_forms(self):
self.assertEqual(parse_goal_command("/goal 收xxx"), "收xxx")
self.assertEqual(parse_goal_command("/goal,目标是收xxx"), "收xxx")
self.assertEqual(parse_goal_command("/goal: 收xxx"), "收xxx")
self.assertEqual(parse_goal_command("请开启/goal收xxx"), "收xxx")
self.assertEqual(parse_goal_command("/goal"), "")
def test_discussion_is_not_a_command(self):
self.assertIsNone(parse_goal_command("讨论/goal的用法"))
self.assertIsNone(parse_goal_command("请帮我完成目标收xxx"))
self.assertIsNone(parse_goal_command("hello world"))
class ServerTestCase(unittest.TestCase):
def setUp(self):
self.tmp = tempfile.TemporaryDirectory()
self.db_path = os.path.join(self.tmp.name, "control.db")
self.auth = AuthStore(self.db_path)
self.owner_id, self.owner_key = self.auth.create_user("owner")
self.server = create_server(self.db_path, "127.0.0.1", 0)
self.port = self.server.server_address[1]
self.thread = threading.Thread(
target=self.server.serve_forever, daemon=True)
self.thread.start()
status, self.project = self._req(
"POST", "/projects", {"name": "demo"}, self.owner_key)
self.assertEqual(status, 201)
def tearDown(self):
self.server.shutdown()
self.server.server_close()
self.thread.join(timeout=5)
try:
self.auth.conn.close()
except Exception:
pass
self.tmp.cleanup()
def _req(self, method, path, body=None, key=None):
conn = http.client.HTTPConnection(
"127.0.0.1", self.port, timeout=10)
hdrs = {}
payload = None
if key:
hdrs["X-API-Key"] = key
if body is not None:
payload = json.dumps(body)
hdrs["Content-Type"] = "application/json"
conn.request(method, path, body=payload, headers=hdrs)
resp = conn.getresponse()
raw = resp.read()
conn.close()
try:
return resp.status, json.loads(raw.decode("utf-8"))
except ValueError:
return resp.status, raw.decode("utf-8", errors="replace")
def _create(self, **extra):
body = {"project_id": self.project["id"], "prompt": "do work"}
body.update(extra)
status, task = self._req("POST", "/tasks", body, self.owner_key)
self.assertEqual(status, 201, task)
return task
def _set_status(self, task_id, status):
conn = sqlite3.connect(self.db_path)
try:
conn.execute("UPDATE tasks SET status = ? WHERE id = ?",
(status, task_id))
conn.commit()
finally:
conn.close()
def _get_task(self, task_id):
status, task = self._req("GET", "/tasks/" + task_id, None,
self.owner_key)
self.assertEqual(status, 200, task)
return task
def test_followup_queued_converts_in_place(self):
task = self._create()
status, body = self._req(
"POST", "/tasks/%s/followups" % task["id"],
{"body": "请在这条主线开启一个持久 /goal,目标是收xxx"},
self.owner_key)
self.assertEqual(status, 200, body)
self.assertEqual(body["delivery"], "goal_converted")
self.assertEqual(body["goal"], "收xxx")
after = self._get_task(task["id"])
self.assertEqual(after["status"], "queued")
self.assertEqual(after["goal_mode"], 1)
self.assertEqual(after["goal"], "收xxx")
self.assertIsNone(after["verify_command"])
self.assertEqual(after["goal_max_rounds"], 0)
def test_followup_running_queues_for_worker(self):
task = self._create()
self._set_status(task["id"], "running")
status, body = self._req(
"POST", "/tasks/%s/followups" % task["id"],
{"body": "/goal 收xxx"}, self.owner_key)
self.assertEqual(status, 201, body)
self.assertEqual(body["delivery"], "goal_queued")
self.assertEqual(body["goal"], "收xxx")
# running 主线本轮不被打断:DB 里仍是 running,未转 goal。
after = self._get_task(task["id"])
self.assertEqual(after["status"], "running")
self.assertEqual(after["goal_mode"], 0)
def test_followup_terminal_revives_as_goal(self):
task = self._create()
self._set_status(task["id"], "completed")
status, body = self._req(
"POST", "/tasks/%s/followups" % task["id"],
{"body": "/goal 收xxx"}, self.owner_key)
self.assertEqual(status, 200, body)
self.assertEqual(body["delivery"], "goal_revived")
after = self._get_task(task["id"])
self.assertEqual(after["status"], "queued")
self.assertEqual(after["goal_mode"], 1)
self.assertEqual(after["goal"], "收xxx")
self.assertEqual(after["resume_message"], "收xxx")
def test_followup_already_goal_409(self):
task = self._create(goal_mode=1)
status, body = self._req(
"POST", "/tasks/%s/followups" % task["id"],
{"body": "/goal 收yyy"}, self.owner_key)
self.assertEqual(status, 409, body)
after = self._get_task(task["id"])
self.assertEqual(after["goal"], "do work")
def test_followup_empty_goal_400(self):
task = self._create()
status, body = self._req(
"POST", "/tasks/%s/followups" % task["id"],
{"body": "/goal"}, self.owner_key)
self.assertEqual(status, 400, body)
def test_followup_plan_conflict_400(self):
task = self._create(plan=1)
status, body = self._req(
"POST", "/tasks/%s/followups" % task["id"],
{"body": "/goal 收xxx"}, self.owner_key)
self.assertEqual(status, 400, body)
def test_followup_merged_409(self):
task = self._create()
self._set_status(task["id"], "merged")
status, body = self._req(
"POST", "/tasks/%s/followups" % task["id"],
{"body": "/goal 收xxx"}, self.owner_key)
self.assertEqual(status, 409, body)
def test_ordinary_followup_unchanged(self):
task = self._create()
status, body = self._req(
"POST", "/tasks/%s/followups" % task["id"],
{"body": "继续做"}, self.owner_key)
self.assertEqual(status, 201, body)
self.assertNotIn("goal", body)
after = self._get_task(task["id"])
self.assertEqual(after["goal_mode"], 0)
def test_continue_goal_command(self):
task = self._create()
self._set_status(task["id"], "completed")
status, body = self._req(
"POST", "/tasks/%s/continue" % task["id"],
{"message": "请开启/goal,目标是收xxx"}, self.owner_key)
self.assertEqual(status, 200, body)
self.assertEqual(body["goal_mode"], 1)
self.assertEqual(body["goal"], "收xxx")
self.assertEqual(body["resume_message"], "收xxx")
def test_thread_message_terminal_goal(self):
task = self._create()
self._set_status(task["id"], "failed")
status, body = self._req(
"POST", "/tasks/%s/thread/message" % task["id"],
{"body": "/goal 收xxx"}, self.owner_key)
self.assertEqual(status, 201, body)
self.assertEqual(body["delivery"], "goal_revived")
after = self._get_task(task["id"])
self.assertEqual(after["goal_mode"], 1)
self.assertEqual(after["goal"], "收xxx")
class WorkerTestCase(unittest.TestCase):
def setUp(self):
self.tmp = tempfile.TemporaryDirectory()
self.db_path = os.path.join(self.tmp.name, "control.db")
init_db(self.db_path)
self.env = {"XCM_API_KEY": "test-placeholder"}
def tearDown(self):
self.tmp.cleanup()
def _insert_task(self, task_id, **extra):
now = time.time()
cols = ["id", "project_id", "prompt", "status",
"idempotency_key", "depends_on", "priority",
"created_by", "created_at", "updated_at",
"last_heartbeat"]
vals = [task_id, "p1", "do work", "queued", None, "[]", 0,
"tester", now, now, None]
for k, v in extra.items():
cols.append(k)
vals.append(v)
conn = sqlite3.connect(self.db_path)
try:
conn.execute(
"INSERT INTO tasks(%s) VALUES(%s)"
% (", ".join(cols), ", ".join("?" * len(cols))), vals)
conn.commit()
finally:
conn.close()
def _insert_followup(self, task_id, body):
conn = sqlite3.connect(self.db_path)
try:
ensure_followups_table(conn)
conn.execute(
"INSERT INTO task_followups(id, task_id, project_id,"
" body, mode, status, created_by, created_at,"
" updated_at) VALUES(?, ?, ?, ?, 'queued', 'pending',"
" ?, ?, ?)",
("fu1", task_id, "p1", body, "tester",
time.time(), time.time()))
conn.commit()
finally:
conn.close()
def _row(self, task_id):
conn = sqlite3.connect(self.db_path)
try:
row = conn.execute(
"SELECT status, goal_mode, goal, resume_message"
" FROM tasks WHERE id = ?", (task_id,)).fetchone()
fu = conn.execute(
"SELECT status FROM task_followups WHERE id = 'fu1'"
).fetchone()
return row, (fu[0] if fu else None)
finally:
conn.close()
def test_running_task_converts_at_turn_boundary_then_goal_runs(self):
self._insert_task("T")
self._insert_followup("T", "/goal 收xxx")
seen = []
def fake_dispatch(cmd):
seen.append(list(cmd))
name = cmd[cmd.index("--name") + 1]
return SimpleNamespace(
returncode=0,
stdout=json.dumps({"task": name, "status": "completed",
"text": "done"}),
stderr="")
worker = Worker(self.db_path, "/fake/host.py",
runner=fake_dispatch, env=self.env)
result = worker.run_one()
self.assertEqual(result["status"], "queued")
(status, goal_mode, goal, resume), fu_status = self._row("T")
self.assertEqual(status, "queued")
self.assertEqual(goal_mode, 1)
self.assertEqual(goal, "收xxx")
self.assertEqual(resume, "收xxx")
self.assertEqual(fu_status, "done")
self.assertTrue(any("dispatch" in c for c in seen))
self.assertFalse(any("goal" in c for c in seen))
# 下次认领走 goal-resume:goal 循环宣告达成即完成。
def fake_goal(cmd):
seen.append(list(cmd))
# goal-resume 的命令元素是 "goal-resume",不是 "goal",
# 用子串判,别用元素相等判。
if any("goal" in part for part in cmd):
return SimpleNamespace(
returncode=0,
stdout="round 1: turn=completed verify_rc=-"
" verify_out=\n"
"goal met after rounds\n",
stderr="")
raise AssertionError("第二轮不应走 dispatch: %s" % cmd)
worker2 = Worker(self.db_path, "/fake/host.py",
runner=fake_goal, env=self.env)
result2 = worker2.run_one()
self.assertEqual(result2["status"], "completed")
self.assertTrue(any(any("goal" in part for part in c)
for c in seen))
if __name__ == "__main__":
unittest.main()