feat: 子任务默认模型/强度(child_model/child_effort)

主任务可显式声明派生默认,或在 prompt 里写“子任务用 gpt-6-luna max”
由服务端解析填入;marker task-create 与协调员分工未显式写模型/强度时
按 块/分工 > 派生默认 > 父任务模型/强度 继承,fork 复制派生默认,
continue 可改派生默认;console 新建/继续表单与任务详情同步展示。
This commit is contained in:
host committed 2026-10-29 05:27:22 +08:00
1 parent f99e667222
commit 14757f15c4
7 files changed
+588 -25

No files matched your search

+118 -1
View File
@@ -25,7 +25,12 @@ wait-threads / task-cancel / task-retry。
fork)。新任务 prompt 由标记块给,不继承父任务的历史;
环境基线继承父任务的 environment_id,但 worker 会给它独立
的 tenant/worktree,不动父任务的 cwd。父任务未提交的改动
不会自动带过去。
不会自动带过去。模型/强度继承优先级:块里显式写 >
父任务的 child_model / child_effort(派生默认)> 父任务
自身的 model / effort > worker 默认(NULL 时由认领方补)。
子任务行上同时记下父任务的 child_model / child_effort,
这样隔代派生时派生默认仍然沿用,不会因为中间一层没写
就丢回 worker 默认。
- task-poll / task-read:按任务 id 轮询阅读。task-poll 按
interval 查库直到终态或超时;task-read 是同款别名,
不传 wait_seconds 时立即读一次快照,传了则等这么久再读
@@ -185,6 +190,118 @@ def extract_task_creates(text):
return out
_SPAWN_SUBJECT_ZH = ("新建的任务", "派生任务", "新建任务",
"子任务", "子代理", "新任务", "新会话")
_SPAWN_SUBJECT_EN = ("child tasks", "spawned tasks", "new tasks",
"new sessions", "subtasks", "subtask",
"child task", "new task", "new session")
def _effort_after(text, start):
"""在 model 后的同一子句里找强度:先找“强度/effort + 值”,
再找紧跟 model 的裸强度词(如“gpt-6-luna max”)。子句以
句号/换行/分号截断,跨子句的强度词不算,避免把下一句的
词误当成这一句的强度。"""
clause = str(text)[start:start + 60]
for stop in ("。", "\n", ";", ";"):
cut = clause.find(stop)
if cut >= 0:
clause = clause[:cut]
labelled = re.search(
r"(?:强度|effort)\s*[::=\s]\s*(low|medium|high|xhigh|max)",
clause, re.IGNORECASE)
if labelled:
return labelled.group(1).lower()
trailing = re.search(
r"^\s*[,,、\s]*(low|medium|high|xhigh|max)\b",
clause, re.IGNORECASE)
if trailing:
return trailing.group(1).lower()
return None
def _model_from_match(text, match, token_group):
"""从一次正则命中里取 (model, effort):model 词本身若是
强度词(如“子任务用 max”),只当强度、不当模型;模型词
尾部的句点是句子标点、不是模型名的一部分,要去掉。"""
token = match.group(token_group).rstrip(".")
if not token:
return None, None
if token.lower() in EFFORTS:
return None, token.lower()
return token, _effort_after(text, match.end(token_group))
def parse_spawn_defaults(text):
"""从主线 prompt 里解析派生默认模型/强度,只认显式指令。
认的句式(主语必须是派生对象,裸说“主线用 X”不算,
那是本任务自己的模型、应走建任务的 model 字段):
- 中文:子任务/子代理/派生任务/新建任务/新任务/新会话 +
用/使用/采用/模型:/模型是 + 模型词,可跟强度词或
“强度/effort + 强度词”,如“子代理用 gpt-6-luna max”、
“子任务模型:gpt-6-luna,强度:max”。
- 英文:subtask/child task/spawned task/new task/session +
use/using/with model/model: + 模型词,如
“subtasks use gpt-6-luna with effort max”。
- 中文倒装:用 + 模型词 + … + 子任务/子代理,如
“用 gpt-6-luna max 跑子任务”。
只取第一条命中;模型词只认 ASCII 词(中转模型 id 都是
ASCII),“用默认模型”这类中文词不会被误当成模型。
显式传了 child_model / child_effort 的任务不走本函数,
本函数只在建任务时两者缺省才作兜底,且结果落在任务行
的 child_model / child_effort 上、调用方可直接检查。
返回 dict,只含解析到的键(都没解析到时为空 dict)。"""
if not text:
return {}
text = str(text)
zh_subject = "|".join(_SPAWN_SUBJECT_ZH)
zh_pattern = re.compile(
r"(?:%s)[^。\n;;]{0,50}?(?:用\s*|使用\s*|采用\s*"
r"|模型\s*[::]\s*|模型\s*是\s*)"
r"([A-Za-z][A-Za-z0-9._-]{1,63})" % zh_subject)
match = zh_pattern.search(text)
if match:
model, effort = _model_from_match(text, match, 1)
out = {}
if model:
out["model"] = model
if effort:
out["effort"] = effort
if out:
return out
en_subject = "|".join(_SPAWN_SUBJECT_EN)
en_pattern = re.compile(
r"(?:%s)\b[^.\n]{0,50}?(?:use\s+|using\s+|with\s+model\s+"
r"|model\s*[:=]\s*)"
r"([A-Za-z][A-Za-z0-9._-]{1,63})" % en_subject,
re.IGNORECASE)
match = en_pattern.search(text)
if match:
model, effort = _model_from_match(text, match, 1)
out = {}
if model:
out["model"] = model
if effort:
out["effort"] = effort
if out:
return out
rev_pattern = re.compile(
r"(?:用|使用|采用)\s*([A-Za-z][A-Za-z0-9._-]{1,63})"
r"[^。\n;;]{0,30}?(?:%s)" % zh_subject)
match = rev_pattern.search(text)
if match:
model, effort = _model_from_match(text, match, 1)
out = {}
if model:
out["model"] = model
if effort:
out["effort"] = effort
if out:
return out
return {}
def extract_task_polls(text):
"""提取 task-poll / task-read(含 read-thread 别名)块。