codebee 0.1.95 → 0.1.97
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 +733 -720
- package/LICENSE +21 -21
- package/README.md +516 -509
- package/app/core/acceptance.py +100 -100
- package/app/core/agent_context.py +118 -0
- package/app/core/aiflavor.py +197 -197
- package/app/core/analytics.py +38 -38
- package/app/core/artifacts.py +148 -148
- package/app/core/assets.py +110 -110
- package/app/core/attachments.py +623 -623
- package/app/core/audit.py +30 -30
- package/app/core/backup.py +470 -470
- package/app/core/beekeeper.py +183 -183
- package/app/core/benchstore.py +102 -95
- package/app/core/board.py +246 -209
- package/app/core/bookmeta.py +1036 -1036
- package/app/core/bookmeta_catalog.py +180 -180
- package/app/core/branching.py +137 -137
- package/app/core/builtin_agent.py +2055 -1680
- package/app/core/capability.py +144 -144
- package/app/core/chaptersafe.py +32 -32
- package/app/core/checkpoints.py +648 -0
- package/app/core/cleanup.py +303 -303
- package/app/core/compaction.py +201 -201
- package/app/core/contracts.py +255 -255
- package/app/core/covergen.py +356 -356
- package/app/core/defectretro.py +43 -43
- package/app/core/diagnostics.py +114 -114
- package/app/core/dispatch.py +198 -191
- package/app/core/dispatch_log.py +128 -128
- package/app/core/env_scrub.py +85 -85
- package/app/core/errorlog.py +137 -137
- package/app/core/eval_matrix.py +82 -0
- package/app/core/evalbench.py +184 -91
- package/app/core/evaluation.py +174 -174
- package/app/core/failure_notify.py +290 -290
- package/app/core/flow_graph.py +33 -0
- package/app/core/goal_service.py +167 -167
- package/app/core/health.py +389 -389
- package/app/core/history.py +38 -38
- package/app/core/hooks.py +162 -162
- package/app/core/jobs.py +25 -4
- package/app/core/knowledge_pipeline.py +86 -0
- package/app/core/manager.py +2232 -2232
- package/app/core/mcp_client.py +200 -29
- package/app/core/mocks.py +64 -64
- package/app/core/notify.py +201 -201
- package/app/core/novel_quality.py +181 -189
- package/app/core/openapi.py +177 -154
- package/app/core/operations.py +228 -228
- package/app/core/paihang.py +145 -145
- package/app/core/pipeline.py +319 -59
- package/app/core/planner.py +17 -11
- package/app/core/policy.py +84 -0
- package/app/core/portguard.py +111 -111
- package/app/core/presence.py +77 -77
- package/app/core/preview.py +293 -293
- package/app/core/project_memory.py +139 -0
- package/app/core/prompt_guard.py +78 -78
- package/app/core/publish/__init__.py +7 -7
- package/app/core/publish/auto.py +106 -22
- package/app/core/publish/browser.py +694 -655
- package/app/core/publish/chaprows.py +138 -0
- package/app/core/publish/fanqie.py +212 -165
- package/app/core/publish/flow.py +832 -640
- package/app/core/publish/flows-fanqie-calibrated.json +361 -336
- package/app/core/publish/ledger.py +51 -2
- package/app/core/publish/manager.py +1150 -1118
- package/app/core/publish/qimao.py +176 -176
- package/app/core/publish/volumes.py +108 -0
- package/app/core/publish/ws.py +139 -139
- package/app/core/quality_gate.py +194 -194
- package/app/core/redact.py +38 -38
- package/app/core/repeat_guard.py +129 -129
- package/app/core/retrieval.py +137 -137
- package/app/core/router.py +398 -398
- package/app/core/runner.py +2239 -2238
- package/app/core/selfupdate.py +384 -384
- package/app/core/session_log.py +161 -161
- package/app/core/sessions.py +312 -312
- package/app/core/settings.py +33 -5
- package/app/core/settings_schema.py +282 -271
- package/app/core/share_page.py +114 -114
- package/app/core/skill_scan.py +139 -139
- package/app/core/skillpacks/fanqie-novel.md +8 -6
- package/app/core/skillpacks/market/character-bible.md +66 -66
- package/app/core/skillpacks/market/code-risk-checklist.md +58 -58
- package/app/core/skillpacks/market/git-workflow.md +57 -57
- package/app/core/skillpacks/market/release-notes.md +72 -72
- package/app/core/skillpacks/market/weekly-report.md +71 -71
- package/app/core/skillpacks/market/worldview-consistency.md +70 -70
- package/app/core/skillpacks/qimao-signing.md +14 -11
- package/app/core/skills.py +32 -21
- package/app/core/step_runner.py +132 -132
- package/app/core/store.py +24 -0
- package/app/core/story_tracking.py +275 -275
- package/app/core/task_compile.py +199 -199
- package/app/core/telemetry.py +292 -292
- package/app/core/tlsctx.py +64 -64
- package/app/core/token_meter.py +147 -147
- package/app/core/tool_outputs.py +107 -0
- package/app/core/tracing.py +207 -144
- package/app/core/upstream.py +41 -41
- package/app/core/usage.py +1038 -1038
- package/app/core/volumes.py +389 -389
- package/app/core/webhooks.py +89 -0
- package/app/core/wxdigest.py +865 -858
- package/app/core/zentao.py +2692 -2692
- package/app/main.py +179 -10
- package/app/mcp_server.py +244 -244
- package/app/pick_dialog.py +110 -110
- package/app/ui/app.js +563 -21
- package/app/ui/artdiff.js +74 -74
- package/app/ui/assets/community-qr.png +0 -0
- package/app/ui/assets/donate-alipay.jpg +0 -0
- package/app/ui/assets/donate-qr.jpg +0 -0
- package/app/ui/assets/workbench-reference.png +0 -0
- package/app/ui/board.css +451 -666
- package/app/ui/board.html +165 -162
- package/app/ui/board.js +509 -305
- package/app/ui/hive3d.js +242 -0
- package/app/ui/i18n.js +125 -9
- package/app/ui/icons/bee.svg +79 -79
- package/app/ui/index.html +60 -3
- package/app/ui/manifest.json +16 -16
- package/app/ui/qrcode.js +2297 -2297
- package/app/ui/style.css +189 -9
- package/bin/tutti.js +147 -147
- package/package.json +49 -48
- package/tools/wx_reader.py +261 -261
- package/app/.playwright-cli/console-2026-09-23T08-26-03-635Z.log +0 -88
- package/app/.playwright-cli/page-2026-09-23T08-26-03-845Z.yml +0 -126
- package/app/.playwright-cli/page-2026-09-23T08-29-44-203Z.png +0 -0
- package/app/.playwright-cli/page-2026-09-23T08-35-42-046Z.yml +0 -210
- package/app/.playwright-cli/page-2026-09-23T08-36-52-858Z.png +0 -0
- package/app/.playwright-cli/page-2026-09-23T08-37-33-938Z.png +0 -0
- package/app/.playwright-cli/run-detail-after.png +0 -0
- package/app/output/_createbook_body.json +0 -1
- package/app/output/_probe_fq_click.py +0 -50
- package/app/output/_probe_fq_form.py +0 -42
- package/app/output/_probe_fq_modal.py +0 -55
- package/app/output/_probe_fq_points.py +0 -71
- package/app/output/_probe_fq_submit.py +0 -34
- package/app/output/_probe_fq_watch.py +0 -62
package/app/core/pipeline.py
CHANGED
|
@@ -19,7 +19,7 @@ import re
|
|
|
19
19
|
import threading
|
|
20
20
|
import time
|
|
21
21
|
|
|
22
|
-
from . import aiflavor, attachments, branching, catalog, chaptersafe, defectretro, dispatch_log, flows, history, hooks, jobs, knowledge, manager, modelhub, mocks, novel_quality, paihang, planner, quality_gate, registry, router, runner, skills, store, task_compile, usage, volumes
|
|
22
|
+
from . import aiflavor, agent_context, attachments, branching, catalog, chaptersafe, checkpoints, defectretro, dispatch_log, flows, history, hooks, jobs, knowledge, manager, modelhub, mocks, novel_quality, paihang, planner, quality_gate, registry, router, runner, skills, store, task_compile, usage, volumes
|
|
23
23
|
from . import builtin_agent
|
|
24
24
|
from . import diagnostics
|
|
25
25
|
from . import paths as paths_mod
|
|
@@ -229,10 +229,12 @@ def _resume_workdir(resume_ctx, fallback):
|
|
|
229
229
|
|
|
230
230
|
|
|
231
231
|
def _compaction_enabled():
|
|
232
|
-
"""
|
|
233
|
-
|
|
234
|
-
if
|
|
232
|
+
"""环境变量可显式覆盖设置页开关;未设置时读取 settings_v2。"""
|
|
233
|
+
env_value = os.environ.get("TUTTI_COMPACTION", "").strip().lower()
|
|
234
|
+
if env_value in ("1", "true", "yes", "on"):
|
|
235
235
|
return True
|
|
236
|
+
if env_value in ("0", "false", "no", "off"):
|
|
237
|
+
return False
|
|
236
238
|
try:
|
|
237
239
|
from .settings_schema import get as ss_get, register_default_namespaces
|
|
238
240
|
register_default_namespaces()
|
|
@@ -442,7 +444,39 @@ def _run_step(run_id, role, agent, prompt, workdir, readonly, ev, timeout=runner
|
|
|
442
444
|
agent.get("label", agent["id"]), note=note,
|
|
443
445
|
model=agent.get("model") or "",
|
|
444
446
|
provider=_binding_brief(agent))
|
|
447
|
+
try:
|
|
448
|
+
checkpoints.start(
|
|
449
|
+
run_id, step["n"], role, prompt,
|
|
450
|
+
input_data={"workdir": workdir, "readonly": readonly,
|
|
451
|
+
"agent": agent.get("id")},
|
|
452
|
+
attempt=len((store.get_run(run_id) or {}).get("steps") or []))
|
|
453
|
+
except Exception:
|
|
454
|
+
pass
|
|
445
455
|
start = time.time()
|
|
456
|
+
# Third-party CLI backends have their own sandbox semantics, but do not
|
|
457
|
+
# consume CodeBee's per-task policy. Fail closed for narrowed policies
|
|
458
|
+
# instead of treating a prompt or workdir as an OS-level boundary.
|
|
459
|
+
sandbox_error = ""
|
|
460
|
+
try:
|
|
461
|
+
run_row = store.get_run(run_id) or {}
|
|
462
|
+
task_row = store.get_task(run_row.get("task_id")) if run_row.get("task_id") else {}
|
|
463
|
+
if agent.get("mode") != "mock":
|
|
464
|
+
if not task_row:
|
|
465
|
+
sandbox_error = "无法读取任务沙箱策略,外部智能体执行已拒绝"
|
|
466
|
+
else:
|
|
467
|
+
from . import policy
|
|
468
|
+
sandbox_error = _external_sandbox_block_reason(
|
|
469
|
+
agent, workdir, task_row.get("sandbox") or {})
|
|
470
|
+
except Exception:
|
|
471
|
+
# A missing task record is not permission to synthesize a wider policy.
|
|
472
|
+
sandbox_error = "无法读取任务沙箱策略,外部智能体执行已拒绝"
|
|
473
|
+
if sandbox_error:
|
|
474
|
+
res = {"ok": False, "text": "", "json": None, "cost_usd": 0.0,
|
|
475
|
+
"tokens": 0, "usage": None, "error": sandbox_error,
|
|
476
|
+
"raw": {"exit_code": None, "sandbox_blocked": True},
|
|
477
|
+
"kind": agent.get("kind", "generic"), "model": agent.get("model")}
|
|
478
|
+
_finish_step_result(run_id, step, res, role, agent, start)
|
|
479
|
+
return res
|
|
446
480
|
if dead_binding:
|
|
447
481
|
from .error_codes import ErrorCode
|
|
448
482
|
res = {"ok": False, "text": "", "json": None, "cost_usd": 0.0,
|
|
@@ -484,11 +518,47 @@ def _run_step(run_id, role, agent, prompt, workdir, readonly, ev, timeout=runner
|
|
|
484
518
|
if directive_block:
|
|
485
519
|
prompt = directive_block + "\n\n---\n\n" + prompt
|
|
486
520
|
effective_prompt = (guard["reminder"] + "\n\n---\n\n" + prompt) if guard["reminder"] else prompt
|
|
521
|
+
git_checkpoint = None
|
|
522
|
+
try:
|
|
523
|
+
git_checkpoint = checkpoints.begin_git_step(run_id, workdir)
|
|
524
|
+
except Exception:
|
|
525
|
+
git_checkpoint = {"ok": False, "error": "文件检查点准备失败,外部智能体未启动"}
|
|
526
|
+
if not git_checkpoint.get("ok"):
|
|
527
|
+
res = {"ok": False, "text": "", "json": None, "cost_usd": 0.0,
|
|
528
|
+
"tokens": 0, "usage": None,
|
|
529
|
+
"error": git_checkpoint.get("error") or "文件检查点准备失败",
|
|
530
|
+
"raw": {"exit_code": None, "checkpoint_blocked": True},
|
|
531
|
+
"kind": agent.get("kind", "generic"), "model": agent.get("model")}
|
|
532
|
+
_finish_step_result(run_id, step, res, role, agent, start)
|
|
533
|
+
return res
|
|
487
534
|
res = _spawn_step(session_run_id=run_id, role=role, agent=agent,
|
|
488
535
|
prompt=effective_prompt, workdir=workdir, readonly=readonly,
|
|
489
536
|
ev=ev, timeout=timeout, resume=resume, step=step,
|
|
490
537
|
log_abs=log_abs, images=images, require_tools=require_tools,
|
|
491
538
|
deadline=deadline)
|
|
539
|
+
checkpoint_warning = git_checkpoint.get("warning", "")
|
|
540
|
+
if git_checkpoint.get("supported"):
|
|
541
|
+
try:
|
|
542
|
+
finished_checkpoint = checkpoints.finish_git_step(
|
|
543
|
+
run_id, workdir, git_checkpoint.get("head"),
|
|
544
|
+
git_checkpoint.get("before_paths") or [])
|
|
545
|
+
checkpoint_warning = finished_checkpoint.get("warning") or ""
|
|
546
|
+
if not finished_checkpoint.get("ok"):
|
|
547
|
+
checkpoint_warning = finished_checkpoint.get("warning") or "外部智能体文件快照未能完整记录"
|
|
548
|
+
except Exception:
|
|
549
|
+
checkpoint_warning = "外部智能体文件快照记录失败;本步骤改动可能无法恢复"
|
|
550
|
+
if checkpoint_warning:
|
|
551
|
+
res["checkpoint_warning"] = checkpoint_warning
|
|
552
|
+
if res.get("ok"):
|
|
553
|
+
res["text"] = (res.get("text") or "") + "\n\n(检查点范围提示:%s)" % checkpoint_warning
|
|
554
|
+
elif res.get("error"):
|
|
555
|
+
res["error"] = res["error"] + ";" + checkpoint_warning
|
|
556
|
+
if log_abs:
|
|
557
|
+
try:
|
|
558
|
+
with log_abs.open("a", encoding="utf-8") as handle:
|
|
559
|
+
handle.write("\n[检查点] %s\n" % checkpoint_warning)
|
|
560
|
+
except OSError:
|
|
561
|
+
pass
|
|
492
562
|
# 先收尾再查取消:取消时进程已被 run_process 杀停,若先抛 Cancelled,
|
|
493
563
|
# 步骤记录会永远停在「运行中」变僵尸(与 _run_verify 的顺序对齐)
|
|
494
564
|
_finish_step_result(run_id, step, res, role, agent, start)
|
|
@@ -500,6 +570,32 @@ def _run_step(run_id, role, agent, prompt, workdir, readonly, ev, timeout=runner
|
|
|
500
570
|
return res
|
|
501
571
|
|
|
502
572
|
|
|
573
|
+
def _external_sandbox_block_reason(agent, workdir, sandbox):
|
|
574
|
+
"""Reject task policies that this CLI launcher cannot enforce.
|
|
575
|
+
|
|
576
|
+
Codex is currently the only supported external runner with an OS-backed
|
|
577
|
+
workspace write sandbox. Other CLI integrations have no equivalent boundary
|
|
578
|
+
in this launcher and must use the in-process agent/tool executor instead.
|
|
579
|
+
"""
|
|
580
|
+
if (agent or {}).get("mode") == "mock":
|
|
581
|
+
return ""
|
|
582
|
+
from . import policy
|
|
583
|
+
if (agent or {}).get("kind") != "codex":
|
|
584
|
+
return "沙箱拒绝:该外部 CLI 没有经验证的 OS 文件隔离;请改用内置 Agent"
|
|
585
|
+
root = os.path.realpath(workdir or ".")
|
|
586
|
+
normalized = policy.normalize_sandbox(sandbox, root)
|
|
587
|
+
roots = [os.path.realpath(x) for x in normalized.get("allowed_roots") or []]
|
|
588
|
+
if normalized.get("network") is False:
|
|
589
|
+
return "沙箱拒绝:该 CLI 后端未接入网络隔离,拒绝运行网络受限任务"
|
|
590
|
+
if roots != [root]:
|
|
591
|
+
return "沙箱拒绝:该 CLI 后端未接入允许目录隔离,拒绝运行目录受限任务"
|
|
592
|
+
if normalized.get("disabled_tools"):
|
|
593
|
+
return "权限拒绝:该 CLI 后端无法执行 CodeBee 工具禁用策略,拒绝运行"
|
|
594
|
+
if normalized.get("env_allowlist"):
|
|
595
|
+
return "沙箱拒绝:该 CLI 后端未接入环境变量白名单,拒绝运行受限任务"
|
|
596
|
+
return ""
|
|
597
|
+
|
|
598
|
+
|
|
503
599
|
def _create_task_from_builtin(payload):
|
|
504
600
|
"""Create and enqueue a task requested by the direct conversation tool."""
|
|
505
601
|
task = store.create_task(payload)
|
|
@@ -579,7 +675,8 @@ def _run_builtin_step(run_id, role, bi, prompt, workdir, ev, note="", images=Non
|
|
|
579
675
|
deadline=deadline, cancel_event=ev, log=_log, images=images,
|
|
580
676
|
on_reason=_on_reason, on_stream=_on_stream,
|
|
581
677
|
on_activity=_on_activity,
|
|
582
|
-
task_creator=_create_task_from_builtin
|
|
678
|
+
task_creator=_create_task_from_builtin,
|
|
679
|
+
sandbox=task.get("sandbox"), checkpoint_run_id=run_id)
|
|
583
680
|
if followups and res.get("ok"):
|
|
584
681
|
clean, fups = _parse_followups(res.get("text") or "")
|
|
585
682
|
if fups:
|
|
@@ -779,6 +876,17 @@ def _finish_step_result(run_id, step, res, role, agent, start):
|
|
|
779
876
|
# 思考过程(内置智能体流式抓取):落进步骤记录,对话气泡折叠展示
|
|
780
877
|
thinking=res.get("reasoning") or None,
|
|
781
878
|
partial=bool(res.get("partial")))
|
|
879
|
+
try:
|
|
880
|
+
checkpoint_status = ("unknown" if raw.get("unknown") else
|
|
881
|
+
"timeout" if status == "timeout" else
|
|
882
|
+
"cancelled" if status == "cancelled" else
|
|
883
|
+
"done" if status == "done" else "failed")
|
|
884
|
+
checkpoints.finish(
|
|
885
|
+
run_id, step["n"], checkpoint_status,
|
|
886
|
+
error=res.get("error") or "", output=res.get("text") or "",
|
|
887
|
+
metadata={"role": role, "agent": agent.get("id") or ""})
|
|
888
|
+
except Exception:
|
|
889
|
+
pass
|
|
782
890
|
# 错误台账:失败/超时各记一条结构化记录(遥测与诊断包的数据源)。
|
|
783
891
|
# 用户主动取消不入账——那不是产品问题;detail 只存脱敏后的失败摘录。
|
|
784
892
|
if status in ("failed", "timeout"):
|
|
@@ -1442,6 +1550,28 @@ def _run_code(run, task, agents, ev, stats, mode):
|
|
|
1442
1550
|
if project_memory:
|
|
1443
1551
|
task = dict(task, context=(task.get("context") or "") + "\n\n" + project_memory)
|
|
1444
1552
|
|
|
1553
|
+
# 断点简报(B):重试继承上一遍失败/中断 run 的现场,注入规划器与实现者
|
|
1554
|
+
# 共用的 __CONTEXT__——「走到哪、改了什么、还剩什么」不再从零重建上下文。
|
|
1555
|
+
_handoff = run.get("handoff") or {}
|
|
1556
|
+
_brief = _handoff_brief(_handoff)
|
|
1557
|
+
if _brief:
|
|
1558
|
+
task = dict(task, context=(task.get("context") or "") + "\n\n" + _brief)
|
|
1559
|
+
# 断点会话(A):上一遍实现者的 CLI 会话 id,implement_all 里按人复用。
|
|
1560
|
+
_inh_session = run.get("inherit_session") or {}
|
|
1561
|
+
|
|
1562
|
+
def _keep_sid(agent_row, agent_b, r):
|
|
1563
|
+
"""记录最后一次实现的会话 id:run 内 fix 轮复用 + 落盘 run 供重试跨 run 续会话。"""
|
|
1564
|
+
sid = _resume_sid(agent_b, r.get("sid"))
|
|
1565
|
+
if sid:
|
|
1566
|
+
impl_sid[0] = sid
|
|
1567
|
+
try:
|
|
1568
|
+
store.update_run(run_id, impl_session={
|
|
1569
|
+
"agent": agent_row.get("id"), "session": sid,
|
|
1570
|
+
"workdir": workdir})
|
|
1571
|
+
except Exception:
|
|
1572
|
+
pass
|
|
1573
|
+
return sid
|
|
1574
|
+
|
|
1445
1575
|
# ---- 规划。低风险短任务直接把目标作为单步计划,省掉一次独立模型调用;
|
|
1446
1576
|
# 附件或较长的用户背景仍先规划,避免快速路径漏读约束。
|
|
1447
1577
|
fast_path = (mode == "fast" or
|
|
@@ -1507,11 +1637,20 @@ def _run_code(run, task, agents, ev, stats, mode):
|
|
|
1507
1637
|
|
|
1508
1638
|
def implement_all(impl_agent, prefix_note):
|
|
1509
1639
|
nonlocal reviewer
|
|
1510
|
-
#
|
|
1640
|
+
# 会话延续只对原实现者有效;换将后新智能体没有该会话,必须丢弃。
|
|
1641
|
+
# 优先级:任务级 resume(用户显式指定)> 断点继承会话(A:跨 run 续跑,
|
|
1642
|
+
# 仅同一实现者 + 同一工作目录 + 该 CLI 支持续会话时复用)
|
|
1643
|
+
inh_sid = ""
|
|
1644
|
+
if (not resume_ctx and _inh_session.get("session")
|
|
1645
|
+
and _inh_session.get("agent") == impl_agent["id"]
|
|
1646
|
+
and _same_workdir(_inh_session.get("workdir") or "", workdir)
|
|
1647
|
+
and _resume_sid(impl_agent, _inh_session.get("session"))):
|
|
1648
|
+
inh_sid = _inh_session["session"]
|
|
1511
1649
|
use_resume = (resume_ctx["session"]
|
|
1512
|
-
if (resume_ctx and impl_agent["id"] == resume_ctx["agent"]["id"])
|
|
1650
|
+
if (resume_ctx and impl_agent["id"] == resume_ctx["agent"]["id"])
|
|
1651
|
+
else (inh_sid or None))
|
|
1513
1652
|
# 续会话时 CLI 要在会话所属项目目录下启动,否则定位不到会话
|
|
1514
|
-
step_wd = _resume_workdir(resume_ctx, workdir) if use_resume else workdir
|
|
1653
|
+
step_wd = _resume_workdir(resume_ctx, workdir) if (resume_ctx and use_resume) else workdir
|
|
1515
1654
|
|
|
1516
1655
|
def _run_one(agt):
|
|
1517
1656
|
"""用指定智能体跑全部子任务;返回 (ok, 最后一次 res)。"""
|
|
@@ -1557,10 +1696,20 @@ def _run_code(run, task, agents, ev, stats, mode):
|
|
|
1557
1696
|
note=prefix_note if i == 0 else "",
|
|
1558
1697
|
resume=use_resume, images=att_imgs,
|
|
1559
1698
|
require_tools=True, swap_guard=swap_guard)
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1699
|
+
_keep_sid(agt, agt_b, res)
|
|
1700
|
+
# 断点继承会话失效兜底(A):会话可能已被 CLI 清理或跨机不可续。
|
|
1701
|
+
# 认证/配额类死因换会话也没用,不重试;其余丢弃继承会话原样重跑一次。
|
|
1702
|
+
if (not res["ok"] and inh_sid and i == 0 and use_resume == inh_sid
|
|
1703
|
+
and not (res.get("raw") or {}).get("cancelled")
|
|
1704
|
+
and not runner._auth_error(res.get("error") or "")
|
|
1705
|
+
and not runner._quota_error(res.get("error") or "")):
|
|
1706
|
+
res = _run_step(run_id, role, agt_b, prompt, step_wd,
|
|
1707
|
+
readonly=False, ev=ev,
|
|
1708
|
+
note=((prefix_note + ";") if prefix_note else "")
|
|
1709
|
+
+ "继承会话续跑失败,已丢弃旧会话全新执行",
|
|
1710
|
+
resume=None, images=att_imgs,
|
|
1711
|
+
require_tools=True, swap_guard=swap_guard)
|
|
1712
|
+
_keep_sid(agt, agt_b, res)
|
|
1564
1713
|
if agt.get("mode") == "mock" and res["ok"]:
|
|
1565
1714
|
try:
|
|
1566
1715
|
mock_path = os.path.abspath(os.path.join(workdir, "mock-impl.txt"))
|
|
@@ -1587,10 +1736,11 @@ def _run_code(run, task, agents, ev, stats, mode):
|
|
|
1587
1736
|
error=auth_err, ended_at=_now())
|
|
1588
1737
|
return False
|
|
1589
1738
|
# 单路实现失败:Best-of-N 赛马兜底(借鉴 orca worktree 择优)——多路并行
|
|
1590
|
-
# 各自 worktree 隔离实现+验证,胜者 diff
|
|
1739
|
+
# 各自 worktree 隔离实现+验证,胜者 diff 回主工作区;失败回落走换将。
|
|
1740
|
+
# 会话延续在场时跳过(N 路共用同一 CLI 会话会互相践踏,与连载同理)
|
|
1591
1741
|
if (mode == "auto" and impl_agent.get("mode") == "real"
|
|
1592
1742
|
and max(1, min(3, int(task.get("best_of") or 1))) >= 2
|
|
1593
|
-
and resume_ctx is None
|
|
1743
|
+
and resume_ctx is None and not inh_sid
|
|
1594
1744
|
and _code_bestof(run, task, impl_agent, difficulty, ev)):
|
|
1595
1745
|
return True
|
|
1596
1746
|
# 实现步失败不立刻判死:2026-09-16 实测配额烧干时 5 连跑全在同一条 CLI 上
|
|
@@ -1737,6 +1887,7 @@ def _run_code(run, task, agents, ev, stats, mode):
|
|
|
1737
1887
|
note="自动修复第 %d 轮" % round_no,
|
|
1738
1888
|
resume=resume_ctx["session"] if resume_ctx else impl_sid[0],
|
|
1739
1889
|
require_tools=True)
|
|
1890
|
+
_keep_sid(impl, modelhub.bind_agent(impl, difficulty), res)
|
|
1740
1891
|
if (res.get("raw") or {}).get("timed_out"):
|
|
1741
1892
|
# 自动修复没有换将/重试兜底;超时后继续验收会把未完成的修复
|
|
1742
1893
|
# 当作正常轮次,让 run 继续显示 running,最终还可能误标 done。
|
|
@@ -1854,7 +2005,7 @@ def _run_code(run, task, agents, ev, stats, mode):
|
|
|
1854
2005
|
_mem_lines = ["改动文件:%s" % (_touched_str or "(无 diff)"),
|
|
1855
2006
|
"验收:%s" % ("通过" if verify_pass else "未通过"),
|
|
1856
2007
|
"修复轮数:%d" % (len(repairs) - 1)]
|
|
1857
|
-
_write_project_memory(task, workdir, _mem_lines)
|
|
2008
|
+
_write_project_memory(task, workdir, _mem_lines, source_run=run_id)
|
|
1858
2009
|
except Exception:
|
|
1859
2010
|
pass
|
|
1860
2011
|
# Stop gate(planning-with-files):收工时活计划若有未勾选项,摘要与 verdict
|
|
@@ -2692,8 +2843,8 @@ __VOLUME__
|
|
|
2692
2843
|
- 撰写本书第 __I__ 章,把本章正文写入文件 `__FILE__`(直接写入该文件,只写本章)。文件必须以 UTF-8 编码保存:PowerShell 一律显式加 `-Encoding UTF8`(如 `Set-Content -Path __FILE__ -Encoding UTF8`),禁止依赖系统默认编码,否则中文会乱码。
|
|
2693
2844
|
- 章节标题:__TITLE__
|
|
2694
2845
|
- 剧情要点:__BEATS__
|
|
2695
|
-
-
|
|
2696
|
-
-
|
|
2846
|
+
- 章末收束/继续阅读理由(可自然收束或留白):__HOOK__
|
|
2847
|
+
- 本章主要情绪/戏剧变化:__HIGHLIGHT__(按其重要性决定篇幅和表现方式;不强制放大,不为满足标签伪造高潮)
|
|
2697
2848
|
__LOCKED_CH__
|
|
2698
2849
|
__STALE_NOTE__
|
|
2699
2850
|
- 正文约 __WORDS__ 字,中文,直接开写正文(可含本章标题行)。
|
|
@@ -2732,23 +2883,23 @@ __STYLE__
|
|
|
2732
2883
|
|
|
2733
2884
|
## 签约返修原则
|
|
2734
2885
|
- 统一叙述语域、句式和视角距离,不在白描、半文半白和网络梗之间跳变。
|
|
2735
|
-
-
|
|
2736
|
-
-
|
|
2886
|
+
- 复杂情感需按其叙事分量呈现;动作、感官、对话、内心直述、环境或留白均可,避免与本稿风格不合的机械扩写。
|
|
2887
|
+
- 重要情绪/戏剧变化要有与其分量相称的呈现;避免概括带过,但不把每个变化都扩写成高潮。
|
|
2737
2888
|
- 删除不改变局面的环境、解释和重复叙述;每段至少完成冲突、信息或人物选择之一。
|
|
2738
2889
|
- 让主角主动做出选择并推动下一步,补足因果桥,不用旁白硬推剧情。
|
|
2739
2890
|
|
|
2740
2891
|
## 要求
|
|
2741
2892
|
- 针对性解决所有 major 问题(含确定性文本信号指出的问题),保持与前后的剧情衔接;字数仍约 __WORDS__ 字。"""
|
|
2742
2893
|
|
|
2743
|
-
SERIAL_GLOBAL_PROMPT = """
|
|
2894
|
+
SERIAL_GLOBAL_PROMPT = """你是独立小说质量评审(不要修改任何文件),不代表任何签约平台。全书各章已完稿,请从**全书整体**视角评审。
|
|
2744
2895
|
请输出一个 ```json 代码块,不要输出其他内容。JSON 结构:
|
|
2745
2896
|
{
|
|
2746
2897
|
"scores": {"__DIMKEYS__"},
|
|
2747
2898
|
"issues": [{"dim": "维度名", "severity": "major|minor", "note": "具体问题(指明哪一章)", "quote": "支撑该问题的稿件原文连续片段(≥8字,逐字摘录不许改写)"}],
|
|
2748
|
-
"summary": "
|
|
2899
|
+
"summary": "一句话总评:所读范围内的文本优势、主要风险与未验证项;不得预测签约结果"
|
|
2749
2900
|
}
|
|
2750
|
-
每个维度打 1-10
|
|
2751
|
-
|
|
2901
|
+
每个维度打 1-10 分,按统一文本 rubric 与所读证据评估,不以“宁严勿宽”压分。重点关注:主线一致性、人物弧光、开篇吸引力、文风表现、情感表达、情节推进与节奏控制。
|
|
2902
|
+
若是全书最初的前 3 章,可观察视角人物、故事承诺、开篇兑现和读者理解成本;固定字数点、钩子/爽点密度仅作参考,不是扣分门槛。发现问题须给可定位原文引文,并区分文本事实、解释和建议。
|
|
2752
2903
|
major 问题必须给 quote(系统会逐条校验引文是否真在稿件中——编造的引文会被降档标记)。
|
|
2753
2904
|
|
|
2754
2905
|
## 全书目标
|
|
@@ -2759,19 +2910,19 @@ __GOAL__
|
|
|
2759
2910
|
__MANUSCRIPT__
|
|
2760
2911
|
---"""
|
|
2761
2912
|
|
|
2762
|
-
#
|
|
2763
|
-
#
|
|
2764
|
-
SERIAL_SIGNING_EVAL_PROMPT = """
|
|
2913
|
+
# 独立文本质量评估(开篇闸门 + 项目检查点共用):沿用稳定 rubric,
|
|
2914
|
+
# 不代表平台决定;按原文证据检查常见拒稿问题,不将经验观察点变成硬门槛。
|
|
2915
|
+
SERIAL_SIGNING_EVAL_PROMPT = """你是独立小说质量评审(不要修改任何文件),不是番茄或七猫的签约编辑,不能代表平台结论。
|
|
2916
|
+
评审稿件__NOTE__,按下方统一文本质量 rubric 评估;如附当前平台规则或编辑原话,可单独核对适配,不得臆测平台内部标准。
|
|
2765
2917
|
请输出一个 ```json 代码块,不要输出其他内容。JSON 结构:
|
|
2766
2918
|
{
|
|
2767
2919
|
"scores": {"__DIMKEYS__"},
|
|
2768
2920
|
"issues": [{"dim": "维度名", "severity": "major|minor", "note": "具体问题(指明哪一章)", "quote": "支撑该问题的稿件原文连续片段(≥8字,逐字摘录不许改写)"}],
|
|
2769
|
-
"summary": "
|
|
2921
|
+
"summary": "一句话内部文本诊断:优势、主要风险和未验证项;不得表述为平台结论或过签预测"
|
|
2770
2922
|
}
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
行文拖沓节奏缓慢(重复铺垫、与主线无关的描写)——每一条都要给出明确判定与证据。
|
|
2923
|
+
统一评分 rubric:1-3 为明显影响理解或阅读的严重问题;4-6 为有具体可定位的弱项;7-8 为基本有效、仍有局部提升空间;9-10 为在本稿目标下表现突出。分数必须由所读文本支撑,不把分数映射成过签概率。
|
|
2924
|
+
五个维度固定为:开篇吸引力、情节推进、文风统一、情感细腻度、节奏控制。每项给证据与未读范围;不能因不用某种套路(如黄金三章、打脸、单一视角)扣分。
|
|
2925
|
+
可检查文风是否突变、情绪是否被概括、开篇承诺是否兑现、因果桥是否成立、重复是否损害节奏;这些是检查问题而不是预设缺陷。没有当前平台规则或明确编辑反馈时,不把质量评审包装成平台审核结论。
|
|
2775
2926
|
|
|
2776
2927
|
## 待评审稿件
|
|
2777
2928
|
---
|
|
@@ -4309,7 +4460,7 @@ def _run_serial_review(run, task, agents, ev, stats, mode, critics, impl, route,
|
|
|
4309
4460
|
agent.get("label"))
|
|
4310
4461
|
time.sleep(0.15)
|
|
4311
4462
|
gj = {"scores": {d: 8.0 for d in dims},
|
|
4312
|
-
"issues": [], "summary": "(mock
|
|
4463
|
+
"issues": [], "summary": "(mock)内部结构评估:整体结构完整;此结果不代表平台签约结论"}
|
|
4313
4464
|
store.finish_step(run_id, step["n"], "done", summary="均分 8.0:(mock)全书达标",
|
|
4314
4465
|
duration_s=0.15)
|
|
4315
4466
|
results[idx] = gj
|
|
@@ -4326,7 +4477,7 @@ def _run_serial_review(run, task, agents, ev, stats, mode, critics, impl, route,
|
|
|
4326
4477
|
"- 本次送审批次为全书第 %d—%d 章(连载中段批次),文本中的前文节选仅供衔接核对;\n"
|
|
4327
4478
|
"- 「黄金一章/黄金三章」口径仅适用于全书第 1—3 章,不得据此扣减本批批首章的开篇分;\n"
|
|
4328
4479
|
"- 本批各章的既定事件以工作目录「章纲/」下的锁定章纲为准(可自行查阅):承段、"
|
|
4329
|
-
"
|
|
4480
|
+
"过渡章的节奏分应对照章纲既定事件与叙事功能是否落实;自然收束、留白均可,不以悬念钩子作为硬门槛。"
|
|
4330
4481
|
"不以高潮段的事件密度为统一标准;\n"
|
|
4331
4482
|
"- 「伏笔悬置」类 major 须先核对章纲的揭示位安排:揭示位写在后批章纲中的伏笔"
|
|
4332
4483
|
"不算悬置,无任何安排的悬置才成立;\n"
|
|
@@ -4894,6 +5045,14 @@ def _run_content_review(run, task, agents, ev, stats, mode):
|
|
|
4894
5045
|
pass
|
|
4895
5046
|
else:
|
|
4896
5047
|
is_research = task.get("type") == "research"
|
|
5048
|
+
# 经验库/知识库注入(review 引擎 13 类型补全,serial 起草同款):
|
|
5049
|
+
# learn_async 面向全部 run 收割,此前注入只在连载链——「全类型收割、
|
|
5050
|
+
# 单链注入」闭环半开。在 _draft_prompt_for 外取一次(bestof 多稿件
|
|
5051
|
+
# 共用一份,命中计数不重复累加);stable_order 保前缀缓存,预算纪律
|
|
5052
|
+
# 由块内自带(skills 包区限额+教训保底 / knowledge 装箱截断)。
|
|
5053
|
+
sk_lessons, _ = skills.block_for(task, stable_order=True, run_id=run_id)
|
|
5054
|
+
kb_block = knowledge.block_for(task)
|
|
5055
|
+
ctx_block = "\n\n".join(p for p in (sk_lessons, kb_block) if p)
|
|
4897
5056
|
|
|
4898
5057
|
def _draft_prompt_for(vfile):
|
|
4899
5058
|
p = (_tpl(task, "draft_prompt", NOVEL_DRAFT_PROMPT).replace("__FILE__", vfile)
|
|
@@ -4927,6 +5086,9 @@ def _run_content_review(run, task, agents, ev, stats, mode):
|
|
|
4927
5086
|
gl = _gitlog_brief(workdir)
|
|
4928
5087
|
if gl:
|
|
4929
5088
|
p += ("\n\n## 本周提交素材(git log,如实取材,缺失勿虚构)\n" + gl)
|
|
5089
|
+
if ctx_block:
|
|
5090
|
+
# 空块不追加:零命中零噪音(空库时 prompt 与旧版字节一致)
|
|
5091
|
+
p += "\n\n" + ctx_block
|
|
4930
5092
|
p += _content_contract(task)
|
|
4931
5093
|
return p
|
|
4932
5094
|
|
|
@@ -4987,6 +5149,16 @@ def _run_content_review(run, task, agents, ev, stats, mode):
|
|
|
4987
5149
|
.replace("__MANUSCRIPT__", manuscript or "(稿件为空!)"))
|
|
4988
5150
|
if task.get("context"):
|
|
4989
5151
|
crit_prompt += "\n\n## 原始任务背景与附件参考\n" + task["context"]
|
|
5152
|
+
# 经验库/知识库注入(serial 逐章评审同款;「## 待评审稿件」锚点由
|
|
5153
|
+
# _ensure_critique_placeholders 保证存在),按证据强度随评审下发
|
|
5154
|
+
sk, _ = skills.block_for(task, stable_order=True, run_id=run_id)
|
|
5155
|
+
if sk:
|
|
5156
|
+
crit_prompt = crit_prompt.replace(
|
|
5157
|
+
"## 待评审稿件", "%s\n\n## 待评审稿件" % sk, 1)
|
|
5158
|
+
kb = knowledge.block_for(task)
|
|
5159
|
+
if kb:
|
|
5160
|
+
crit_prompt = crit_prompt.replace(
|
|
5161
|
+
"## 待评审稿件", "%s\n\n## 待评审稿件" % kb, 1)
|
|
4990
5162
|
# AI 味确定性检测(借鉴 oh-story 去AI味):客观参考线随评审下发,
|
|
4991
5163
|
# 命中才追加——评审官结合上下文判断是否真问题,脚本不直接扣分
|
|
4992
5164
|
crit_prompt = aiflavor.inject_into_prompt(crit_prompt, manuscript, task.get("type"))
|
|
@@ -5182,41 +5354,26 @@ def _read_constitution(workdir):
|
|
|
5182
5354
|
"与其他要求冲突时以宪章为准)\n\n" + txt + "\n\n")
|
|
5183
5355
|
|
|
5184
5356
|
|
|
5185
|
-
def _write_project_memory(task, workdir, lines):
|
|
5186
|
-
"""
|
|
5187
|
-
.codebee/project-memory.md——同目录后续 code 任务规划前自动注入,
|
|
5188
|
-
让编排者「知道这个代码库的脾气」而非每次从零摸索。失败静默。"""
|
|
5357
|
+
def _write_project_memory(task, workdir, lines, source_run=""):
|
|
5358
|
+
"""Persist proposed project facts; they are not injected before approval."""
|
|
5189
5359
|
if not lines:
|
|
5190
5360
|
return ""
|
|
5191
5361
|
try:
|
|
5192
|
-
|
|
5193
|
-
|
|
5194
|
-
|
|
5195
|
-
|
|
5196
|
-
if header_needed:
|
|
5197
|
-
f.write("# 项目记忆(每次代码任务完成后自动追加,供后续任务参考)\n\n")
|
|
5198
|
-
f.write("### %s · %s\n" % (task.get("title") or "", _now()))
|
|
5199
|
-
for ln in lines:
|
|
5200
|
-
f.write("- %s\n" % str(ln)[:300])
|
|
5201
|
-
f.write("\n")
|
|
5202
|
-
return pm
|
|
5362
|
+
from . import project_memory
|
|
5363
|
+
row = project_memory.propose(workdir, source_run=source_run,
|
|
5364
|
+
title=task.get("title") or "", facts=lines)
|
|
5365
|
+
return str(project_memory._file(workdir)) if row else ""
|
|
5203
5366
|
except Exception:
|
|
5204
5367
|
return ""
|
|
5205
5368
|
|
|
5206
5369
|
|
|
5207
5370
|
def _read_project_memory(workdir, cap=4000):
|
|
5208
|
-
"""
|
|
5209
|
-
p = os.path.join(workdir or "", ".codebee", "project-memory.md")
|
|
5210
|
-
if not _inside(workdir, p) or not os.path.isfile(p):
|
|
5211
|
-
return ""
|
|
5371
|
+
"""Only inject approved, unexpired and fingerprint-verified memory."""
|
|
5212
5372
|
try:
|
|
5213
|
-
|
|
5214
|
-
|
|
5215
|
-
|
|
5216
|
-
if not txt:
|
|
5373
|
+
from . import project_memory
|
|
5374
|
+
return project_memory.active_text(workdir, cap=cap)
|
|
5375
|
+
except (OSError, ValueError, TypeError):
|
|
5217
5376
|
return ""
|
|
5218
|
-
return ("## 项目记忆(此前代码任务在此工作目录留下的架构事实,"
|
|
5219
|
-
"规划时优先参考)\n\n" + txt + "\n\n")
|
|
5220
5377
|
|
|
5221
5378
|
|
|
5222
5379
|
def _write_task_spec(task, workdir):
|
|
@@ -5347,10 +5504,24 @@ def _write_task_plan(task, workdir, plan):
|
|
|
5347
5504
|
if root not in target.parents:
|
|
5348
5505
|
return ""
|
|
5349
5506
|
target.parent.mkdir(parents=True, exist_ok=True)
|
|
5507
|
+
# 断点续跑(B):旧计划已勾选 [x] 的项按标题继承到新计划——重试重新
|
|
5508
|
+
# 规划不再抹掉已完成进度(继承发生在写盘前,mark_task_plan 的行号
|
|
5509
|
+
# 约定基于新文件,安全);[!]/[>] 不继承,重跑自然重置。
|
|
5510
|
+
prev_done = {}
|
|
5511
|
+
try:
|
|
5512
|
+
if target.is_file():
|
|
5513
|
+
_pat = re.compile(r"^\d+\. \[([ x!>])\] (.+)$")
|
|
5514
|
+
for _ln in target.read_text("utf-8").splitlines():
|
|
5515
|
+
_m = _pat.match(_ln)
|
|
5516
|
+
if _m and _m.group(1) == "x":
|
|
5517
|
+
prev_done.setdefault(_m.group(2).strip(), True)
|
|
5518
|
+
except Exception:
|
|
5519
|
+
prev_done = {}
|
|
5350
5520
|
lines = ["# 任务计划", "", "来源:%s" % (plan or {}).get("source", "?"), ""]
|
|
5351
5521
|
# checkbox 形态:mark_task_plan 按行号翻 [x]/[!]/[>],断点一眼可见
|
|
5352
5522
|
for i, s in enumerate((plan or {}).get("steps") or [], 1):
|
|
5353
|
-
|
|
5523
|
+
_t = str(s.get("detail") or s.get("title") or "")[:200]
|
|
5524
|
+
lines.append("%d. [%s] %s" % (i, "x" if prev_done.get(_t.strip()) else " ", _t))
|
|
5354
5525
|
target.write_text("\n".join(lines) + "\n", encoding="utf-8")
|
|
5355
5526
|
return str(target)
|
|
5356
5527
|
except Exception:
|
|
@@ -5414,6 +5585,79 @@ def mark_task_plan(workdir, index, status):
|
|
|
5414
5585
|
return False
|
|
5415
5586
|
|
|
5416
5587
|
|
|
5588
|
+
def _same_workdir(a, b):
|
|
5589
|
+
"""路径同位判断(Windows 大小写/斜杠不敏感);任一为空即不同。"""
|
|
5590
|
+
try:
|
|
5591
|
+
na = os.path.normcase(os.path.abspath(a or ""))
|
|
5592
|
+
nb = os.path.normcase(os.path.abspath(b or ""))
|
|
5593
|
+
return bool(na) and bool(nb) and na == nb
|
|
5594
|
+
except Exception:
|
|
5595
|
+
return False
|
|
5596
|
+
|
|
5597
|
+
|
|
5598
|
+
def _assemble_handoff(task, run):
|
|
5599
|
+
"""断点简报组装(B):终态 run 的「走到哪/改了什么/还剩什么」快照。
|
|
5600
|
+
存进 run 记录供 retry_task 继承;组装失败返回近空 dict,绝不挡收尾。"""
|
|
5601
|
+
steps = run.get("steps") or []
|
|
5602
|
+
done_steps = [{"role": s.get("role") or "", "summary": (s.get("summary") or "")[:120]}
|
|
5603
|
+
for s in steps if (s.get("status") or "") == "done"][-8:]
|
|
5604
|
+
pending = ""
|
|
5605
|
+
for s in steps:
|
|
5606
|
+
if (s.get("status") or "") != "done":
|
|
5607
|
+
pending = "%s%s" % (s.get("role") or "?",
|
|
5608
|
+
("(%s)" % s["summary"][:100]) if s.get("summary") else "")
|
|
5609
|
+
break
|
|
5610
|
+
try:
|
|
5611
|
+
_total, _left = plan_stopgate(task.get("workdir") or "")
|
|
5612
|
+
except Exception:
|
|
5613
|
+
_total, _left = 0, []
|
|
5614
|
+
ch = run.get("changes")
|
|
5615
|
+
if not ch:
|
|
5616
|
+
try:
|
|
5617
|
+
from . import gitmod
|
|
5618
|
+
ch = gitmod.collect_changes(task.get("workdir") or "")
|
|
5619
|
+
except Exception:
|
|
5620
|
+
ch = {}
|
|
5621
|
+
files = [f.get("path") for f in ((ch or {}).get("files") or []) if f.get("path")][:25]
|
|
5622
|
+
return {
|
|
5623
|
+
"status": run.get("status") or "",
|
|
5624
|
+
"error": (run.get("error") or "")[:240],
|
|
5625
|
+
"ended_at": run.get("ended_at") or "",
|
|
5626
|
+
"done_steps": done_steps,
|
|
5627
|
+
"pending": pending,
|
|
5628
|
+
"plan_total": _total,
|
|
5629
|
+
"plan_left": [[n, m, t] for n, m, t in (_left or [])][:6],
|
|
5630
|
+
"changed_files": files,
|
|
5631
|
+
}
|
|
5632
|
+
|
|
5633
|
+
|
|
5634
|
+
def _handoff_brief(h):
|
|
5635
|
+
"""断点简报 → 注入 __CONTEXT__ 的人话块;无实质内容返回空串(零噪音)。"""
|
|
5636
|
+
if not isinstance(h, dict) or not (h.get("done_steps") or h.get("pending")
|
|
5637
|
+
or h.get("plan_left") or h.get("changed_files")):
|
|
5638
|
+
return ""
|
|
5639
|
+
rows = ["## 断点简报(上次运行中断现场,系统注入)",
|
|
5640
|
+
"上次运行以「%s」终止%s。工作目录/任务分支保留着上次已完成的部分改动。"
|
|
5641
|
+
"先核对现场再继续:已完成的修复不要推翻重做,从中断处接着完成目标。"
|
|
5642
|
+
% (h.get("status") or "?",
|
|
5643
|
+
("(原因:%s)" % h["error"]) if h.get("error") else "")]
|
|
5644
|
+
done = h.get("done_steps") or []
|
|
5645
|
+
if done:
|
|
5646
|
+
rows.append("- 已完成步骤:" + ";".join(
|
|
5647
|
+
("%s:%s" % (s.get("role") or "?", s["summary"])) if s.get("summary")
|
|
5648
|
+
else (s.get("role") or "?") for s in done))
|
|
5649
|
+
if h.get("pending"):
|
|
5650
|
+
rows.append("- 中断位置:%s" % h["pending"])
|
|
5651
|
+
left = h.get("plan_left") or []
|
|
5652
|
+
if left:
|
|
5653
|
+
rows.append("- 活计划未完成项:" + "、".join(
|
|
5654
|
+
"#%s[%s]%s" % (n, m, t) for n, m, t in left))
|
|
5655
|
+
files = h.get("changed_files") or []
|
|
5656
|
+
if files:
|
|
5657
|
+
rows.append("- 上次已改动的文件:" + "、".join(str(f) for f in files))
|
|
5658
|
+
return "\n".join(rows)
|
|
5659
|
+
|
|
5660
|
+
|
|
5417
5661
|
def _workdir_blocker(task, run_id):
|
|
5418
5662
|
"""同工作目录互斥:返回占用该目录的任务(无则 None)。
|
|
5419
5663
|
任务计划/项目记忆/任务分支检出都是工作目录级共享状态,并行会互相覆盖
|
|
@@ -5505,6 +5749,11 @@ def execute_run(run_id):
|
|
|
5505
5749
|
difficulty=task_spec["difficulty"])
|
|
5506
5750
|
task = dict(task)
|
|
5507
5751
|
task = attachments.refresh_task(task, task.get("workdir") or "")
|
|
5752
|
+
try:
|
|
5753
|
+
task, _agent_context = agent_context.inject(task, task.get("workdir") or "")
|
|
5754
|
+
store.update_run(run_id, agent_context=_agent_context)
|
|
5755
|
+
except Exception:
|
|
5756
|
+
pass
|
|
5508
5757
|
task["_compiled_spec"] = task_spec
|
|
5509
5758
|
# 运行内统一使用编译后的难度;store 中历史任务常带 difficulty=auto,
|
|
5510
5759
|
# 不能让这个兼容值覆盖 easy/default/hard 的模型调度决策。
|
|
@@ -5708,6 +5957,17 @@ def execute_run(run_id):
|
|
|
5708
5957
|
"restore_error": repr(e)[:200]})
|
|
5709
5958
|
except Exception:
|
|
5710
5959
|
pass
|
|
5960
|
+
# 断点简报(B):失败/取消/超时的代码 run 组装「走到哪/改了什么/还剩什么」,
|
|
5961
|
+
# 供 retry_task 继承注入新 run——重试从断点继续,不从零烧 token。
|
|
5962
|
+
# done 不组装:主动重跑语义就是重来。放在 git 收尾后,changes 是最新的。
|
|
5963
|
+
try:
|
|
5964
|
+
if (task.get("engine")
|
|
5965
|
+
or ("code" if task.get("type") == "code" else "review")) == "code":
|
|
5966
|
+
_fin = store.get_run(run_id) or {}
|
|
5967
|
+
if _fin.get("status") in ("failed", "cancelled", "timeout"):
|
|
5968
|
+
store.update_run(run_id, handoff=_assemble_handoff(task, _fin))
|
|
5969
|
+
except Exception:
|
|
5970
|
+
pass
|
|
5711
5971
|
# 自学习闭环:运行结束自动把本次评审暴露的问题沉淀为可复用教训(异步,不阻塞)
|
|
5712
5972
|
try:
|
|
5713
5973
|
if store.get_run(run_id):
|