feat: 新建任务的 prompt 里写 /goal 也自动变 goal

上一提交只覆盖了已有主线的转换,新建任务的 prompt 里写
/goal 时仍会当成普通一轮任务跑。本提交补上 POST /tasks
入口:prompt 经 cloud/goal_command.py 解析后,目标即新任
务的 prompt 与 goal,goal_mode 强制为 1(命令优先于 body
里的 goal_mode=0),plan/验收命令冲突仍按新建规则 400,
空目标 400 不建任务;讨论里提到 /goal 不触发。
单测补 5 项:/goal 直写、整句、空目标、plan/验收冲突、
讨论 prompt 不变。
This commit is contained in:
host committed 2026-10-30 07:15:05 +08:00
1 parent 3096e04345
commit f3bd65c171
3 files changed
+59 -2

No files matched your search

+4 -1
View File
@@ -539,7 +539,10 @@ goal 模式的继续:server 把任务转成新 goal 语义(goal=新消息、
持久 /goal,目标是收xxx”,或直接写“/goal 收xxx”。解析在
`cloud/goal_command.py`(server 与 worker 共用同一份,防两端
判断不一致);只是在讨论里提到“/goal 的用法”不算命令。
入口三处同款:`POST /tasks/{id}/followups`、
新建目标也同款:`POST /tasks` 的 prompt 里直接写 `/goal
收xxx`(或上面那句整话)即新建 goal 任务,prompt 与 goal
都存解析出的目标,不用勾 goal 选项;空目标、plan 冲突、
带验收命令各 400。入口三处同款:`POST /tasks/{id}/followups`、
`POST /tasks/{id}/thread/message`、`POST /tasks/{id}/continue`
(message 字段);响应里 `delivery` 说明实际走了哪条路:
queued 主线原地转 goal(`goal_converted`)、已终态复活并转
+15 -1
View File
@@ -4244,6 +4244,17 @@ class CloudControlPlaneHandler(BaseHTTPRequestHandler):
if not prompt or not isinstance(prompt, str):
self._send_json(400, {"error": "prompt required"})
return
# 新建目标:prompt 里直接写 /goal 也算新建 goal,不必
# 先勾选项——例如 prompt="/goal 收xxx" 或“请开启一个
# 持久 /goal,目标是收xxx”。解析在 cloud/goal_command.py,
# 与主线转换同款(讨论里提到 /goal 不算)。目标即新
# 任务的 prompt 与 goal;空目标 400,不建普通任务。
prompt_goal_target = _parse_goal_command(prompt)
if prompt_goal_target is not None and not prompt_goal_target:
self._send_json(400, {"error": "goal 目标不能为空"})
return
if prompt_goal_target:
prompt = prompt_goal_target
depends_on = body.get("depends_on") or []
if isinstance(depends_on, str):
depends_on = [depends_on]
@@ -4327,7 +4338,10 @@ class CloudControlPlaneHandler(BaseHTTPRequestHandler):
# verify_command 是语义冲突,400。旧语义(verify_command
# 非空)保留:goal 文本缺省用 prompt,轮次缺省 3,非法值
# 400 且不建任务;老客户端与已建的旧 goal 任务继续可用。
goal_mode = 1 if body.get("goal_mode") in (True, 1) else 0
# /goal 命令优先于 body 里的 goal_mode=0(prompt 里的
# 命令比选项更具体),plan/验收命令冲突仍在下面 400。
goal_mode = 1 if body.get("goal_mode") in (True, 1) \
or prompt_goal_target else 0
if goal_mode:
if plan:
self._send_json(400, {
+40
View File
@@ -208,6 +208,46 @@ class ServerTestCase(unittest.TestCase):
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_continue_goal_command(self):
task = self._create()
self._set_status(task["id"], "completed")