Files
codex-app-server/cloud/tests/test_mainline_goal_command.py
T
host f1aab5415c feat: 不用斜杠,单发 goal 也触发 /goal 目标
触发词从仅 /goal 放宽到 /goal 或裸 goal(大小写不敏感,
单数 only,goals/mygoal 不算):goal 收xxx、goal,目标是
收xxx、发goal 收xxx 均同款触发,新建与主线转换入口共用
同一解析。讨论句式仍不触发:goal 的用法、goal 是什么、
讨论goal的用法、/goal的用法 均返回 None。
单测补 4 项:裸 goal 触发、裸 goal 讨论不触发、followup
裸 goal 转换、新建裸 goal。
2026-10-30 07:15:05 +08:00

414 lines
16 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"))
def test_bare_goal_without_slash(self):
# 不用斜杠:用户单发 goal 也触发同款目标。
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 收xxx"), "收xxx")
self.assertEqual(parse_goal_command("goal"), "")
def test_bare_goal_discussion_still_not_a_command(self):
self.assertIsNone(parse_goal_command("goal的用法"))
self.assertIsNone(parse_goal_command("goal是什么意思"))
self.assertIsNone(parse_goal_command("讨论goal的用法"))
self.assertIsNone(parse_goal_command("请问goal是什么意思"))
self.assertIsNone(parse_goal_command("/goal的用法"))
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_create_task_prompt_goal_command(self):
task = self._create(prompt="/goal 收xxx")
self.assertEqual(task["goal_mode"], 1)
self.assertEqual(task["goal"], "收xxx")
self.assertEqual(task["prompt"], "收xxx")
self.assertIsNone(task["verify_command"])
self.assertEqual(task["goal_max_rounds"], 0)
def test_create_task_prompt_goal_user_sentence(self):
task = self._create(
prompt="请在这条主线开启一个持久 /goal,目标是收xxx")
self.assertEqual(task["goal_mode"], 1)
self.assertEqual(task["goal"], "收xxx")
self.assertEqual(task["prompt"], "收xxx")
def test_create_task_prompt_goal_empty_400(self):
status, body = self._req(
"POST", "/tasks",
{"project_id": self.project["id"], "prompt": "/goal"},
self.owner_key)
self.assertEqual(status, 400, body)
def test_create_task_prompt_goal_conflicts_400(self):
status, _body = self._req(
"POST", "/tasks",
{"project_id": self.project["id"], "prompt": "/goal 收xxx",
"plan": 1},
self.owner_key)
self.assertEqual(status, 400)
status, _body = self._req(
"POST", "/tasks",
{"project_id": self.project["id"], "prompt": "/goal 收xxx",
"verify_command": "true"},
self.owner_key)
self.assertEqual(status, 400)
def test_create_task_discussion_prompt_unchanged(self):
task = self._create(prompt="讨论/goal的用法")
self.assertEqual(task["goal_mode"], 0)
def test_followup_bare_goal_converts(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")
def test_create_task_bare_goal(self):
task = self._create(prompt="goal 收xxx")
self.assertEqual(task["goal_mode"], 1)
self.assertEqual(task["goal"], "收xxx")
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()