feat: 子任务默认模型/强度(child_model/child_effort)
主任务可显式声明派生默认,或在 prompt 里写“子任务用 gpt-6-luna max” 由服务端解析填入;marker task-create 与协调员分工未显式写模型/强度时 按 块/分工 > 派生默认 > 父任务模型/强度 继承,fork 复制派生默认, continue 可改派生默认;console 新建/继续表单与任务详情同步展示。
This commit is contained in:
1 parent
f99e667222
commit
14757f15c4
7 files changed
+588
-25
No files matched your search
+88
-11
@@ -71,6 +71,7 @@ from cloud.peer_messages import deliver_peer_message as _deliver_peer_message
|
||||
from cloud.peer_messages import ensure_followups_table as _peer_ensure_followups
|
||||
from cloud.peer_messages import peer_message_prefix as _peer_prefix
|
||||
from cloud.peer_messages import row_get as _row_get
|
||||
from cloud.marker_tools import parse_spawn_defaults as _parse_spawn_defaults
|
||||
from cloud.automations import cancel_automation as _auto_cancel
|
||||
from cloud.automations import create_automation as _auto_create
|
||||
from cloud.automations import get_automation as _auto_get
|
||||
@@ -1076,6 +1077,13 @@ def migrate_tasks_table(conn):
|
||||
"model": "TEXT",
|
||||
"effort": "TEXT",
|
||||
"approval": "TEXT",
|
||||
# 派生默认(子任务模型/强度):NULL = 没有单独声明,
|
||||
# 派生时先取它、再取本任务的 model / effort;显式
|
||||
# 传 child_model / child_effort,或建任务 prompt 里
|
||||
# 写明“子任务用 X max”这类显式指令时由服务端解析
|
||||
# 填入(见 marker_tools.parse_spawn_defaults)。
|
||||
"child_model": "TEXT",
|
||||
"child_effort": "TEXT",
|
||||
# 计划模式(只读)与分支来源:plan=1 时 worker 用
|
||||
# read-only 沙箱;forked_from 记源任务 id(非分支为 NULL)。
|
||||
"plan": "INTEGER",
|
||||
@@ -1520,6 +1528,8 @@ def _task_to_dict(row, attachments=None):
|
||||
"worktree_host": _row_get(row, "worktree_host"),
|
||||
"model": _row_get(row, "model"),
|
||||
"effort": _row_get(row, "effort"),
|
||||
"child_model": _row_get(row, "child_model"),
|
||||
"child_effort": _row_get(row, "child_effort"),
|
||||
"approval": _row_get(row, "approval"),
|
||||
"plan": _row_get(row, "plan") or 0,
|
||||
"forked_from": _row_get(row, "forked_from"),
|
||||
@@ -3837,6 +3847,34 @@ class CloudControlPlaneHandler(BaseHTTPRequestHandler):
|
||||
self._send_json(400, {
|
||||
"error": "approval must be ask, auto or full"})
|
||||
return
|
||||
# 派生默认:本任务派生的子任务(marker task-create、
|
||||
# 协调员分工)未显式写模型/强度时用的模型/强度。
|
||||
# 显式字段优先;缺省时若 prompt 里有“子任务用 X max”
|
||||
# 这类显式指令,由 parse_spawn_defaults 解析填入——
|
||||
# 只认带派生主语的显式指令,裸说“主线用 X”不算,
|
||||
# 那是本任务自己的 model,不应被误当成派生默认。
|
||||
# 两者都缺省(NULL)时派生走本任务 model / effort,
|
||||
# 再缺省才走 worker 默认,行为与过去一致。
|
||||
child_model = body.get("child_model")
|
||||
if child_model is not None:
|
||||
if not isinstance(child_model, str) or not child_model.strip():
|
||||
self._send_json(400, {
|
||||
"error": "child_model must be a non-empty string"})
|
||||
return
|
||||
child_model = child_model.strip()
|
||||
child_effort = body.get("child_effort")
|
||||
if child_effort is not None and child_effort not in (
|
||||
"low", "medium", "high", "xhigh", "max"):
|
||||
self._send_json(400, {
|
||||
"error": "child_effort must be low, medium, high,"
|
||||
" xhigh or max"})
|
||||
return
|
||||
if child_model is None or child_effort is None:
|
||||
parsed = _parse_spawn_defaults(prompt)
|
||||
if child_model is None:
|
||||
child_model = parsed.get("model")
|
||||
if child_effort is None:
|
||||
child_effort = parsed.get("effort")
|
||||
# 计划模式:只读沙箱,不改任何文件;与“完全访问”
|
||||
# (danger-full-access)语义直接冲突,拒绝建任务。
|
||||
plan = 1 if body.get("plan") in (True, 1) else 0
|
||||
@@ -4067,16 +4105,17 @@ class CloudControlPlaneHandler(BaseHTTPRequestHandler):
|
||||
" goal, verify_command, goal_max_rounds,"
|
||||
" model, effort, approval, plan, goal_mode,"
|
||||
" environment_id, coordinator_parts,"
|
||||
" parent_task_id, coordinator_part)"
|
||||
" parent_task_id, coordinator_part,"
|
||||
" child_model, child_effort)"
|
||||
" VALUES(?, ?, ?, 'queued', ?, ?, ?, ?, ?, ?, NULL, ?,"
|
||||
" ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
||||
" ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
||||
(task_id, project_id, prompt, idempotency_key,
|
||||
json.dumps(depends_on), priority, user_id, now, now,
|
||||
executor, goal, verify_command, goal_max_rounds,
|
||||
model, effort, approval, plan, goal_mode,
|
||||
environment_id,
|
||||
json.dumps(coord_parts) if coord_parts else None,
|
||||
None, None),
|
||||
None, None, child_model, child_effort),
|
||||
)
|
||||
for rel_path, size in bundle_files:
|
||||
conn.execute(
|
||||
@@ -6089,11 +6128,12 @@ class CloudControlPlaneHandler(BaseHTTPRequestHandler):
|
||||
def _fork_task(self, user_id, task_id):
|
||||
"""会话分支:从已结束任务的 thread 分叉出新任务。
|
||||
|
||||
|
||||
新任务继承源任务的 executor/模型/强度/权限/计划模式,
|
||||
不继承 goal(分支是普通任务,首轮 prompt 就是这里传的
|
||||
message)。worker 认领后先调 host.py fork(分叉 thread
|
||||
历史 + 拷 worktree 文件),再以 message 跑首轮。
|
||||
以及派生默认 child_model / child_effort(分支会话自己
|
||||
再派生时仍用同一派生默认),不继承 goal(分支是普通
|
||||
任务,首轮 prompt 就是这里传的 message)。worker 认领
|
||||
后先调 host.py fork(分叉 thread 历史 + 拷 worktree
|
||||
文件),再以 message 跑首轮。
|
||||
只能从终态任务分支(进行中的 thread 正在变,分叉快照
|
||||
不可信);message 必填——分支没有首条消息就没有方向。
|
||||
"""
|
||||
@@ -6137,15 +6177,16 @@ class CloudControlPlaneHandler(BaseHTTPRequestHandler):
|
||||
" idempotency_key, depends_on, priority, created_by,"
|
||||
" created_at, updated_at, last_heartbeat, executor,"
|
||||
" goal, verify_command, goal_max_rounds,"
|
||||
" model, effort, approval, plan, forked_from)"
|
||||
" model, effort, approval, plan, forked_from,"
|
||||
" child_model, child_effort)"
|
||||
" VALUES(?, ?, ?, 'queued', NULL, '[]', ?, ?, ?, ?,"
|
||||
" NULL, ?, NULL, NULL, ?, ?, ?, ?, ?, ?)",
|
||||
" NULL, ?, NULL, NULL, ?, ?, ?, ?, ?, ?, ?, ?)",
|
||||
(new_id, task["project_id"], message, task["priority"],
|
||||
user_id, now, now,
|
||||
task["executor"] or _default_executor(),
|
||||
task["goal_max_rounds"] or 3, task["model"],
|
||||
task["effort"], task["approval"], task["plan"] or 0,
|
||||
task_id),
|
||||
task_id, task["child_model"], task["child_effort"]),
|
||||
)
|
||||
# 分支带走附件清单(worktree 本身已含这些文件,分支
|
||||
# 不再 seed;清单只作展示,bundle 删了也不影响分支)。
|
||||
@@ -6412,7 +6453,8 @@ class CloudControlPlaneHandler(BaseHTTPRequestHandler):
|
||||
必须是终端且非 merged(queued/running 请用后续消息,
|
||||
merged 的 worktree 已删、请分支,各 409)。选项与新建
|
||||
任务同款校验:model(空串 = 跟随配置)、effort、approval、
|
||||
plan、goal_mode;不传的选项沿用任务原值。冲突与新建
|
||||
plan、goal_mode,以及派生默认 child_model / child_effort
|
||||
(空串 = 清空);不传的选项沿用任务原值。冲突与新建
|
||||
一致:plan + approval=full、plan + goal_mode 各 400。
|
||||
goal_mode=1 时同时把任务转成新 goal 语义(goal=新消息、
|
||||
verify_command 清空、轮次 0),worker 走 goal-resume
|
||||
@@ -6483,6 +6525,35 @@ class CloudControlPlaneHandler(BaseHTTPRequestHandler):
|
||||
self._send_json(400, {
|
||||
"error": "effort must be low, medium, high, xhigh or max"})
|
||||
return
|
||||
# 派生默认同款:child_model 空串 = 清空(跟随本任务
|
||||
# model / effort),child_effort 空串 = 清空;不传沿用原值。
|
||||
child_model = None
|
||||
child_model_present = "child_model" in body
|
||||
if child_model_present:
|
||||
if body.get("child_model") is not None \
|
||||
and not isinstance(body.get("child_model"), str):
|
||||
self._send_json(400, {
|
||||
"error": "child_model must be a string"})
|
||||
return
|
||||
raw_child_model = body.get("child_model")
|
||||
child_model = raw_child_model.strip() \
|
||||
if isinstance(raw_child_model, str) else None
|
||||
if child_model == "":
|
||||
child_model = None
|
||||
child_effort = None
|
||||
child_effort_present = "child_effort" in body
|
||||
if child_effort_present:
|
||||
raw_child_effort = body.get("child_effort")
|
||||
if raw_child_effort is None or raw_child_effort == "":
|
||||
child_effort = None
|
||||
elif raw_child_effort not in (
|
||||
"low", "medium", "high", "xhigh", "max"):
|
||||
self._send_json(400, {
|
||||
"error": "child_effort must be low, medium, high,"
|
||||
" xhigh or max"})
|
||||
return
|
||||
else:
|
||||
child_effort = raw_child_effort
|
||||
approval = body.get("approval")
|
||||
if approval is not None and approval not in (
|
||||
"ask", "auto", "full"):
|
||||
@@ -6518,6 +6589,12 @@ class CloudControlPlaneHandler(BaseHTTPRequestHandler):
|
||||
if effort is not None:
|
||||
sets.append("effort = ?")
|
||||
vals.append(effort)
|
||||
if child_model_present:
|
||||
sets.append("child_model = ?")
|
||||
vals.append(child_model)
|
||||
if child_effort_present:
|
||||
sets.append("child_effort = ?")
|
||||
vals.append(child_effort)
|
||||
if approval is not None:
|
||||
sets.append("approval = ?")
|
||||
vals.append(approval)
|
||||
|
||||
Reference in new issue
Block a user