codebee 0.1.65 → 0.1.66
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 +520 -513
- package/LICENSE +21 -21
- package/README.md +494 -487
- package/app/core/aiflavor.py +197 -197
- package/app/core/artifacts.py +148 -148
- package/app/core/attachments.py +532 -532
- package/app/core/automation.py +758 -758
- package/app/core/backup.py +470 -470
- package/app/core/beekeeper.py +183 -183
- package/app/core/board.py +209 -209
- package/app/core/bookmeta.py +796 -759
- package/app/core/bookmeta_catalog.py +180 -180
- package/app/core/branching.py +137 -137
- package/app/core/builtin_agent.py +1234 -1191
- package/app/core/capability.py +144 -144
- package/app/core/catalog.py +23 -24
- package/app/core/chaptersafe.py +32 -32
- package/app/core/cleanup.py +303 -303
- package/app/core/compaction.py +201 -186
- package/app/core/covergen.py +356 -356
- package/app/core/diagnostics.py +114 -114
- package/app/core/dispatch.py +173 -173
- package/app/core/dispatch_log.py +113 -113
- package/app/core/env_scrub.py +85 -85
- package/app/core/error_codes.py +220 -65
- package/app/core/errorlog.py +137 -137
- package/app/core/evaluation.py +174 -0
- package/app/core/failure_notify.py +290 -0
- package/app/core/flows.py +355 -355
- package/app/core/goal_service.py +167 -167
- package/app/core/health.py +374 -342
- package/app/core/history.py +38 -38
- package/app/core/hooks.py +162 -156
- package/app/core/jobs.py +1034 -1022
- package/app/core/knowledge.py +6 -2
- package/app/core/manager.py +2115 -1996
- package/app/core/market.py +362 -362
- package/app/core/market_remote.py +66 -66
- package/app/core/mocks.py +64 -64
- package/app/core/modelhub.py +3517 -3332
- package/app/core/notify.py +106 -106
- package/app/core/paihang.py +109 -109
- package/app/core/paths.py +2 -2
- package/app/core/pipeline.py +4614 -4494
- package/app/core/planner.py +33 -31
- package/app/core/portguard.py +111 -111
- package/app/core/preview.py +293 -293
- package/app/core/publish/__init__.py +7 -7
- package/app/core/publish/auto.py +367 -367
- package/app/core/publish/browser.py +533 -533
- package/app/core/publish/fanqie.py +128 -128
- package/app/core/publish/flow.py +432 -406
- package/app/core/publish/flows-fanqie-calibrated.json +17 -9
- package/app/core/publish/flows-qimao-calibrated.json +6 -2
- package/app/core/publish/ledger.py +198 -198
- package/app/core/publish/manager.py +623 -590
- package/app/core/publish/qimao.py +141 -141
- package/app/core/publish/ws.py +139 -139
- package/app/core/redact.py +38 -38
- package/app/core/repeat_guard.py +129 -124
- package/app/core/router.py +119 -81
- package/app/core/runner.py +2050 -2042
- 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 +149 -149
- package/app/core/settings_schema.py +262 -262
- package/app/core/share_page.py +114 -114
- package/app/core/skill_scan.py +114 -106
- package/app/core/skillpacks/fanqie-novel.md +80 -80
- 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/skills.py +221 -72
- package/app/core/step_runner.py +132 -132
- package/app/core/store.py +23 -0
- 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/upstream.py +41 -0
- package/app/core/usage.py +114 -3
- package/app/core/volumes.py +355 -355
- package/app/core/wxdigest.py +858 -858
- package/app/core/zentao.py +2447 -2447
- package/app/main.py +2838 -2832
- package/app/pick_dialog.py +110 -110
- package/app/ui/app.js +13994 -13977
- package/app/ui/board.css +20 -41
- package/app/ui/board.html +162 -162
- package/app/ui/board.js +305 -305
- package/app/ui/i18n.js +2716 -2711
- package/app/ui/icons/bee.svg +79 -79
- package/app/ui/index.html +1 -1
- package/app/ui/manifest.json +16 -16
- package/app/ui/qrcode.js +2297 -2297
- package/app/ui/style.css +6107 -6125
- package/bin/tutti.js +147 -147
- package/package.json +46 -41
- 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/core/store.py
CHANGED
|
@@ -554,6 +554,7 @@ def load_all():
|
|
|
554
554
|
# ---------------------------------------------------------------- 运行(含管理操作)
|
|
555
555
|
|
|
556
556
|
_RUN_ESTIMATOR = None
|
|
557
|
+
_RUN_AUDITOR = None
|
|
557
558
|
|
|
558
559
|
|
|
559
560
|
def set_run_estimator(estimator):
|
|
@@ -562,6 +563,16 @@ def set_run_estimator(estimator):
|
|
|
562
563
|
_RUN_ESTIMATOR = estimator if callable(estimator) else None
|
|
563
564
|
|
|
564
565
|
|
|
566
|
+
def set_run_auditor(auditor):
|
|
567
|
+
"""注入 run 终态对账器(usage.audit_run),同样不让存储层依赖用量模块。"""
|
|
568
|
+
global _RUN_AUDITOR
|
|
569
|
+
_RUN_AUDITOR = auditor if callable(auditor) else None
|
|
570
|
+
|
|
571
|
+
|
|
572
|
+
# run 终态集合:与 CAS 收口、状态回填共用同一份定义
|
|
573
|
+
TERMINAL_STATUSES = ("done", "failed", "cancelled", "timeout")
|
|
574
|
+
|
|
575
|
+
|
|
565
576
|
def create_run(kind, title, task_id=None, entry_id=None, op=None):
|
|
566
577
|
run = {
|
|
567
578
|
"id": _new_id("r" if kind == "orchestration" else "m"),
|
|
@@ -776,12 +787,14 @@ def update_run(run_id, expected_status=None, **fields):
|
|
|
776
787
|
当前状态不等于 expected_status 则拒绝写入并返回 None,
|
|
777
788
|
防止陈旧执行方(被取消的 worker、崩溃恢复前的旧线程)覆盖新状态
|
|
778
789
|
——防御模式「异步状态不是同步状态」。不传则保持原行为。"""
|
|
790
|
+
audit = None
|
|
779
791
|
with LOCK:
|
|
780
792
|
run = _RUNS.get(run_id)
|
|
781
793
|
if not run:
|
|
782
794
|
return None
|
|
783
795
|
if expected_status is not None and run.get("status") != expected_status:
|
|
784
796
|
return None
|
|
797
|
+
prev_status = str(run.get("status") or "")
|
|
785
798
|
for k in ("error", "summary"):
|
|
786
799
|
if isinstance(fields.get(k), str):
|
|
787
800
|
# 错误串多是我们自己拼的 CLI 尾巴(含 ANSI/覆写/乱码墙):
|
|
@@ -813,6 +826,11 @@ def update_run(run_id, expected_status=None, **fields):
|
|
|
813
826
|
if task:
|
|
814
827
|
task["status"] = st
|
|
815
828
|
_save_json(paths.TASKS_DIR / (tid + ".json"), task)
|
|
829
|
+
if st in TERMINAL_STATUSES and prev_status not in TERMINAL_STATUSES:
|
|
830
|
+
# 事后对账:只有「非终态 → 终态」这一次翻转才记账,重复写终态
|
|
831
|
+
# (UI 补写、迟到写手)不得二次入账。快照在锁内浅拷,锁外调用
|
|
832
|
+
# 审计器——审计器会回读 store,锁内调用即自锁。
|
|
833
|
+
audit = dict(run)
|
|
816
834
|
if st in ("done", "failed", "cancelled", "timeout"):
|
|
817
835
|
# run_end 钩子(2026-09-22):终态即触发,后台跑副作用型脚本
|
|
818
836
|
# (回写知识库等);失败/超时静默,绝不拖慢收尾路径
|
|
@@ -831,6 +849,11 @@ def update_run(run_id, expected_status=None, **fields):
|
|
|
831
849
|
# 「在跑」直到下一次无关 bump(2026-09-22 侧栏假在跑案)。
|
|
832
850
|
# 调用都是步骤级边界,不会形成推送风暴;CAS 拒绝/无此 run 的早退
|
|
833
851
|
# 路径不动版本号。
|
|
852
|
+
if audit is not None and _RUN_AUDITOR is not None:
|
|
853
|
+
try:
|
|
854
|
+
_RUN_AUDITOR(audit)
|
|
855
|
+
except Exception:
|
|
856
|
+
pass # 对账永远不能拖垮终态收尾
|
|
834
857
|
bump_state()
|
|
835
858
|
return run
|
|
836
859
|
|
package/app/core/task_compile.py
CHANGED
|
@@ -1,199 +1,199 @@
|
|
|
1
|
-
# -*- coding: utf-8 -*-
|
|
2
|
-
"""任务编译器:把用户任务和预置流程编译成统一、可审计的运行规格。"""
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
from collections.abc import Mapping
|
|
6
|
-
|
|
7
|
-
from . import dispatch, flows
|
|
8
|
-
|
|
9
|
-
SCHEMA_VERSION = 1
|
|
10
|
-
DIFFICULTIES = ("easy", "default", "hard")
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
def _difficulty(task, flow):
|
|
14
|
-
explicit = str((task or {}).get("difficulty") or "").strip().lower()
|
|
15
|
-
if explicit in DIFFICULTIES:
|
|
16
|
-
return explicit, "用户指定"
|
|
17
|
-
try:
|
|
18
|
-
threshold = float((task or {}).get("threshold")
|
|
19
|
-
if (task or {}).get("threshold") is not None
|
|
20
|
-
else (flow or {}).get("threshold") or 7.0)
|
|
21
|
-
except (TypeError, ValueError):
|
|
22
|
-
threshold = 7.0
|
|
23
|
-
if threshold >= 8.5:
|
|
24
|
-
return "hard", "高质量门槛"
|
|
25
|
-
if threshold <= 6.0:
|
|
26
|
-
return "easy", "快速交付门槛"
|
|
27
|
-
if str((task or {}).get("type") or "").lower() in (
|
|
28
|
-
"research", "tech_proposal", "rank_scan"):
|
|
29
|
-
return "hard", "研究/方案类默认需要深度"
|
|
30
|
-
return "default", "标准难度"
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
def _capabilities(task, dimension, engine):
|
|
34
|
-
caps = [dimension]
|
|
35
|
-
if engine == "code" or (task or {}).get("verify_command"):
|
|
36
|
-
caps.extend(["filesystem", "verification"])
|
|
37
|
-
if (task or {}).get("serial"):
|
|
38
|
-
caps.extend(["long_context", "continuity"])
|
|
39
|
-
if (task or {}).get("attachments"):
|
|
40
|
-
caps.append("attachments")
|
|
41
|
-
return list(dict.fromkeys(caps))
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
def compile_task(task):
|
|
45
|
-
"""返回统一任务规格;输入缺失或字段异常时保持可编排的安全兜底。"""
|
|
46
|
-
raw = dict(task) if isinstance(task, Mapping) else {}
|
|
47
|
-
ttype = str(raw.get("type") or "direct").strip().lower()
|
|
48
|
-
flow = flows.get_flow(ttype) or {}
|
|
49
|
-
engine = str(raw.get("engine") or flow.get("engine") or
|
|
50
|
-
("code" if ttype == "code" else "review")).strip()
|
|
51
|
-
if engine not in flows.ENGINES:
|
|
52
|
-
engine = flow.get("engine") or ("code" if ttype == "code" else "review")
|
|
53
|
-
difficulty, difficulty_reason = _difficulty(raw, flow)
|
|
54
|
-
dimension = dispatch.task_dimension(ttype, raw.get("role") or "")
|
|
55
|
-
rubric = raw.get("rubric") or flow.get("rubric") or []
|
|
56
|
-
if isinstance(rubric, str):
|
|
57
|
-
rubric = [x.strip() for x in rubric.replace(",", ",").split(",") if x.strip()]
|
|
58
|
-
elif not isinstance(rubric, (list, tuple)):
|
|
59
|
-
rubric = flow.get("rubric") or []
|
|
60
|
-
rubric = [str(x).strip() for x in rubric if str(x).strip()][:8]
|
|
61
|
-
deliverable = str(raw.get("manuscript") or flow.get("manuscript") or "").strip()
|
|
62
|
-
serial = raw.get("serial") if isinstance(raw.get("serial"), dict) else None
|
|
63
|
-
return {
|
|
64
|
-
"schema_version": SCHEMA_VERSION,
|
|
65
|
-
"type": ttype,
|
|
66
|
-
"title": str(raw.get("title") or "")[:120],
|
|
67
|
-
"goal": str(raw.get("goal") or "")[:2000],
|
|
68
|
-
"engine": engine,
|
|
69
|
-
"dimension": dimension,
|
|
70
|
-
"difficulty": difficulty,
|
|
71
|
-
"difficulty_reason": difficulty_reason,
|
|
72
|
-
"thinking": (str(raw.get("thinking") or "standard").lower()
|
|
73
|
-
if str(raw.get("thinking") or "standard").lower()
|
|
74
|
-
in ("auto", "low", "standard", "high") else "auto"),
|
|
75
|
-
"capabilities": _capabilities(raw, dimension, engine),
|
|
76
|
-
"deliverable": deliverable,
|
|
77
|
-
"quality_dimensions": rubric,
|
|
78
|
-
"serial": serial,
|
|
79
|
-
"explicit_agent": str(raw.get("implementer") or "").strip(),
|
|
80
|
-
"has_verification": bool(raw.get("verify_command")),
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
def summary(spec):
|
|
85
|
-
"""给运行日志/UI 的短说明,不包含用户正文。"""
|
|
86
|
-
spec = spec or {}
|
|
87
|
-
return "%s/%s/%s(能力:%s)" % (
|
|
88
|
-
spec.get("type") or "direct", spec.get("engine") or "direct",
|
|
89
|
-
spec.get("difficulty") or "default",
|
|
90
|
-
"、".join(spec.get("capabilities") or []) or "通用")
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
def code_workflow(task, difficulty, plan=None, mode="auto", planned=True):
|
|
94
|
-
"""把难度、验收能力和规划建议收敛成代码任务的实际步骤策略。
|
|
95
|
-
|
|
96
|
-
自动模式允许低风险任务走短链;手动模式与高风险任务保留完整质量门禁。
|
|
97
|
-
规划器只能在安全边界内减少步骤,不能让无确定性验证的任务跳过评审。
|
|
98
|
-
"""
|
|
99
|
-
task = task if isinstance(task, Mapping) else {}
|
|
100
|
-
rec = (plan or {}).get("workflow") if isinstance(plan, Mapping) else None
|
|
101
|
-
rec = rec if isinstance(rec, Mapping) else {}
|
|
102
|
-
has_verify = bool(str(task.get("verify_command") or "").strip())
|
|
103
|
-
risk_text = " ".join(str(task.get(k) or "") for k in ("title", "goal", "context")).lower()
|
|
104
|
-
high_risk = bool(any(k in risk_text for k in (
|
|
105
|
-
"安全", "权限", "鉴权", "认证", "加密", "密钥", "注入", "迁移", "数据库",
|
|
106
|
-
"并发", "竞态", "支付", "架构", "公共接口", "public api", "公共 api",
|
|
107
|
-
"breaking change", "security", "permission", "auth", "migration",
|
|
108
|
-
"concurrency", "architecture")))
|
|
109
|
-
fast_mode = mode == "fast"
|
|
110
|
-
expert_mode = mode == "expert"
|
|
111
|
-
easy_auto = (mode == "auto" and difficulty == "easy") or fast_mode
|
|
112
|
-
|
|
113
|
-
review_required = True
|
|
114
|
-
max_repairs = 2
|
|
115
|
-
allow_switch = True
|
|
116
|
-
reason = "复杂或手动任务使用完整质量门禁"
|
|
117
|
-
if easy_auto:
|
|
118
|
-
review_required = high_risk or not has_verify
|
|
119
|
-
max_repairs = 1
|
|
120
|
-
allow_switch = False
|
|
121
|
-
reason = ("低风险任务以确定性验证代替模型评审"
|
|
122
|
-
if has_verify else "低风险任务无验证命令,保留一次模型评审")
|
|
123
|
-
if isinstance(rec.get("review_required"), bool):
|
|
124
|
-
review_required = high_risk or rec["review_required"] or not has_verify
|
|
125
|
-
try:
|
|
126
|
-
if "max_repair_rounds" in rec:
|
|
127
|
-
max_repairs = max(1 if not has_verify else 0,
|
|
128
|
-
min(1, int(rec["max_repair_rounds"])))
|
|
129
|
-
except (TypeError, ValueError):
|
|
130
|
-
pass
|
|
131
|
-
elif expert_mode or (mode == "auto" and difficulty == "hard"):
|
|
132
|
-
review_required = True
|
|
133
|
-
max_repairs = 2
|
|
134
|
-
allow_switch = True
|
|
135
|
-
try:
|
|
136
|
-
if "max_repair_rounds" in rec:
|
|
137
|
-
max_repairs = max(1, min(2, int(rec["max_repair_rounds"])))
|
|
138
|
-
except (TypeError, ValueError):
|
|
139
|
-
pass
|
|
140
|
-
if isinstance(rec.get("allow_switch"), bool):
|
|
141
|
-
allow_switch = rec["allow_switch"]
|
|
142
|
-
reason = "复杂任务保留规划、验证与模型评审"
|
|
143
|
-
|
|
144
|
-
return {
|
|
145
|
-
"planning": "llm" if planned else "inline",
|
|
146
|
-
"implementation_steps": len((plan or {}).get("steps") or []) or 1,
|
|
147
|
-
"verification": has_verify,
|
|
148
|
-
"review_required": review_required,
|
|
149
|
-
"reviewers": 1 if review_required else 0,
|
|
150
|
-
"max_repair_rounds": max_repairs,
|
|
151
|
-
"allow_switch": allow_switch,
|
|
152
|
-
"reason": reason,
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
def content_workflow(task, difficulty, plan=None, mode="auto"):
|
|
157
|
-
"""为非连载内容任务生成动态大纲/评审/修订策略。"""
|
|
158
|
-
task = task if isinstance(task, Mapping) else {}
|
|
159
|
-
ttype = str(task.get("type") or "doc")
|
|
160
|
-
requested_rounds = max(1, min(5, int(task.get("rounds") or 2)))
|
|
161
|
-
rec = (plan or {}).get("workflow") if isinstance(plan, Mapping) else None
|
|
162
|
-
rec = rec if isinstance(rec, Mapping) else {}
|
|
163
|
-
light_types = {"email", "weekly_report", "translation"}
|
|
164
|
-
deep_types = {"novel", "research", "tech_proposal"}
|
|
165
|
-
|
|
166
|
-
if mode == "fast":
|
|
167
|
-
return {"outline": False, "reviewers": 1, "review_rounds": 1,
|
|
168
|
-
"reason": "快速模式省略独立大纲,仅保留一次快速评审"}
|
|
169
|
-
if mode == "expert":
|
|
170
|
-
return {"outline": True, "reviewers": 2,
|
|
171
|
-
"review_rounds": min(requested_rounds, 3),
|
|
172
|
-
"reason": "专家模式启用完整大纲、多评审与修订门禁"}
|
|
173
|
-
if mode != "auto":
|
|
174
|
-
return {"outline": True, "reviewers": 0,
|
|
175
|
-
"review_rounds": requested_rounds,
|
|
176
|
-
"reason": "手动模式尊重用户指定的评审组与轮数"}
|
|
177
|
-
|
|
178
|
-
if ttype in light_types and difficulty != "hard":
|
|
179
|
-
return {"outline": False, "reviewers": 1, "review_rounds": 1,
|
|
180
|
-
"reason": "轻量内容省略独立大纲,只做一次快速评审"}
|
|
181
|
-
|
|
182
|
-
outline = ttype in deep_types or difficulty == "hard"
|
|
183
|
-
reviewers = 2 if outline else 1
|
|
184
|
-
review_rounds = min(requested_rounds, 2 if outline else 1)
|
|
185
|
-
try:
|
|
186
|
-
if "reviewers" in rec:
|
|
187
|
-
reviewers = max(1, min(2, int(rec["reviewers"])))
|
|
188
|
-
if "review_rounds" in rec:
|
|
189
|
-
review_rounds = max(1, min(requested_rounds, 3,
|
|
190
|
-
int(rec["review_rounds"])))
|
|
191
|
-
except (TypeError, ValueError):
|
|
192
|
-
pass
|
|
193
|
-
# 深度类型至少保留一轮评审,但允许规划器把双评审降成一名;高门槛强制双评审。
|
|
194
|
-
if float(task.get("threshold") or 7.0) >= 8.5:
|
|
195
|
-
reviewers = 2
|
|
196
|
-
return {"outline": outline, "reviewers": reviewers,
|
|
197
|
-
"review_rounds": review_rounds,
|
|
198
|
-
"reason": ("深度内容保留大纲和多轮质量门禁" if outline
|
|
199
|
-
else "标准内容使用单评审短链")}
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""任务编译器:把用户任务和预置流程编译成统一、可审计的运行规格。"""
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Mapping
|
|
6
|
+
|
|
7
|
+
from . import dispatch, flows
|
|
8
|
+
|
|
9
|
+
SCHEMA_VERSION = 1
|
|
10
|
+
DIFFICULTIES = ("easy", "default", "hard")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _difficulty(task, flow):
|
|
14
|
+
explicit = str((task or {}).get("difficulty") or "").strip().lower()
|
|
15
|
+
if explicit in DIFFICULTIES:
|
|
16
|
+
return explicit, "用户指定"
|
|
17
|
+
try:
|
|
18
|
+
threshold = float((task or {}).get("threshold")
|
|
19
|
+
if (task or {}).get("threshold") is not None
|
|
20
|
+
else (flow or {}).get("threshold") or 7.0)
|
|
21
|
+
except (TypeError, ValueError):
|
|
22
|
+
threshold = 7.0
|
|
23
|
+
if threshold >= 8.5:
|
|
24
|
+
return "hard", "高质量门槛"
|
|
25
|
+
if threshold <= 6.0:
|
|
26
|
+
return "easy", "快速交付门槛"
|
|
27
|
+
if str((task or {}).get("type") or "").lower() in (
|
|
28
|
+
"research", "tech_proposal", "rank_scan"):
|
|
29
|
+
return "hard", "研究/方案类默认需要深度"
|
|
30
|
+
return "default", "标准难度"
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _capabilities(task, dimension, engine):
|
|
34
|
+
caps = [dimension]
|
|
35
|
+
if engine == "code" or (task or {}).get("verify_command"):
|
|
36
|
+
caps.extend(["filesystem", "verification"])
|
|
37
|
+
if (task or {}).get("serial"):
|
|
38
|
+
caps.extend(["long_context", "continuity"])
|
|
39
|
+
if (task or {}).get("attachments"):
|
|
40
|
+
caps.append("attachments")
|
|
41
|
+
return list(dict.fromkeys(caps))
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def compile_task(task):
|
|
45
|
+
"""返回统一任务规格;输入缺失或字段异常时保持可编排的安全兜底。"""
|
|
46
|
+
raw = dict(task) if isinstance(task, Mapping) else {}
|
|
47
|
+
ttype = str(raw.get("type") or "direct").strip().lower()
|
|
48
|
+
flow = flows.get_flow(ttype) or {}
|
|
49
|
+
engine = str(raw.get("engine") or flow.get("engine") or
|
|
50
|
+
("code" if ttype == "code" else "review")).strip()
|
|
51
|
+
if engine not in flows.ENGINES:
|
|
52
|
+
engine = flow.get("engine") or ("code" if ttype == "code" else "review")
|
|
53
|
+
difficulty, difficulty_reason = _difficulty(raw, flow)
|
|
54
|
+
dimension = dispatch.task_dimension(ttype, raw.get("role") or "")
|
|
55
|
+
rubric = raw.get("rubric") or flow.get("rubric") or []
|
|
56
|
+
if isinstance(rubric, str):
|
|
57
|
+
rubric = [x.strip() for x in rubric.replace(",", ",").split(",") if x.strip()]
|
|
58
|
+
elif not isinstance(rubric, (list, tuple)):
|
|
59
|
+
rubric = flow.get("rubric") or []
|
|
60
|
+
rubric = [str(x).strip() for x in rubric if str(x).strip()][:8]
|
|
61
|
+
deliverable = str(raw.get("manuscript") or flow.get("manuscript") or "").strip()
|
|
62
|
+
serial = raw.get("serial") if isinstance(raw.get("serial"), dict) else None
|
|
63
|
+
return {
|
|
64
|
+
"schema_version": SCHEMA_VERSION,
|
|
65
|
+
"type": ttype,
|
|
66
|
+
"title": str(raw.get("title") or "")[:120],
|
|
67
|
+
"goal": str(raw.get("goal") or "")[:2000],
|
|
68
|
+
"engine": engine,
|
|
69
|
+
"dimension": dimension,
|
|
70
|
+
"difficulty": difficulty,
|
|
71
|
+
"difficulty_reason": difficulty_reason,
|
|
72
|
+
"thinking": (str(raw.get("thinking") or "standard").lower()
|
|
73
|
+
if str(raw.get("thinking") or "standard").lower()
|
|
74
|
+
in ("auto", "low", "standard", "high") else "auto"),
|
|
75
|
+
"capabilities": _capabilities(raw, dimension, engine),
|
|
76
|
+
"deliverable": deliverable,
|
|
77
|
+
"quality_dimensions": rubric,
|
|
78
|
+
"serial": serial,
|
|
79
|
+
"explicit_agent": str(raw.get("implementer") or "").strip(),
|
|
80
|
+
"has_verification": bool(raw.get("verify_command")),
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def summary(spec):
|
|
85
|
+
"""给运行日志/UI 的短说明,不包含用户正文。"""
|
|
86
|
+
spec = spec or {}
|
|
87
|
+
return "%s/%s/%s(能力:%s)" % (
|
|
88
|
+
spec.get("type") or "direct", spec.get("engine") or "direct",
|
|
89
|
+
spec.get("difficulty") or "default",
|
|
90
|
+
"、".join(spec.get("capabilities") or []) or "通用")
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def code_workflow(task, difficulty, plan=None, mode="auto", planned=True):
|
|
94
|
+
"""把难度、验收能力和规划建议收敛成代码任务的实际步骤策略。
|
|
95
|
+
|
|
96
|
+
自动模式允许低风险任务走短链;手动模式与高风险任务保留完整质量门禁。
|
|
97
|
+
规划器只能在安全边界内减少步骤,不能让无确定性验证的任务跳过评审。
|
|
98
|
+
"""
|
|
99
|
+
task = task if isinstance(task, Mapping) else {}
|
|
100
|
+
rec = (plan or {}).get("workflow") if isinstance(plan, Mapping) else None
|
|
101
|
+
rec = rec if isinstance(rec, Mapping) else {}
|
|
102
|
+
has_verify = bool(str(task.get("verify_command") or "").strip())
|
|
103
|
+
risk_text = " ".join(str(task.get(k) or "") for k in ("title", "goal", "context")).lower()
|
|
104
|
+
high_risk = bool(any(k in risk_text for k in (
|
|
105
|
+
"安全", "权限", "鉴权", "认证", "加密", "密钥", "注入", "迁移", "数据库",
|
|
106
|
+
"并发", "竞态", "支付", "架构", "公共接口", "public api", "公共 api",
|
|
107
|
+
"breaking change", "security", "permission", "auth", "migration",
|
|
108
|
+
"concurrency", "architecture")))
|
|
109
|
+
fast_mode = mode == "fast"
|
|
110
|
+
expert_mode = mode == "expert"
|
|
111
|
+
easy_auto = (mode == "auto" and difficulty == "easy") or fast_mode
|
|
112
|
+
|
|
113
|
+
review_required = True
|
|
114
|
+
max_repairs = 2
|
|
115
|
+
allow_switch = True
|
|
116
|
+
reason = "复杂或手动任务使用完整质量门禁"
|
|
117
|
+
if easy_auto:
|
|
118
|
+
review_required = high_risk or not has_verify
|
|
119
|
+
max_repairs = 1
|
|
120
|
+
allow_switch = False
|
|
121
|
+
reason = ("低风险任务以确定性验证代替模型评审"
|
|
122
|
+
if has_verify else "低风险任务无验证命令,保留一次模型评审")
|
|
123
|
+
if isinstance(rec.get("review_required"), bool):
|
|
124
|
+
review_required = high_risk or rec["review_required"] or not has_verify
|
|
125
|
+
try:
|
|
126
|
+
if "max_repair_rounds" in rec:
|
|
127
|
+
max_repairs = max(1 if not has_verify else 0,
|
|
128
|
+
min(1, int(rec["max_repair_rounds"])))
|
|
129
|
+
except (TypeError, ValueError):
|
|
130
|
+
pass
|
|
131
|
+
elif expert_mode or (mode == "auto" and difficulty == "hard"):
|
|
132
|
+
review_required = True
|
|
133
|
+
max_repairs = 2
|
|
134
|
+
allow_switch = True
|
|
135
|
+
try:
|
|
136
|
+
if "max_repair_rounds" in rec:
|
|
137
|
+
max_repairs = max(1, min(2, int(rec["max_repair_rounds"])))
|
|
138
|
+
except (TypeError, ValueError):
|
|
139
|
+
pass
|
|
140
|
+
if isinstance(rec.get("allow_switch"), bool):
|
|
141
|
+
allow_switch = rec["allow_switch"]
|
|
142
|
+
reason = "复杂任务保留规划、验证与模型评审"
|
|
143
|
+
|
|
144
|
+
return {
|
|
145
|
+
"planning": "llm" if planned else "inline",
|
|
146
|
+
"implementation_steps": len((plan or {}).get("steps") or []) or 1,
|
|
147
|
+
"verification": has_verify,
|
|
148
|
+
"review_required": review_required,
|
|
149
|
+
"reviewers": 1 if review_required else 0,
|
|
150
|
+
"max_repair_rounds": max_repairs,
|
|
151
|
+
"allow_switch": allow_switch,
|
|
152
|
+
"reason": reason,
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def content_workflow(task, difficulty, plan=None, mode="auto"):
|
|
157
|
+
"""为非连载内容任务生成动态大纲/评审/修订策略。"""
|
|
158
|
+
task = task if isinstance(task, Mapping) else {}
|
|
159
|
+
ttype = str(task.get("type") or "doc")
|
|
160
|
+
requested_rounds = max(1, min(5, int(task.get("rounds") or 2)))
|
|
161
|
+
rec = (plan or {}).get("workflow") if isinstance(plan, Mapping) else None
|
|
162
|
+
rec = rec if isinstance(rec, Mapping) else {}
|
|
163
|
+
light_types = {"email", "weekly_report", "translation"}
|
|
164
|
+
deep_types = {"novel", "research", "tech_proposal"}
|
|
165
|
+
|
|
166
|
+
if mode == "fast":
|
|
167
|
+
return {"outline": False, "reviewers": 1, "review_rounds": 1,
|
|
168
|
+
"reason": "快速模式省略独立大纲,仅保留一次快速评审"}
|
|
169
|
+
if mode == "expert":
|
|
170
|
+
return {"outline": True, "reviewers": 2,
|
|
171
|
+
"review_rounds": min(requested_rounds, 3),
|
|
172
|
+
"reason": "专家模式启用完整大纲、多评审与修订门禁"}
|
|
173
|
+
if mode != "auto":
|
|
174
|
+
return {"outline": True, "reviewers": 0,
|
|
175
|
+
"review_rounds": requested_rounds,
|
|
176
|
+
"reason": "手动模式尊重用户指定的评审组与轮数"}
|
|
177
|
+
|
|
178
|
+
if ttype in light_types and difficulty != "hard":
|
|
179
|
+
return {"outline": False, "reviewers": 1, "review_rounds": 1,
|
|
180
|
+
"reason": "轻量内容省略独立大纲,只做一次快速评审"}
|
|
181
|
+
|
|
182
|
+
outline = ttype in deep_types or difficulty == "hard"
|
|
183
|
+
reviewers = 2 if outline else 1
|
|
184
|
+
review_rounds = min(requested_rounds, 2 if outline else 1)
|
|
185
|
+
try:
|
|
186
|
+
if "reviewers" in rec:
|
|
187
|
+
reviewers = max(1, min(2, int(rec["reviewers"])))
|
|
188
|
+
if "review_rounds" in rec:
|
|
189
|
+
review_rounds = max(1, min(requested_rounds, 3,
|
|
190
|
+
int(rec["review_rounds"])))
|
|
191
|
+
except (TypeError, ValueError):
|
|
192
|
+
pass
|
|
193
|
+
# 深度类型至少保留一轮评审,但允许规划器把双评审降成一名;高门槛强制双评审。
|
|
194
|
+
if float(task.get("threshold") or 7.0) >= 8.5:
|
|
195
|
+
reviewers = 2
|
|
196
|
+
return {"outline": outline, "reviewers": reviewers,
|
|
197
|
+
"review_rounds": review_rounds,
|
|
198
|
+
"reason": ("深度内容保留大纲和多轮质量门禁" if outline
|
|
199
|
+
else "标准内容使用单评审短链")}
|