codebee 0.1.54 → 0.1.56
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +13 -0
- package/README.md +3 -4
- package/app/core/builtin_agent.py +67 -26
- package/app/core/jobs.py +23 -4
- package/app/core/knowledge.py +26 -4
- package/app/core/modelhub.py +50 -5
- package/app/core/pipeline.py +156 -66
- package/app/core/planner.py +27 -13
- package/app/core/runner.py +398 -121
- package/app/core/store.py +64 -27
- package/app/core/zentao.py +39 -6
- package/app/main.py +43 -3
- package/app/ui/app.js +289 -56
- package/app/ui/board.css +616 -258
- package/app/ui/board.html +119 -41
- package/app/ui/board.js +60 -14
- package/app/ui/i18n.js +2699 -2659
- package/app/ui/index.html +2 -0
- package/app/ui/style.css +8 -6
- package/package.json +1 -1
package/app/core/pipeline.py
CHANGED
|
@@ -30,17 +30,60 @@ from .repeat_guard import guard as repeat_guard
|
|
|
30
30
|
DEFAULT_RUBRIC = ["情节", "人物", "文笔", "节奏", "吸引力"]
|
|
31
31
|
|
|
32
32
|
|
|
33
|
-
class Cancelled(Exception):
|
|
34
|
-
pass
|
|
33
|
+
class Cancelled(Exception):
|
|
34
|
+
pass
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class TaskTimeout(Exception):
|
|
38
|
+
pass
|
|
35
39
|
|
|
36
40
|
|
|
37
41
|
def _now():
|
|
38
42
|
return time.strftime("%Y-%m-%d %H:%M:%S")
|
|
39
43
|
|
|
40
44
|
|
|
41
|
-
def _check_cancel(ev):
|
|
42
|
-
if ev is not None and ev.is_set():
|
|
43
|
-
raise Cancelled()
|
|
45
|
+
def _check_cancel(ev):
|
|
46
|
+
if ev is not None and ev.is_set():
|
|
47
|
+
raise Cancelled()
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _run_deadline(run_id):
|
|
51
|
+
"""Convert the persisted wall-clock cutoff into a monotonic deadline."""
|
|
52
|
+
run = store.get_run(run_id) or {}
|
|
53
|
+
try:
|
|
54
|
+
deadline_at = float(run.get("deadline_at"))
|
|
55
|
+
except (TypeError, ValueError):
|
|
56
|
+
return None
|
|
57
|
+
return time.monotonic() + (deadline_at - time.time())
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _ensure_budget(run_id):
|
|
61
|
+
deadline = _run_deadline(run_id)
|
|
62
|
+
if deadline is not None and time.monotonic() >= deadline:
|
|
63
|
+
store.update_run(run_id, expected_status="running", status="timeout",
|
|
64
|
+
ended_at=_now(), error="任务总时限已到")
|
|
65
|
+
raise TaskTimeout()
|
|
66
|
+
return deadline
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _step_timeout(run_id, requested, deadline=None):
|
|
70
|
+
value = max(0.0, float(requested or runner.DEFAULT_TIMEOUT))
|
|
71
|
+
deadline = deadline if deadline is not None else _run_deadline(run_id)
|
|
72
|
+
if deadline is not None:
|
|
73
|
+
value = min(value, max(0.0, deadline - time.monotonic()))
|
|
74
|
+
return value, deadline
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _planner_call(fn, *args, deadline=None, **kwargs):
|
|
78
|
+
"""Allow older injected planner doubles to omit the new deadline kwarg."""
|
|
79
|
+
if deadline is None:
|
|
80
|
+
return fn(*args, **kwargs)
|
|
81
|
+
try:
|
|
82
|
+
return fn(*args, deadline=deadline, **kwargs)
|
|
83
|
+
except TypeError as exc:
|
|
84
|
+
if "unexpected keyword argument 'deadline'" not in str(exc):
|
|
85
|
+
raise
|
|
86
|
+
return fn(*args, **kwargs)
|
|
44
87
|
|
|
45
88
|
|
|
46
89
|
def _inside(dirpath, target):
|
|
@@ -195,12 +238,12 @@ def _get_session(run_id):
|
|
|
195
238
|
return s
|
|
196
239
|
|
|
197
240
|
|
|
198
|
-
def _make_llm_caller(agent, workdir):
|
|
241
|
+
def _make_llm_caller(agent, workdir, deadline=None):
|
|
199
242
|
"""压缩摘要用 LLM:直接复用当前 step 的 agent(同 CLI 同模型)。"""
|
|
200
243
|
def caller(messages):
|
|
201
244
|
prompt = "\n\n".join(m.get("content", "") for m in messages)
|
|
202
|
-
res = runner.run_agent(agent, prompt, workdir=workdir, readonly=True,
|
|
203
|
-
timeout=300)
|
|
245
|
+
res = runner.run_agent(agent, prompt, workdir=workdir, readonly=True,
|
|
246
|
+
timeout=300, deadline=deadline)
|
|
204
247
|
return res.get("text") or ""
|
|
205
248
|
return caller
|
|
206
249
|
|
|
@@ -303,9 +346,11 @@ def _binding_dead_msg(agent):
|
|
|
303
346
|
"请在「模型调度(可选)」页为该 CLI 指定已启用的供应商")
|
|
304
347
|
|
|
305
348
|
|
|
306
|
-
def _run_step(run_id, role, agent, prompt, workdir, readonly, ev, timeout=runner.DEFAULT_TIMEOUT, note="", resume=None, images=None, require_tools=False):
|
|
307
|
-
"""执行一个智能体步骤并记录。返回 runner 统一结果。"""
|
|
308
|
-
_wait_gate(run_id, ev)
|
|
349
|
+
def _run_step(run_id, role, agent, prompt, workdir, readonly, ev, timeout=runner.DEFAULT_TIMEOUT, note="", resume=None, images=None, require_tools=False):
|
|
350
|
+
"""执行一个智能体步骤并记录。返回 runner 统一结果。"""
|
|
351
|
+
_wait_gate(run_id, ev)
|
|
352
|
+
deadline = _ensure_budget(run_id)
|
|
353
|
+
timeout, deadline = _step_timeout(run_id, timeout, deadline)
|
|
309
354
|
# 绑定解析为空分两种(2026-09-18 起 区分对待):
|
|
310
355
|
# · 从没配过链(binding_configured=False):回落 CLI 本机默认照跑——
|
|
311
356
|
# 用户根本没在 CodeBee 里配供应商,谈不到「烧自己配的配额」;判失败
|
|
@@ -373,19 +418,24 @@ def _run_step(run_id, role, agent, prompt, workdir, readonly, ev, timeout=runner
|
|
|
373
418
|
if directive_block:
|
|
374
419
|
prompt = directive_block + "\n\n---\n\n" + prompt
|
|
375
420
|
effective_prompt = (guard["reminder"] + "\n\n---\n\n" + prompt) if guard["reminder"] else prompt
|
|
376
|
-
res = _spawn_step(session_run_id=run_id, role=role, agent=agent,
|
|
377
|
-
prompt=effective_prompt, workdir=workdir, readonly=readonly,
|
|
378
|
-
ev=ev, timeout=timeout, resume=resume, step=step,
|
|
379
|
-
log_abs=log_abs, images=images, require_tools=require_tools
|
|
421
|
+
res = _spawn_step(session_run_id=run_id, role=role, agent=agent,
|
|
422
|
+
prompt=effective_prompt, workdir=workdir, readonly=readonly,
|
|
423
|
+
ev=ev, timeout=timeout, resume=resume, step=step,
|
|
424
|
+
log_abs=log_abs, images=images, require_tools=require_tools,
|
|
425
|
+
deadline=deadline)
|
|
380
426
|
# 先收尾再查取消:取消时进程已被 run_process 杀停,若先抛 Cancelled,
|
|
381
|
-
# 步骤记录会永远停在「运行中」变僵尸(与 _run_verify 的顺序对齐)
|
|
382
|
-
_finish_step_result(run_id, step, res, role, agent, start)
|
|
383
|
-
|
|
427
|
+
# 步骤记录会永远停在「运行中」变僵尸(与 _run_verify 的顺序对齐)
|
|
428
|
+
_finish_step_result(run_id, step, res, role, agent, start)
|
|
429
|
+
if (res.get("raw") or {}).get("deadline_exceeded"):
|
|
430
|
+
store.update_run(run_id, expected_status="running", status="timeout",
|
|
431
|
+
ended_at=_now(), error="任务总时限已到")
|
|
432
|
+
raise TaskTimeout()
|
|
433
|
+
_check_cancel(ev)
|
|
384
434
|
return res
|
|
385
435
|
|
|
386
436
|
|
|
387
|
-
def _run_builtin_step(run_id, role, bi, prompt, workdir, ev, note="", images=None,
|
|
388
|
-
followups=False):
|
|
437
|
+
def _run_builtin_step(run_id, role, bi, prompt, workdir, ev, note="", images=None,
|
|
438
|
+
followups=False):
|
|
389
439
|
"""内置智能体步骤:直连模型 API + 工具循环(builtin_agent),不经 CLI 进程。
|
|
390
440
|
|
|
391
441
|
与 _run_step 对齐的三件事:暂停/取消闸门、运行中指令 drain 注入、重复调用
|
|
@@ -393,7 +443,8 @@ def _run_builtin_step(run_id, role, bi, prompt, workdir, ev, note="", images=Non
|
|
|
393
443
|
日志只有「迭代/工具」摘要行——对话视图吃 output,日志抽屉看工具轨迹。
|
|
394
444
|
followups=True 时从回答末尾解析「建议追问」块(直连对话专用协议):
|
|
395
445
|
剥离出结构化列表落步骤记录,正文保持干净。"""
|
|
396
|
-
_wait_gate(run_id, ev)
|
|
446
|
+
_wait_gate(run_id, ev)
|
|
447
|
+
deadline = _ensure_budget(run_id)
|
|
397
448
|
step, log_abs = store.add_step(run_id, role, "builtin", "CodeBee", note=note,
|
|
398
449
|
model=bi.get("model") or "")
|
|
399
450
|
start = time.time()
|
|
@@ -437,9 +488,11 @@ def _run_builtin_step(run_id, role, bi, prompt, workdir, ev, note="", images=Non
|
|
|
437
488
|
def _on_activity(line):
|
|
438
489
|
store.stream_step(run_id, step["n"], activity=str(line or "")[:200])
|
|
439
490
|
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
491
|
+
remaining = None if deadline is None else max(0.0, deadline - time.monotonic())
|
|
492
|
+
res = builtin_agent.run(bi, prompt, workdir, timeout=remaining or 180,
|
|
493
|
+
deadline=deadline, cancel_event=ev, log=_log, images=images,
|
|
494
|
+
on_reason=_on_reason, on_stream=_on_stream,
|
|
495
|
+
on_activity=_on_activity)
|
|
443
496
|
if followups and res.get("ok"):
|
|
444
497
|
clean, fups = _parse_followups(res.get("text") or "")
|
|
445
498
|
if fups:
|
|
@@ -460,9 +513,13 @@ def _run_builtin_step(run_id, role, bi, prompt, workdir, ev, note="", images=Non
|
|
|
460
513
|
res["raw"]["duration"] = time.time() - start
|
|
461
514
|
res["tokens"] = int(usage.get("total") or 0)
|
|
462
515
|
res.setdefault("cost_usd", 0.0)
|
|
463
|
-
# 先收尾再查取消:与 _run_step 同序,防步骤记录停在「运行中」变僵尸
|
|
464
|
-
_finish_step_result(run_id, step, res, role, agent_pseudo, start)
|
|
465
|
-
|
|
516
|
+
# 先收尾再查取消:与 _run_step 同序,防步骤记录停在「运行中」变僵尸
|
|
517
|
+
_finish_step_result(run_id, step, res, role, agent_pseudo, start)
|
|
518
|
+
if (res.get("raw") or {}).get("deadline_exceeded"):
|
|
519
|
+
store.update_run(run_id, expected_status="running", status="timeout",
|
|
520
|
+
ended_at=_now(), error="任务总时限已到")
|
|
521
|
+
raise TaskTimeout()
|
|
522
|
+
_check_cancel(ev)
|
|
466
523
|
return res
|
|
467
524
|
|
|
468
525
|
|
|
@@ -485,8 +542,9 @@ def _budget_max_tokens():
|
|
|
485
542
|
return 0
|
|
486
543
|
|
|
487
544
|
|
|
488
|
-
def _spawn_step(session_run_id, role, agent, prompt, workdir, readonly, ev,
|
|
489
|
-
timeout, resume, step, log_abs, images=None, require_tools=False
|
|
545
|
+
def _spawn_step(session_run_id, role, agent, prompt, workdir, readonly, ev,
|
|
546
|
+
timeout, resume, step, log_abs, images=None, require_tools=False,
|
|
547
|
+
deadline=None):
|
|
490
548
|
"""真实 CLI 调用:压缩灰度路径或原路径。"""
|
|
491
549
|
# T2.1 预算闸:已用 token 达到单次 run 上限 → 阻断后续真实调用(ENV_BLOCK)。
|
|
492
550
|
# 只拦「下一步」,允许越过线的当前步完成;auto 续跑可在用户调高预算后接手。
|
|
@@ -509,10 +567,10 @@ def _spawn_step(session_run_id, role, agent, prompt, workdir, readonly, ev,
|
|
|
509
567
|
if _compaction_enabled() and not resume:
|
|
510
568
|
# Phase 2(1D):撑爆 → 压缩 → 守门重试;同时把 usage 累进 token_meter(1C)
|
|
511
569
|
session = _get_session(session_run_id)
|
|
512
|
-
llm_caller = _make_llm_caller(agent, workdir)
|
|
513
|
-
call_kwargs = dict(workdir=workdir, readonly=readonly,
|
|
514
|
-
timeout=timeout, cancel_event=ev, log_path=str(log_abs),
|
|
515
|
-
images=images, require_tools=require_tools)
|
|
570
|
+
llm_caller = _make_llm_caller(agent, workdir, deadline=deadline)
|
|
571
|
+
call_kwargs = dict(workdir=workdir, readonly=readonly,
|
|
572
|
+
timeout=timeout, cancel_event=ev, log_path=str(log_abs),
|
|
573
|
+
images=images, require_tools=require_tools, deadline=deadline)
|
|
516
574
|
|
|
517
575
|
def _call(p, **kw):
|
|
518
576
|
nonlocal usage_recorded
|
|
@@ -537,9 +595,10 @@ def _spawn_step(session_run_id, role, agent, prompt, workdir, readonly, ev,
|
|
|
537
595
|
session, _call, prompt, model=agent.get("model") or "",
|
|
538
596
|
llm_caller=llm_caller)
|
|
539
597
|
else:
|
|
540
|
-
res = runner.run_agent(agent, prompt, workdir=workdir, readonly=readonly,
|
|
541
|
-
timeout=timeout, cancel_event=ev, log_path=str(log_abs),
|
|
542
|
-
resume=resume, images=images, require_tools=require_tools
|
|
598
|
+
res = runner.run_agent(agent, prompt, workdir=workdir, readonly=readonly,
|
|
599
|
+
timeout=timeout, cancel_event=ev, log_path=str(log_abs),
|
|
600
|
+
resume=resume, images=images, require_tools=require_tools,
|
|
601
|
+
deadline=deadline)
|
|
543
602
|
# 默认关闭压缩和 resume 都走直通分支,也必须把真实 usage 送进预算表;否则
|
|
544
603
|
# 下一步永远看到 used=0,max_tokens_per_run 只是一个无效设置。
|
|
545
604
|
if not usage_recorded:
|
|
@@ -726,14 +785,17 @@ def _files_hint(sub):
|
|
|
726
785
|
"`%s`" % str(f) for f in files[:10])
|
|
727
786
|
|
|
728
787
|
|
|
729
|
-
def _run_verify(run_id, task, workdir, ev):
|
|
730
|
-
"""确定性验证。返回 (verify_pass, ran)。"""
|
|
731
|
-
|
|
732
|
-
|
|
788
|
+
def _run_verify(run_id, task, workdir, ev):
|
|
789
|
+
"""确定性验证。返回 (verify_pass, ran)。"""
|
|
790
|
+
deadline = _ensure_budget(run_id)
|
|
791
|
+
if not task.get("verify_command"):
|
|
792
|
+
return True, False
|
|
733
793
|
step, log_abs = store.add_step(run_id, "verify", "builtin", "内置验证器")
|
|
734
794
|
start = time.time()
|
|
735
|
-
|
|
736
|
-
|
|
795
|
+
timeout, deadline = _step_timeout(run_id, 600, deadline)
|
|
796
|
+
r = runner.run_process(shell_cmd=task["verify_command"], cwd=workdir,
|
|
797
|
+
timeout=timeout, deadline=deadline, cancel_event=ev,
|
|
798
|
+
log_path=str(log_abs))
|
|
737
799
|
ok = r["ok"]
|
|
738
800
|
# 与 _finish_step_result 同一套显示层细分:超时/取消杀停不再冒充「失败」
|
|
739
801
|
if r.get("cancelled"):
|
|
@@ -743,9 +805,13 @@ def _run_verify(run_id, task, workdir, ev):
|
|
|
743
805
|
else:
|
|
744
806
|
v_status = "done" if ok else "failed"
|
|
745
807
|
v_sum = "验证通过" if ok else "验证失败(exit %s)" % r["exit_code"]
|
|
746
|
-
store.finish_step(run_id, step["n"], v_status,
|
|
747
|
-
summary=v_sum,
|
|
748
|
-
exit_code=r["exit_code"], duration_s=time.time() - start)
|
|
808
|
+
store.finish_step(run_id, step["n"], v_status,
|
|
809
|
+
summary=v_sum,
|
|
810
|
+
exit_code=r["exit_code"], duration_s=time.time() - start)
|
|
811
|
+
if r.get("deadline_exceeded"):
|
|
812
|
+
store.update_run(run_id, expected_status="running", status="timeout",
|
|
813
|
+
ended_at=_now(), error="任务总时限已到")
|
|
814
|
+
raise TaskTimeout()
|
|
749
815
|
_check_cancel(ev)
|
|
750
816
|
return ok, True
|
|
751
817
|
|
|
@@ -1057,11 +1123,14 @@ def _run_code(run, task, agents, ev, stats, mode):
|
|
|
1057
1123
|
_wait_gate(run_id, ev)
|
|
1058
1124
|
plan_step, plan_log = store.add_step(run_id, "plan", impl["id"], impl.get("label"),
|
|
1059
1125
|
note=route.get("implementer", ""))
|
|
1060
|
-
plan =
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1126
|
+
plan = _planner_call(
|
|
1127
|
+
planner.make_code_plan, _steered_task(run_id, task),
|
|
1128
|
+
modelhub.bind_agent(impl, difficulty),
|
|
1129
|
+
_resume_workdir(resume_ctx, workdir), ev,
|
|
1130
|
+
resume=resume_ctx["session"] if resume_ctx else None,
|
|
1131
|
+
log_path=str(plan_log) if plan_log else None,
|
|
1132
|
+
deadline=_run_deadline(run_id))
|
|
1133
|
+
_ensure_budget(run_id)
|
|
1065
1134
|
# 规划器判定优先于启发式(仅当用户未显式指定难度)
|
|
1066
1135
|
if not explicit and plan.get("difficulty") in ("easy", "hard"):
|
|
1067
1136
|
difficulty = plan["difficulty"]
|
|
@@ -1171,11 +1240,19 @@ def _run_code(run, task, agents, ev, stats, mode):
|
|
|
1171
1240
|
return False, res
|
|
1172
1241
|
return True, res
|
|
1173
1242
|
|
|
1174
|
-
ok, res = _run_one(impl_agent)
|
|
1175
|
-
if ok:
|
|
1176
|
-
return True
|
|
1177
|
-
|
|
1178
|
-
|
|
1243
|
+
ok, res = _run_one(impl_agent)
|
|
1244
|
+
if ok:
|
|
1245
|
+
return True
|
|
1246
|
+
if runner._auth_error(res.get("error") or ""):
|
|
1247
|
+
auth_err = ("实现步骤失败(上游拒绝认证;已停止自动换 CLI,避免隐式切换供应商)。"
|
|
1248
|
+
"请检查当前绑定的 Key、账号权限和端点;需要认证降级时,"
|
|
1249
|
+
"请在该 CLI 的绑定链中显式配置不同凭据。错误:%s"
|
|
1250
|
+
% (res.get("error") or "")[:500])
|
|
1251
|
+
store.update_run(run_id, expected_status="running", status="failed",
|
|
1252
|
+
error=auth_err, ended_at=_now())
|
|
1253
|
+
return False
|
|
1254
|
+
# 单路实现失败:Best-of-N 赛马兜底(借鉴 orca worktree 择优)——多路并行
|
|
1255
|
+
# 各自 worktree 隔离实现+验证,胜者 diff 回主工作区;失败回落走换将
|
|
1179
1256
|
if (mode == "auto" and impl_agent.get("mode") == "real"
|
|
1180
1257
|
and max(1, min(3, int(task.get("best_of") or 1))) >= 2
|
|
1181
1258
|
and resume_ctx is None
|
|
@@ -2329,8 +2406,11 @@ def _run_serial_review(run, task, agents, ev, stats, mode, critics, impl, route,
|
|
|
2329
2406
|
_wait_gate(run_id, ev)
|
|
2330
2407
|
outline_step, outline_log = store.add_step(run_id, "outline", impl["id"], impl.get("label"),
|
|
2331
2408
|
note=route.get("author", ""))
|
|
2332
|
-
outline =
|
|
2333
|
-
|
|
2409
|
+
outline = _planner_call(
|
|
2410
|
+
planner.make_serial_outline, _steered_task(run_id, task), impl, workdir, ev,
|
|
2411
|
+
log_path=str(outline_log) if outline_log else None,
|
|
2412
|
+
deadline=_run_deadline(run_id))
|
|
2413
|
+
_ensure_budget(run_id)
|
|
2334
2414
|
if outline.get("degraded") and impl.get("mode") != "mock":
|
|
2335
2415
|
# 兜底模板只有章号没有情节,据此写出的两万字等于废稿——
|
|
2336
2416
|
# 中止并交给自动续跑等编排者恢复后重试,而不是空转烧配额。
|
|
@@ -3500,8 +3580,11 @@ def _run_content_review(run, task, agents, ev, stats, mode):
|
|
|
3500
3580
|
draft_note = route.get("author", "") if mode != "manual" else ""
|
|
3501
3581
|
|
|
3502
3582
|
# 编排者大纲:只对真实执行有意义;失败静默退回无大纲(喂入带指令的任务副本)
|
|
3503
|
-
outline = (planner.make_review_outline
|
|
3504
|
-
|
|
3583
|
+
outline = (_planner_call(planner.make_review_outline,
|
|
3584
|
+
_steered_task(run_id, task),
|
|
3585
|
+
deadline=_run_deadline(run_id))
|
|
3586
|
+
if workflow["outline"] and impl.get("mode") != "mock" else None)
|
|
3587
|
+
_ensure_budget(run_id)
|
|
3505
3588
|
if outline:
|
|
3506
3589
|
workflow = task_compile.content_workflow(
|
|
3507
3590
|
task, difficulty, plan=outline, mode=mode)
|
|
@@ -3989,10 +4072,14 @@ def execute_run(run_id):
|
|
|
3989
4072
|
if store.update_run(run_id, expected_status=initial_status, status="running",
|
|
3990
4073
|
started_at=_now()) is None:
|
|
3991
4074
|
return
|
|
3992
|
-
if task is None:
|
|
3993
|
-
store.update_run(run_id, expected_status="running", status="failed",
|
|
3994
|
-
error="找不到任务 %s" % run.get("task_id"), ended_at=_now())
|
|
3995
|
-
return
|
|
4075
|
+
if task is None:
|
|
4076
|
+
store.update_run(run_id, expected_status="running", status="failed",
|
|
4077
|
+
error="找不到任务 %s" % run.get("task_id"), ended_at=_now())
|
|
4078
|
+
return
|
|
4079
|
+
try:
|
|
4080
|
+
_ensure_budget(run_id)
|
|
4081
|
+
except TaskTimeout:
|
|
4082
|
+
return
|
|
3996
4083
|
# 统一任务编译:旧字段继续供各引擎读取,规格作为运行级诊断与调度输入落盘。
|
|
3997
4084
|
task_spec = task_compile.compile_task(task)
|
|
3998
4085
|
store.update_run(run_id, task_spec=task_spec,
|
|
@@ -4111,9 +4198,12 @@ def execute_run(run_id):
|
|
|
4111
4198
|
critics, impl, route, resume_ctx, difficulty)
|
|
4112
4199
|
else:
|
|
4113
4200
|
_run_content_review(run, task, agents, ev, stats, mode)
|
|
4114
|
-
except
|
|
4115
|
-
store.update_run(run_id, expected_status="running",
|
|
4116
|
-
status="
|
|
4201
|
+
except TaskTimeout:
|
|
4202
|
+
store.update_run(run_id, expected_status="running",
|
|
4203
|
+
status="timeout", ended_at=_now(), error="任务总时限已到")
|
|
4204
|
+
except Cancelled:
|
|
4205
|
+
store.update_run(run_id, expected_status="running",
|
|
4206
|
+
status="cancelled", ended_at=_now())
|
|
4117
4207
|
except Exception as e:
|
|
4118
4208
|
import traceback
|
|
4119
4209
|
store.update_run(run_id, expected_status="running", status="failed",
|
package/app/core/planner.py
CHANGED
|
@@ -69,7 +69,7 @@ def _log_streamer(log_path, min_chars=400, min_secs=0.8):
|
|
|
69
69
|
return cb
|
|
70
70
|
|
|
71
71
|
|
|
72
|
-
def _outline_timeout():
|
|
72
|
+
def _outline_timeout():
|
|
73
73
|
"""大纲/规划的 CLI 兜底超时(秒):env TUTTI_OUTLINE_TIMEOUT(秒)优先,
|
|
74
74
|
其次设置 orchestrator.outline_timeout_s,缺省 900。"""
|
|
75
75
|
try:
|
|
@@ -86,7 +86,16 @@ def _outline_timeout():
|
|
|
86
86
|
return v
|
|
87
87
|
except Exception:
|
|
88
88
|
pass
|
|
89
|
-
return DEFAULT_OUTLINE_TIMEOUT
|
|
89
|
+
return DEFAULT_OUTLINE_TIMEOUT
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _deadline_timeout(deadline, requested):
|
|
93
|
+
if deadline is None:
|
|
94
|
+
return requested
|
|
95
|
+
try:
|
|
96
|
+
return max(1, min(float(requested), deadline - time.monotonic()))
|
|
97
|
+
except (TypeError, ValueError):
|
|
98
|
+
return requested
|
|
90
99
|
|
|
91
100
|
|
|
92
101
|
def _log_usage(source, role, task, res, agent=None, tool="", model="", provider="",
|
|
@@ -346,7 +355,8 @@ def book_volume_plan(task, outline, upto):
|
|
|
346
355
|
return volumes.merge_titles(plan, named)
|
|
347
356
|
|
|
348
357
|
|
|
349
|
-
def make_serial_outline(task, author_agent=None, workdir=None, ev=None, log_path=None
|
|
358
|
+
def make_serial_outline(task, author_agent=None, workdir=None, ev=None, log_path=None,
|
|
359
|
+
deadline=None):
|
|
350
360
|
"""连载大纲:编排者 API 优先 → 作者 CLI → 模板。返回 {book_title, chapters:[{title,beats,hook}]}。
|
|
351
361
|
|
|
352
362
|
续写批次(serial.start_chapter > 1):改用续写大纲提示词,注入前情大纲与
|
|
@@ -443,8 +453,8 @@ def make_serial_outline(task, author_agent=None, workdir=None, ev=None, log_path
|
|
|
443
453
|
# prompt 逐字节相同,命中省一次全量规划调用。第 2 次尝试故意
|
|
444
454
|
# 旁路缓存——首试 ok 但 JSON 不合法时,重试要的是「再抽一次样
|
|
445
455
|
# 本」而不是同一份坏文本。
|
|
446
|
-
res = modelhub.chat(prov["id"], model, prompt,
|
|
447
|
-
max_tokens=16000, timeout=300, on_delta=cb,
|
|
456
|
+
res = modelhub.chat(prov["id"], model, prompt,
|
|
457
|
+
max_tokens=16000, timeout=_deadline_timeout(deadline, 300), on_delta=cb,
|
|
448
458
|
reasoning_effort=_reasoning_effort(task),
|
|
449
459
|
cache_ttl=3600 if _attempt == 1 else 0)
|
|
450
460
|
cb.flush()
|
|
@@ -473,7 +483,8 @@ def make_serial_outline(task, author_agent=None, workdir=None, ev=None, log_path
|
|
|
473
483
|
_append_log(log_path, "===== 作者 CLI(%s)=====" % author_agent.get("id", "?"))
|
|
474
484
|
res = runner.run_agent(modelhub.bind_agent(author_agent), prompt,
|
|
475
485
|
workdir=workdir or task.get("workdir"), readonly=True,
|
|
476
|
-
timeout=_outline_timeout(),
|
|
486
|
+
timeout=_deadline_timeout(deadline, _outline_timeout()),
|
|
487
|
+
deadline=deadline, cancel_event=ev,
|
|
477
488
|
log_path=log_path)
|
|
478
489
|
_log_usage("outline", "outline", task, res, agent=author_agent)
|
|
479
490
|
if not res["ok"]:
|
|
@@ -591,11 +602,13 @@ def _reasoning_effort(task):
|
|
|
591
602
|
return "medium"
|
|
592
603
|
|
|
593
604
|
|
|
594
|
-
def make_code_plan(task, planner_agent, workdir, ev=None, resume=None, log_path=None
|
|
605
|
+
def make_code_plan(task, planner_agent, workdir, ev=None, resume=None, log_path=None,
|
|
606
|
+
deadline=None):
|
|
595
607
|
"""代码任务计划:编排者 API 优先 → CLI 智能体 → 单步模板。"""
|
|
596
608
|
orch = _orchestrator()
|
|
597
609
|
if orch:
|
|
598
|
-
plan = _orch_code_plan(task, orch[0], orch[1], log_path=log_path
|
|
610
|
+
plan = _orch_code_plan(task, orch[0], orch[1], log_path=log_path,
|
|
611
|
+
deadline=deadline)
|
|
599
612
|
if plan:
|
|
600
613
|
return plan
|
|
601
614
|
if planner_agent is None:
|
|
@@ -607,7 +620,8 @@ def make_code_plan(task, planner_agent, workdir, ev=None, resume=None, log_path=
|
|
|
607
620
|
.replace("__CONTEXT__", task.get("context") or "(无)")
|
|
608
621
|
.replace("__VERIFY__", task.get("verify_command") or "(未配置)"))
|
|
609
622
|
res = runner.run_agent(planner_agent, prompt, workdir=workdir, readonly=True,
|
|
610
|
-
timeout=
|
|
623
|
+
timeout=_deadline_timeout(deadline, 300), deadline=deadline,
|
|
624
|
+
cancel_event=ev, resume=resume,
|
|
611
625
|
log_path=log_path)
|
|
612
626
|
_log_usage("plan", "plan", task, res, agent=planner_agent)
|
|
613
627
|
data = runner.extract_json(res.get("text") or "")
|
|
@@ -624,7 +638,7 @@ def _fallback_code_plan(task, note):
|
|
|
624
638
|
"steps": [{"title": "实现任务", "detail": task["goal"]}]}
|
|
625
639
|
|
|
626
640
|
|
|
627
|
-
def _orch_code_plan(task, prov, model, log_path=None):
|
|
641
|
+
def _orch_code_plan(task, prov, model, log_path=None, deadline=None):
|
|
628
642
|
cb = _log_streamer(log_path)
|
|
629
643
|
# §07 T2.2:代码计划同任务同 prompt(断点续跑/重试),TTL 1h 精确缓存
|
|
630
644
|
res = modelhub.chat(prov["id"], model,
|
|
@@ -632,7 +646,7 @@ def _orch_code_plan(task, prov, model, log_path=None):
|
|
|
632
646
|
.replace("__GOAL__", task["goal"])
|
|
633
647
|
.replace("__CONTEXT__", task.get("context") or "(无)")
|
|
634
648
|
.replace("__VERIFY__", task.get("verify_command") or "(未配置)")),
|
|
635
|
-
max_tokens=8000, timeout=300, on_delta=cb,
|
|
649
|
+
max_tokens=8000, timeout=_deadline_timeout(deadline, 300), on_delta=cb,
|
|
636
650
|
reasoning_effort=_reasoning_effort(task),
|
|
637
651
|
cache_ttl=3600)
|
|
638
652
|
cb.flush()
|
|
@@ -652,7 +666,7 @@ def _orch_code_plan(task, prov, model, log_path=None):
|
|
|
652
666
|
"workflow": _plan_workflow(data)}
|
|
653
667
|
|
|
654
668
|
|
|
655
|
-
def make_review_outline(task):
|
|
669
|
+
def make_review_outline(task, deadline=None):
|
|
656
670
|
"""review 类任务:编排者产出写作大纲(失败返回 None,起草退回无大纲)。"""
|
|
657
671
|
orch = _orchestrator()
|
|
658
672
|
if not orch:
|
|
@@ -662,7 +676,7 @@ def make_review_outline(task):
|
|
|
662
676
|
(REVIEW_OUTLINE_PROMPT
|
|
663
677
|
.replace("__GOAL__", task["goal"])
|
|
664
678
|
.replace("__CONTEXT__", task.get("context") or "(无)")),
|
|
665
|
-
max_tokens=8000, timeout=300,
|
|
679
|
+
max_tokens=8000, timeout=_deadline_timeout(deadline, 300),
|
|
666
680
|
reasoning_effort=_reasoning_effort(task),
|
|
667
681
|
cache_ttl=3600) # §07 T2.2:同任务重试幂等,TTL 1h
|
|
668
682
|
_log_usage("outline", "outline", task, res, model=model,
|