codebee 0.1.66 → 0.1.68
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 +543 -520
- package/LICENSE +21 -21
- package/README.md +11 -7
- package/app/.playwright-cli/console-2026-09-23T08-26-03-635Z.log +88 -0
- package/app/.playwright-cli/page-2026-09-23T08-26-03-845Z.yml +126 -0
- 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 +210 -0
- 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/acceptance.py +100 -0
- package/app/core/aiflavor.py +197 -197
- package/app/core/artifacts.py +148 -148
- package/app/core/assets.py +110 -0
- package/app/core/attachments.py +623 -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 +930 -796
- package/app/core/bookmeta_catalog.py +180 -180
- package/app/core/branching.py +137 -137
- package/app/core/builtin_agent.py +1234 -1234
- package/app/core/capability.py +144 -144
- package/app/core/catalog.py +42 -0
- package/app/core/chaptersafe.py +32 -32
- package/app/core/cleanup.py +303 -303
- package/app/core/compaction.py +201 -201
- package/app/core/covergen.py +356 -356
- package/app/core/defectretro.py +43 -0
- package/app/core/diagnostics.py +114 -114
- package/app/core/dispatch.py +173 -173
- package/app/core/dispatch_log.py +128 -113
- package/app/core/env_scrub.py +85 -85
- package/app/core/error_codes.py +23 -23
- package/app/core/errorlog.py +137 -137
- package/app/core/evaluation.py +174 -174
- package/app/core/failure_notify.py +290 -290
- package/app/core/flows.py +48 -2
- package/app/core/goal_service.py +167 -167
- package/app/core/health.py +374 -374
- package/app/core/history.py +38 -38
- package/app/core/hooks.py +162 -162
- package/app/core/jobs.py +46 -26
- package/app/core/knowledge.py +57 -3
- package/app/core/manager.py +2122 -2115
- package/app/core/market.py +362 -362
- package/app/core/market_remote.py +124 -26
- package/app/core/mocks.py +64 -64
- package/app/core/modelhub.py +51 -3
- package/app/core/notify.py +106 -106
- package/app/core/operations.py +228 -0
- package/app/core/paihang.py +138 -109
- package/app/core/paths.py +2 -2
- package/app/core/pipeline.py +221 -12
- package/app/core/planner.py +6 -1
- 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 +539 -533
- package/app/core/publish/fanqie.py +159 -128
- package/app/core/publish/flow.py +440 -432
- package/app/core/publish/flows-fanqie-calibrated.json +332 -332
- package/app/core/publish/flows-qimao-calibrated.json +7 -3
- package/app/core/publish/ledger.py +203 -198
- package/app/core/publish/manager.py +692 -623
- 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 -129
- package/app/core/revisions.py +51 -0
- package/app/core/router.py +398 -398
- package/app/core/runner.py +2067 -2050
- 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 +122 -114
- 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 +109 -2
- package/app/core/step_runner.py +132 -132
- package/app/core/store.py +278 -41
- 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/tracing.py +144 -0
- package/app/core/upstream.py +41 -41
- package/app/core/usage.py +862 -858
- package/app/core/volumes.py +389 -355
- package/app/core/wxdigest.py +858 -858
- package/app/core/zentao.py +2465 -2447
- package/app/main.py +2981 -2838
- package/app/pick_dialog.py +110 -110
- package/app/ui/app.js +14399 -13994
- package/app/ui/artdiff.js +74 -0
- package/app/ui/board.html +162 -162
- package/app/ui/board.js +305 -305
- package/app/ui/i18n.js +2770 -2716
- package/app/ui/icons/bee.svg +79 -79
- package/app/ui/index.html +6 -0
- package/app/ui/manifest.json +16 -16
- package/app/ui/qrcode.js +2297 -2297
- package/app/ui/style.css +96 -24
- package/bin/tutti.js +147 -147
- package/package.json +46 -46
- package/tools/wx_reader.py +261 -261
package/app/core/paihang.py
CHANGED
|
@@ -1,109 +1,138 @@
|
|
|
1
|
-
# -*- coding: utf-8 -*-
|
|
2
|
-
"""扫榜选材:抓取公开排行榜页,产出选题分析素材。
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
- 七猫:www.qimao.com/paihang
|
|
6
|
-
- 番茄 Web 版:fanqienovel.com/rank
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
import
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""扫榜选材:抓取公开排行榜页,产出选题分析素材。
|
|
3
|
+
|
|
4
|
+
数据源(四源聚合,任一失败不影响其余源;2026-09-24 实测全 200):
|
|
5
|
+
- 七猫:www.qimao.com/paihang/(106KB)
|
|
6
|
+
- 番茄 Web 版:fanqienovel.com/rank(题材分类+书名在中文 a 标签里;
|
|
7
|
+
App 端接口有 SecuritySign/X-Argus 签名——那才是难路,Web 版榜单页
|
|
8
|
+
不需要签名,别走弯路)
|
|
9
|
+
- 起点移动版:m.qidian.com/rank(106KB,直接出书名;主站 www.qidian.com
|
|
10
|
+
被 WAF 拦 202/209B,移动页不需要签名)
|
|
11
|
+
- 纵横:www.zongheng.com/rank(65KB,书名+统计标签混排,靠噪音词过滤)
|
|
12
|
+
|
|
13
|
+
下载走 curl 子进程(与 covergen 同款),解析用宽松正则——页面结构变了
|
|
14
|
+
宁可返回空(调用方回落普通直连提示词),不做脆弱的强解析。
|
|
15
|
+
"""
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import logging
|
|
19
|
+
import re
|
|
20
|
+
|
|
21
|
+
log = logging.getLogger(__name__)
|
|
22
|
+
|
|
23
|
+
QIMAO_RANK_URL = "https://www.qimao.com/paihang/"
|
|
24
|
+
FANQIE_RANK_URL = "https://fanqienovel.com/rank"
|
|
25
|
+
QIDIAN_M_RANK_URL = "https://m.qidian.com/rank/"
|
|
26
|
+
ZONGHENG_RANK_URL = "https://www.zongheng.com/rank/"
|
|
27
|
+
_UA = ("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
|
|
28
|
+
"(KHTML, like Gecko) Chrome/126 Safari/537.36")
|
|
29
|
+
|
|
30
|
+
# 页面 UI 噪音词(非书目/分类):页面 chrome、导航、tab 名、登录/搜索、
|
|
31
|
+
# 榜单 tab 与统计标签(起点移动版 tab 名、纵横月票/人气/点击等)
|
|
32
|
+
_NOISE = {"加入书架", "立即阅读", "开始阅读", "免费阅读", "全部", "分类", "排行",
|
|
33
|
+
"排行榜", "完本", "连载中", "阅读", "书城", "男生", "女生",
|
|
34
|
+
"帮助中心", "作家助手", "客服", "登录", "注册", "立即登录",
|
|
35
|
+
"搜索", "热门作品排行", "大热榜", "新书榜", "完结榜", "收藏榜",
|
|
36
|
+
"更新榜", "日榜", "月榜", "蝉联", "榜首", "热度", "免费", "畅销",
|
|
37
|
+
"下载", "客户端", "首页", "我的", "设置", "更多", "返回", "关于",
|
|
38
|
+
"月票榜", "阅读榜", "书友榜", "推荐榜", "签约榜", "新人榜",
|
|
39
|
+
"畅销榜", "大家都在搜", "男生小说排行榜", "女生小说排行榜",
|
|
40
|
+
"男频阅读榜", "女频阅读榜", "男频新书榜", "番茄小说",
|
|
41
|
+
"登录后获得更多特色功能", "安装起点读书客户端", "看更多正版好书",
|
|
42
|
+
"月票", "人气", "点击", "万字", "推荐", "收藏", "鲜花", "打赏", "推荐票"}
|
|
43
|
+
|
|
44
|
+
_CJK_RE = re.compile(r">([\u4e00-\u9fa5]{2,12})<")
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _curl_text(url):
|
|
48
|
+
"""curl 子进程抓页面文本;失败返回空串。"""
|
|
49
|
+
from . import runner
|
|
50
|
+
try:
|
|
51
|
+
r = runner.run_process(
|
|
52
|
+
argv=["curl", "-sS", "--max-time", "20", "-A", _UA, url],
|
|
53
|
+
timeout=30)
|
|
54
|
+
except Exception:
|
|
55
|
+
return ""
|
|
56
|
+
if not r.get("ok"):
|
|
57
|
+
return ""
|
|
58
|
+
return r.get("stdout") or ""
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _extract_cjk(html, limit):
|
|
62
|
+
"""从 HTML 提取中文元素材(去 UI 噪音、保序去重)。"""
|
|
63
|
+
items, seen = [], set()
|
|
64
|
+
for t in _CJK_RE.findall(html or ""):
|
|
65
|
+
if t in _NOISE or t in seen:
|
|
66
|
+
continue
|
|
67
|
+
seen.add(t)
|
|
68
|
+
items.append(t)
|
|
69
|
+
if len(items) >= limit:
|
|
70
|
+
break
|
|
71
|
+
return items
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def fetch_qimao_rank(limit=30):
|
|
75
|
+
"""七猫排行榜:返回元素列表,抓取失败返回 []。"""
|
|
76
|
+
return _extract_cjk(_curl_text(QIMAO_RANK_URL), limit)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def fetch_fanqie_rank(limit=30):
|
|
80
|
+
"""番茄 Web 版榜单:返回元素列表,抓取失败返回 []。"""
|
|
81
|
+
return _extract_cjk(_curl_text(FANQIE_RANK_URL), limit)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def fetch_qidian_rank(limit=30):
|
|
85
|
+
"""起点移动版榜单:返回元素列表,抓取失败返回 []。"""
|
|
86
|
+
return _extract_cjk(_curl_text(QIDIAN_M_RANK_URL), limit)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def fetch_zongheng_rank(limit=30):
|
|
90
|
+
"""纵横排行榜:返回元素列表,抓取失败返回 []。"""
|
|
91
|
+
return _extract_cjk(_curl_text(ZONGHENG_RANK_URL), limit)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
# 源清单:顺序即素材拼接顺序(七猫/番茄为元老源保持在前)。
|
|
95
|
+
# 存函数名、调用期 globals() 解析——mock.patch 换模块属性才能生效
|
|
96
|
+
# (存函数引用会把定义期原函数钉死,测试会穿透打真网络)。
|
|
97
|
+
_SOURCES = (
|
|
98
|
+
("七猫排行榜素材", "fetch_qimao_rank"),
|
|
99
|
+
("番茄排行榜素材", "fetch_fanqie_rank"),
|
|
100
|
+
("起点排行榜素材", "fetch_qidian_rank"),
|
|
101
|
+
("纵横排行榜素材", "fetch_zongheng_rank"),
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def fetch_rank_items(limit=40):
|
|
106
|
+
"""四源聚合:各抓一份(跨源去重合并,按源清单顺序)。
|
|
107
|
+
|
|
108
|
+
全部失败才返回 []——单源挂了其余源仍可用,扫榜永不因单点挡任务。"""
|
|
109
|
+
merged, seen = [], set()
|
|
110
|
+
for _label, fname in _SOURCES:
|
|
111
|
+
for t in globals()[fname](limit):
|
|
112
|
+
if t not in seen:
|
|
113
|
+
seen.add(t)
|
|
114
|
+
merged.append(t)
|
|
115
|
+
return merged
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def rank_scan_prompt(goal):
|
|
119
|
+
"""组装扫榜选材分析提示词。四源全空返回 None(调用方回落直连提示词)。"""
|
|
120
|
+
blocks, seen = [], set()
|
|
121
|
+
for label, fname in _SOURCES:
|
|
122
|
+
items = [t for t in globals()[fname](30) if t not in seen]
|
|
123
|
+
seen.update(items)
|
|
124
|
+
if items:
|
|
125
|
+
blocks.append("### %s\n%s" % (label, "、".join(items)))
|
|
126
|
+
if not blocks:
|
|
127
|
+
return None
|
|
128
|
+
material = "\n\n".join(blocks)
|
|
129
|
+
return (
|
|
130
|
+
"你是网文选题分析师。以下是刚刚抓取的各平台排行榜页面的书目与分类素材:\n\n"
|
|
131
|
+
"## 榜单元素材\n%s\n\n"
|
|
132
|
+
"## 用户想写的方向\n%s\n\n"
|
|
133
|
+
"## 你的产出(Markdown 报告)\n"
|
|
134
|
+
"1. **热门题材 Top3**:各自的共同特征与上榜代表书目(跨平台重合的题材单独点出——多平台都热说明是真风口)\n"
|
|
135
|
+
"2. **高频人设/套路总结**:3-5 条,点名反复出现的元素\n"
|
|
136
|
+
"3. **差异化切入建议**:2-3 个,结合用户方向给出「题材+人设」组合与一句话理由\n"
|
|
137
|
+
"直接输出分析报告,不要复述素材清单,不要输出与报告无关的内容。"
|
|
138
|
+
% (material, goal or "(用户未指定方向,按大盘热门分析)"))
|
package/app/core/paths.py
CHANGED
|
@@ -54,8 +54,8 @@ def default_data_dir(repo=None, environ=None) -> Path:
|
|
|
54
54
|
DATA_DIR = default_data_dir()
|
|
55
55
|
TASKS_DIR = DATA_DIR / "tasks"
|
|
56
56
|
RUNS_DIR = DATA_DIR / "runs"
|
|
57
|
-
USAGE_DIR = DATA_DIR / "usage"
|
|
58
|
-
ERRORS_DIR = DATA_DIR / "errors"
|
|
57
|
+
USAGE_DIR = DATA_DIR / "usage"
|
|
58
|
+
ERRORS_DIR = DATA_DIR / "errors"
|
|
59
59
|
PUBLISH_DIR = DATA_DIR / "publish" # 一键发布:台账/作品登记/流程选择器覆盖/浏览器 profile
|
|
60
60
|
CATALOG_FILE = DATA_DIR / "catalog.json"
|
|
61
61
|
ENABLED_FILE = DATA_DIR / "orchestration.json"
|
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, dispatch_log, history, hooks, jobs, knowledge, manager, modelhub, mocks, paihang, planner, registry, router, runner, skills, store, task_compile, usage, volumes
|
|
22
|
+
from . import aiflavor, attachments, branching, catalog, chaptersafe, defectretro, dispatch_log, flows, history, hooks, jobs, knowledge, manager, modelhub, mocks, paihang, planner, 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
|
|
@@ -365,6 +365,44 @@ def _binding_dead_msg(agent):
|
|
|
365
365
|
"请在「模型调度(可选)」页为该 CLI 指定已启用的供应商")
|
|
366
366
|
|
|
367
367
|
|
|
368
|
+
def _binding_brief(agent):
|
|
369
|
+
"""步骤级绑定速记(借鉴 WorkDSH ResolvedExecutionBinding:模型目录只是发现
|
|
370
|
+
信息,解析结果才是权威)。把本步实际执行用的 provider 链与上游 host 压成
|
|
371
|
+
一行——配额烧错家/模型漂移直接对账步骤行,不必翻日志文本里的审计行。
|
|
372
|
+
只记身份不记密钥:env 形态的注入也只取 URL 的 host 部分。"""
|
|
373
|
+
from . import upstream as _up
|
|
374
|
+
entries = []
|
|
375
|
+
for item in (agent.get("call_chain") or [])[:4]:
|
|
376
|
+
if not isinstance(item, dict):
|
|
377
|
+
continue
|
|
378
|
+
pid = str(item.get("provider_id") or "").strip()
|
|
379
|
+
prov = item.get("provider") if isinstance(item.get("provider"), dict) else {}
|
|
380
|
+
host = ""
|
|
381
|
+
try:
|
|
382
|
+
host = _up.normalize_upstream(
|
|
383
|
+
str(prov.get("base_url") or prov.get("baseUrl") or ""))
|
|
384
|
+
except Exception:
|
|
385
|
+
host = ""
|
|
386
|
+
if pid and host and host not in pid:
|
|
387
|
+
entries.append("%s@%s" % (pid, host))
|
|
388
|
+
else:
|
|
389
|
+
entries.append(pid or host or "?")
|
|
390
|
+
if not entries:
|
|
391
|
+
# env 注入形态(如 ANTHROPIC_BASE_URL):URL 值的 host 不敏感,可入账
|
|
392
|
+
for k, v in sorted((agent.get("env") or {}).items()):
|
|
393
|
+
s = str(v or "")
|
|
394
|
+
if s.startswith(("http://", "https://")):
|
|
395
|
+
try:
|
|
396
|
+
host = _up.normalize_upstream(s)
|
|
397
|
+
except Exception:
|
|
398
|
+
continue
|
|
399
|
+
if host:
|
|
400
|
+
entries.append("%s=%s" % (str(k)[:24], host))
|
|
401
|
+
if len(entries) >= 3:
|
|
402
|
+
break
|
|
403
|
+
return ",".join(entries[:3])[:120]
|
|
404
|
+
|
|
405
|
+
|
|
368
406
|
def _run_step(run_id, role, agent, prompt, workdir, readonly, ev, timeout=runner.DEFAULT_TIMEOUT, note="", resume=None, images=None, require_tools=False, swap_guard=None):
|
|
369
407
|
"""执行一个智能体步骤并记录。返回 runner 统一结果。
|
|
370
408
|
|
|
@@ -402,7 +440,8 @@ def _run_step(run_id, role, agent, prompt, workdir, readonly, ev, timeout=runner
|
|
|
402
440
|
note = ((note + ";") if note else "") + "⚠ " + dead_msg
|
|
403
441
|
step, log_abs = store.add_step(run_id, role, agent["id"],
|
|
404
442
|
agent.get("label", agent["id"]), note=note,
|
|
405
|
-
model=agent.get("model") or ""
|
|
443
|
+
model=agent.get("model") or "",
|
|
444
|
+
provider=_binding_brief(agent))
|
|
406
445
|
start = time.time()
|
|
407
446
|
if dead_binding:
|
|
408
447
|
from .error_codes import ErrorCode
|
|
@@ -473,7 +512,8 @@ def _run_builtin_step(run_id, role, bi, prompt, workdir, ev, note="", images=Non
|
|
|
473
512
|
_wait_gate(run_id, ev)
|
|
474
513
|
deadline = _ensure_budget(run_id)
|
|
475
514
|
step, log_abs = store.add_step(run_id, role, "builtin", "CodeBee", note=note,
|
|
476
|
-
model=bi.get("model") or ""
|
|
515
|
+
model=bi.get("model") or "",
|
|
516
|
+
provider=str(bi.get("provider_id") or "")[:120])
|
|
477
517
|
start = time.time()
|
|
478
518
|
agent_pseudo = {"id": "builtin", "label": "CodeBee", "kind": "builtin", "mode": "real",
|
|
479
519
|
"provider": {"id": bi.get("provider_id") or "",
|
|
@@ -650,9 +690,15 @@ def _finish_step_result(run_id, step, res, role, agent, start):
|
|
|
650
690
|
status = "timeout"
|
|
651
691
|
else:
|
|
652
692
|
status = "done" if res["ok"] else "failed"
|
|
693
|
+
step_summary = (res.get("text") or res.get("error") or "")[:600]
|
|
694
|
+
if raw.get("quiescence_unknown"):
|
|
695
|
+
# 杀树未确认清空(借鉴 WorkDSH 取消结算分层:kill 完成 ≠ 停稳):
|
|
696
|
+
# 孙进程可能仍在跑,孤儿清扫稍后兜底。只提示不改变终态语义。
|
|
697
|
+
step_summary = ((step_summary + "\n") if step_summary else "") + \
|
|
698
|
+
"⚠ 进程树未确认清空:孙进程可能仍在运行(孤儿清扫稍后兜底)"
|
|
653
699
|
store.finish_step(run_id, step["n"],
|
|
654
700
|
status,
|
|
655
|
-
summary=
|
|
701
|
+
summary=step_summary,
|
|
656
702
|
exit_code=res.get("raw", {}).get("exit_code"),
|
|
657
703
|
cost_usd=res.get("cost_usd", 0.0),
|
|
658
704
|
tokens=res.get("tokens", 0),
|
|
@@ -783,6 +829,7 @@ __CONTEXT__
|
|
|
783
829
|
不要自造评分标准或质量闸门;超出任务目标的「顺手加固」一律不做。
|
|
784
830
|
真实风险照样要找:发现就写进总结,由人决定要不要修。
|
|
785
831
|
- 完成后只用 3-5 句话总结你改了什么、为什么。
|
|
832
|
+
- 交付物从简:回复不写开场白、不复述任务、不堆与改动无关的解释——代码、说明、总结直接给干货(借鉴 claude-token-efficient:输出 token 也是真金白银)。
|
|
786
833
|
__VERIFY_HINT__"""
|
|
787
834
|
|
|
788
835
|
CODE_FIX_PROMPT = """你是一名高级工程师。你之前的实现没有通过验收,请在当前工作目录中直接修复(直接修改/创建文件)。创建/写入的文件一律以 UTF-8 编码保存(PowerShell 显式加 -Encoding UTF8,禁止依赖系统默认编码)。
|
|
@@ -1915,6 +1962,10 @@ def _run_direct(run, task, agents, ev, stats, mode):
|
|
|
1915
1962
|
# 扫榜选材(借鉴 oh-story 扫榜):抓七猫排行榜公开数据注入,
|
|
1916
1963
|
# AI 做选题洞察;抓取失败回落普通直连提示词
|
|
1917
1964
|
prompt = paihang.rank_scan_prompt(task.get("goal") or "") or ""
|
|
1965
|
+
if task.get("type") == "defect_retro" and bi is not None:
|
|
1966
|
+
# 缺陷复盘(借鉴 test-defect-retrospective):三视角分析框架
|
|
1967
|
+
# 注入,缺陷导出 CSV 走任务附件通道(框架恒有、不回落)
|
|
1968
|
+
prompt = defectretro.retro_prompt(task.get("goal") or "")
|
|
1918
1969
|
if not prompt:
|
|
1919
1970
|
if bi is not None:
|
|
1920
1971
|
prompt = (BUILTIN_DIRECT_PROMPT
|
|
@@ -2038,6 +2089,10 @@ CONTENT_DELIVERY_CONTRACTS = {
|
|
|
2038
2089
|
"novel": ("小说作者", [
|
|
2039
2090
|
"遵守用户给定的题材、篇幅、视角和风格;未给出的核心设定不要擅自扩张。",
|
|
2040
2091
|
"用场景、行动和对话推进冲突,人物动机与前后因果保持一致。",
|
|
2092
|
+
# Madman-Architect-Carpenter 写作框架(writing-with-agents 借鉴;Judge=评审链已有):
|
|
2093
|
+
# 先发散倾倒念头再组织结构成文,不要边写边自我审查把点子掐死在半路
|
|
2094
|
+
"成文分三步:先把不设限的情节/画面/台词念头快速倾倒成素材清单,再挑出主线组织成"
|
|
2095
|
+
"结构,最后按结构成文——初稿期不做质量审判。",
|
|
2041
2096
|
]),
|
|
2042
2097
|
"article": ("平台内容主编", [
|
|
2043
2098
|
"标题、开头钩子、正文层级和结尾行动建议要适配目标平台与读者。",
|
|
@@ -2079,6 +2134,9 @@ CONTENT_DELIVERY_CONTRACTS = {
|
|
|
2079
2134
|
# sepia 分场合规则(PR 回复体裁):先答再铺陈;篇幅与利害成正比
|
|
2080
2135
|
"第一句/第一段先给结论或答复(对方要做什么、答应还是不答应),再给必要背景。",
|
|
2081
2136
|
"请求具体到动作与截止时间;篇幅与事情轻重成正比,删掉礼节性空话与自我表扬。",
|
|
2137
|
+
# agentic-inbox 借鉴(线程级交付契约):回复是对话不是独白
|
|
2138
|
+
"回复往来邮件时逐条回应对方提出的每个问题与请求(可用清单),不漏问、不回避;"
|
|
2139
|
+
"行动项落到具体动作+负责人+截止时间。",
|
|
2082
2140
|
]),
|
|
2083
2141
|
"tech_proposal": ("解决方案架构师", [
|
|
2084
2142
|
"覆盖现状与目标、约束、候选方案对比、推荐架构、实施阶段、风险与回滚、验收指标。",
|
|
@@ -2117,7 +2175,9 @@ def _content_contract(task):
|
|
|
2117
2175
|
return "\n\n## 本类型交付约束\n" + "\n".join("- " + item for item in spec[1])
|
|
2118
2176
|
|
|
2119
2177
|
# 调研报告类稿件的追加要求(借鉴 gpt-researcher 迭代深研):有网络/读文件工具时
|
|
2120
|
-
#
|
|
2178
|
+
# 多源交叉验证,单源结论降权——调研的可信度来自证据链而非文采。
|
|
2179
|
+
# 来源冲突裁决借鉴 deepresearch-agent 记忆层矛盾检测(Majority/Source-Weight 消解);
|
|
2180
|
+
# 子问题缺口补查借鉴 dzhng/deep-research 迭代循环
|
|
2121
2181
|
RESEARCH_APPENDIX = """
|
|
2122
2182
|
|
|
2123
2183
|
## 调研要求(证据链)
|
|
@@ -2125,6 +2185,11 @@ RESEARCH_APPENDIX = """
|
|
|
2125
2185
|
单源信息要标注「仅单一来源」。
|
|
2126
2186
|
- 引用来源在文中用行内链接或脚注标明(域名即可,不编造 URL)。
|
|
2127
2187
|
- 区分「事实」与「观点」:数据/时间/版本号给来源,预测/评价标明是分析。
|
|
2188
|
+
- 来源冲突显式裁决:同一事实多个来源说法不一致时,写明分歧点与各方依据,给出
|
|
2189
|
+
倾向判断和理由(多数来源一致取多数,权威差异按来源可信度加权)——不各说一半,
|
|
2190
|
+
也不悄悄只取其一。
|
|
2191
|
+
- 缺口驱动补查:起草前先列出报告要回答的子问题清单,逐个核对证据是否到位;
|
|
2192
|
+
没找到答案的子问题明确标注「证据不足」,不用泛泛而谈填空。
|
|
2128
2193
|
- 结构硬性要求:报告第一段必须是「**核心结论**」三行以内的要点摘要(结论先行),
|
|
2129
2194
|
之后才展开分层论证 → 风险与局限(说明哪些结论证据不足)。"""
|
|
2130
2195
|
|
|
@@ -2144,10 +2209,10 @@ NOVEL_CRITIQUE_PROMPT = """你是严格的评审(不要使用任何工具、
|
|
|
2144
2209
|
请输出一个 ```json 代码块,不要输出其他内容。JSON 结构:
|
|
2145
2210
|
{
|
|
2146
2211
|
"scores": {"__DIMKEYS__"},
|
|
2147
|
-
"issues": [{"dim": "维度名", "severity": "major|minor", "note": "具体问题"}],
|
|
2212
|
+
"issues": [{"dim": "维度名", "severity": "major|minor", "note": "具体问题", "quote": "支撑该问题的稿件原文连续片段(≥8字,逐字摘录不许改写)"}],
|
|
2148
2213
|
"summary": "一句话总评"
|
|
2149
2214
|
}
|
|
2150
|
-
每个维度打 1-10 分(可为小数),宁严勿宽。
|
|
2215
|
+
每个维度打 1-10 分(可为小数),宁严勿宽。major 问题必须给 quote(系统会逐条校验引文是否真在稿件中——编造的引文会被降档标记)。
|
|
2151
2216
|
|
|
2152
2217
|
## 待评审稿件
|
|
2153
2218
|
---
|
|
@@ -2260,6 +2325,18 @@ def _read_ledger(workdir):
|
|
|
2260
2325
|
return txt or "(暂无记录)"
|
|
2261
2326
|
|
|
2262
2327
|
|
|
2328
|
+
def _read_ledger_raw(workdir):
|
|
2329
|
+
"""账本原文(缺文件/读失败返回空串)——供 _ledger_watchlist 解析,
|
|
2330
|
+
不带注入面的兜底文案。"""
|
|
2331
|
+
p = os.path.abspath(os.path.join(str(workdir or ""), LEDGER_FILE))
|
|
2332
|
+
if not _inside(workdir, p) or not os.path.isfile(p):
|
|
2333
|
+
return ""
|
|
2334
|
+
try:
|
|
2335
|
+
return _read_text_any_enc(p)
|
|
2336
|
+
except OSError:
|
|
2337
|
+
return ""
|
|
2338
|
+
|
|
2339
|
+
|
|
2263
2340
|
def _shrink_context_block(sk_block, bible, budget=12000):
|
|
2264
2341
|
"""分层上下文降级(长提示词在容量受限通道上会挂起/秒拒,2026-09-17 讯飞实测)。
|
|
2265
2342
|
|
|
@@ -2369,10 +2446,11 @@ SERIAL_GLOBAL_PROMPT = """你是网文主编(不要修改任何文件)。全
|
|
|
2369
2446
|
请输出一个 ```json 代码块,不要输出其他内容。JSON 结构:
|
|
2370
2447
|
{
|
|
2371
2448
|
"scores": {"__DIMKEYS__"},
|
|
2372
|
-
"issues": [{"dim": "维度名", "severity": "major|minor", "note": "具体问题(指明哪一章)"}],
|
|
2449
|
+
"issues": [{"dim": "维度名", "severity": "major|minor", "note": "具体问题(指明哪一章)", "quote": "支撑该问题的稿件原文连续片段(≥8字,逐字摘录不许改写)"}],
|
|
2373
2450
|
"summary": "一句话总评:是否达到可签约水平"
|
|
2374
2451
|
}
|
|
2375
2452
|
每个维度打 1-10 分,宁严勿宽。重点关注:主线一致性、人物弧光、节奏、爽点密度、完本感。
|
|
2453
|
+
major 问题必须给 quote(系统会逐条校验引文是否真在稿件中——编造的引文会被降档标记)。
|
|
2376
2454
|
|
|
2377
2455
|
## 全书目标
|
|
2378
2456
|
__GOAL__
|
|
@@ -2446,6 +2524,87 @@ def _wc(text):
|
|
|
2446
2524
|
return len(re.sub(r"\s", "", text or ""))
|
|
2447
2525
|
|
|
2448
2526
|
|
|
2527
|
+
_ANCHOR_EDGE_PUNCT = "。!?,、;:…—·,.!?;:\"'“”‘’()()[]【】《》<>*#-"
|
|
2528
|
+
|
|
2529
|
+
|
|
2530
|
+
def _norm_anchor(s):
|
|
2531
|
+
"""锚校验归一:去全部空白 + 剥首尾标点(引文摘录常多带一个句号/引号,
|
|
2532
|
+
中英混排空格差异也不应判假)。"""
|
|
2533
|
+
s = re.sub(r"\s+", "", str(s or ""))
|
|
2534
|
+
return s.strip(_ANCHOR_EDGE_PUNCT)
|
|
2535
|
+
|
|
2536
|
+
|
|
2537
|
+
def _anchor_issues(issues, manuscript):
|
|
2538
|
+
"""评审证据锚(ARIS Anti-Autoresearch 借鉴):issue 带 quote(稿件原文
|
|
2539
|
+
摘录)时做确定性 containment 校验——模型只提议发现,锚定事实由规则裁决。
|
|
2540
|
+
|
|
2541
|
+
三态:quote 命中稿件 → anchored=True;quote 给了但对不上 → anchored=False
|
|
2542
|
+
(降档殿后+标记,不删除:模型改写式引用时保留可见性);没给 quote →
|
|
2543
|
+
anchored=None(老格式评审灰度兼容,不标记不排序影响)。返回 (issues, 未锚定数)。"""
|
|
2544
|
+
ms = _norm_anchor(manuscript)
|
|
2545
|
+
unanchored = 0
|
|
2546
|
+
for it in issues or []:
|
|
2547
|
+
q = _norm_anchor(it.get("quote"))
|
|
2548
|
+
if not q:
|
|
2549
|
+
it["anchored"] = None
|
|
2550
|
+
continue
|
|
2551
|
+
it["anchored"] = len(q) >= 6 and q in ms
|
|
2552
|
+
if it["anchored"] is False:
|
|
2553
|
+
unanchored += 1
|
|
2554
|
+
return issues, unanchored
|
|
2555
|
+
|
|
2556
|
+
|
|
2557
|
+
def _major_lines(majors, limit=8):
|
|
2558
|
+
"""majors → 修订面意见行:锚定的在前,未锚定殿后并带标记(模型据此
|
|
2559
|
+
分配采信权重);无 quote 的老格式行居中不动。"""
|
|
2560
|
+
ranked = sorted(majors or [],
|
|
2561
|
+
key=lambda x: {True: 0, False: 2}.get(x.get("anchored"), 1))
|
|
2562
|
+
lines = []
|
|
2563
|
+
for x in ranked[:limit]:
|
|
2564
|
+
mark = ("(未锚定:引文与稿件不符,酌情采信)"
|
|
2565
|
+
if x.get("anchored") is False else "")
|
|
2566
|
+
lines.append("- [%s] %s%s" % (x.get("dim", "?"),
|
|
2567
|
+
str(x.get("note", ""))[:140], mark))
|
|
2568
|
+
return lines
|
|
2569
|
+
|
|
2570
|
+
|
|
2571
|
+
_LEDGER_DONE_WORDS = ("已兑现", "已回收", "已解除", "已完成", "已了结", "已交付")
|
|
2572
|
+
|
|
2573
|
+
|
|
2574
|
+
def _ledger_watchlist(ledger_text, upto_chapter, min_age=3, limit=8):
|
|
2575
|
+
"""陈年承诺督促(continuum 承诺/伏笔账借鉴):按「## 第 N 章」段解析
|
|
2576
|
+
资源账本,承诺/伏笔类条目现状未了结、且距本章 ≥min_age 章未更新 →
|
|
2577
|
+
观查清单(推送起草面督促推进/回收)。账本按章追加、同名条目以最后
|
|
2578
|
+
出现的章为准;老账本无该类条目返回空。"""
|
|
2579
|
+
last = {} # (类型, 名称) -> (最后章号, 现状)
|
|
2580
|
+
cur = 0
|
|
2581
|
+
for ln in str(ledger_text or "").splitlines():
|
|
2582
|
+
m = re.match(r"##\s*第\s*(\d+)\s*章", ln.strip())
|
|
2583
|
+
if m:
|
|
2584
|
+
cur = int(m.group(1))
|
|
2585
|
+
continue
|
|
2586
|
+
s = ln.strip().lstrip("-*").strip()
|
|
2587
|
+
if not s or "|" not in s:
|
|
2588
|
+
continue
|
|
2589
|
+
parts = [p.strip() for p in s.split("|")]
|
|
2590
|
+
if len(parts) < 3:
|
|
2591
|
+
continue
|
|
2592
|
+
typ, name, status = parts[0], parts[1], "|".join(parts[2:])
|
|
2593
|
+
if "承诺" not in typ and "伏笔" not in typ:
|
|
2594
|
+
continue
|
|
2595
|
+
last[(typ, name)] = (cur, status)
|
|
2596
|
+
out = []
|
|
2597
|
+
for (typ, name), (ch_no, status) in last.items():
|
|
2598
|
+
if any(w in status for w in _LEDGER_DONE_WORDS):
|
|
2599
|
+
continue
|
|
2600
|
+
age = upto_chapter - ch_no
|
|
2601
|
+
if age >= min_age:
|
|
2602
|
+
out.append({"type": typ, "name": name, "status": status,
|
|
2603
|
+
"age": age, "last_chapter": ch_no})
|
|
2604
|
+
out.sort(key=lambda x: -x["age"])
|
|
2605
|
+
return out[:limit]
|
|
2606
|
+
|
|
2607
|
+
|
|
2449
2608
|
def _write_chapter(workdir, i, text):
|
|
2450
2609
|
# 安全落盘委托(inkos 安全章节工作区借鉴):tmp+原子改名,写一半
|
|
2451
2610
|
# 崩溃不留半章冒充成稿;路径守卫在 chaptersafe(resolve+parents)
|
|
@@ -2702,6 +2861,19 @@ def _run_serial_review(run, task, agents, ev, stats, mode, critics, impl, route,
|
|
|
2702
2861
|
ev_check_lines = [[]] # 每章重置:第一份非空评审的逐项判定
|
|
2703
2862
|
ledger_lines = [] # 本章全部评审的账本增量并集
|
|
2704
2863
|
ledger_txt = _read_ledger(workdir) # 截至上一章的资源账本(起草注入)
|
|
2864
|
+
# 陈年承诺督促(continuum 借鉴):账本里 ≥3 章未推进且未了结的承诺/
|
|
2865
|
+
# 伏笔点名给起草面——「本章推进/回收其一,或明确有意留白」;老账本
|
|
2866
|
+
# 无承诺类条目时静默不占字节
|
|
2867
|
+
try:
|
|
2868
|
+
_wl = _ledger_watchlist(_read_ledger_raw(workdir), i)
|
|
2869
|
+
if _wl:
|
|
2870
|
+
ledger_txt += ("\n\n### ⚠ 陈年承诺/伏笔(多章未推进)\n"
|
|
2871
|
+
+ "\n".join("- %s「%s」:%s(已 %d 章未更新——本章应推进、"
|
|
2872
|
+
"回收其一,或在大纲允许处明确留白)"
|
|
2873
|
+
% (w["type"], w["name"], w["status"], w["age"])
|
|
2874
|
+
for w in _wl))
|
|
2875
|
+
except Exception:
|
|
2876
|
+
pass
|
|
2705
2877
|
prev = ""
|
|
2706
2878
|
draft_sid = "" # §07 T1.1:本轮 draft/复用章的会话 id(revise 复用;reuse 时为空)
|
|
2707
2879
|
if i > 1:
|
|
@@ -3234,9 +3406,11 @@ def _run_serial_review(run, task, agents, ev, stats, mode, critics, impl, route,
|
|
|
3234
3406
|
rounds_used = 2
|
|
3235
3407
|
majors = [x for x in issues_all if x.get("chapter") == i
|
|
3236
3408
|
and x.get("severity") == "major"][:8]
|
|
3409
|
+
majors, _unanchored = _anchor_issues(majors, text)
|
|
3237
3410
|
crit_lines = ["- %s:%.1f(章阈值 %.1f)" % (d, means[d], threshold_ch) for d in dims]
|
|
3238
|
-
crit_lines +=
|
|
3239
|
-
|
|
3411
|
+
crit_lines += _major_lines(majors)
|
|
3412
|
+
if _unanchored:
|
|
3413
|
+
crit_lines.append("- ⚠ %d 条 major 意见的引文未能在稿件中锚定,已降序殿后" % _unanchored)
|
|
3240
3414
|
if rnd == 1 and race_losers and impl.get("mode") == "real":
|
|
3241
3415
|
# 赛马败者精华回收(连载版):一次选择器调用提炼落选稿优点,
|
|
3242
3416
|
# 与评审意见一并喂给首轮修订;失败静默不影响修订
|
|
@@ -3962,8 +4136,10 @@ def _run_content_review(run, task, agents, ev, stats, mode):
|
|
|
3962
4136
|
# 修订
|
|
3963
4137
|
crit_lines = ["- %s:%.1f(阈值 %.1f)" % (d, means[d], threshold) for d in dims]
|
|
3964
4138
|
majors = [i for i in issues_all if i.get("severity") == "major"][:8]
|
|
3965
|
-
|
|
3966
|
-
|
|
4139
|
+
majors, _unanchored = _anchor_issues(majors, manuscript)
|
|
4140
|
+
crit_lines += _major_lines(majors)
|
|
4141
|
+
if _unanchored:
|
|
4142
|
+
crit_lines.append("- ⚠ %d 条 major 意见的引文未能在稿件中锚定,已降序殿后" % _unanchored)
|
|
3967
4143
|
if r == 1 and bestof_improvements:
|
|
3968
4144
|
# 败者精华回收(freebuff suggestedImprovements 的落地):终审从落选
|
|
3969
4145
|
# 候选稿提炼的优点,首轮修订时与评审意见一并喂给作者
|
|
@@ -4387,6 +4563,39 @@ def execute_run(run_id):
|
|
|
4387
4563
|
store.update_run(run_id, expected_status="running", status="failed",
|
|
4388
4564
|
error="找不到任务 %s" % run.get("task_id"), ended_at=_now())
|
|
4389
4565
|
return
|
|
4566
|
+
# 流程修订漂移诊断(借鉴 WorkDSH ADR-0010 修订钉住):任务参数创建时已
|
|
4567
|
+
# 固化,改流程不影响固化字段;但历史任务缺失字段的兜底仍取当前流程定义,
|
|
4568
|
+
# 续跑/重试可能静默吃到新流程。漂移不阻断——「改完流程再重跑」是合法
|
|
4569
|
+
# 路径——但必须留痕可对账:run 记结构化 flow_drift + 用户可见 warning。
|
|
4570
|
+
_drift = flows.flow_drift(task)
|
|
4571
|
+
if _drift:
|
|
4572
|
+
store.update_run(run_id, flow_drift=_drift)
|
|
4573
|
+
try:
|
|
4574
|
+
_warns = (store.get_run(run_id) or {}).get("warnings") or []
|
|
4575
|
+
store.update_run(run_id, warnings=_warns + [
|
|
4576
|
+
"流程「%s」在任务创建后已修改(%s → %s);任务固化参数不受影响,"
|
|
4577
|
+
"历史任务缺失字段的兜底取值已随新定义"
|
|
4578
|
+
% (_drift["flow"], _drift["pinned"][:8], _drift["current"][:8])])
|
|
4579
|
+
except Exception:
|
|
4580
|
+
pass
|
|
4581
|
+
print("[CodeBee] 流程漂移:%s(任务 %s 创建后流程已修改 %s → %s)"
|
|
4582
|
+
% (run_id, task.get("id"), _drift["pinned"][:8], _drift["current"][:8]),
|
|
4583
|
+
flush=True)
|
|
4584
|
+
# 附件完整性对账(借鉴 WorkDSH 输入引用修订):任务钉的附件指纹 vs 当前
|
|
4585
|
+
# 文件内容。换过/删过给明确诊断——续跑吃到「不是当初那份附件」不能无声。
|
|
4586
|
+
try:
|
|
4587
|
+
_adrift = attachments.verify_task(task, task.get("workdir"))
|
|
4588
|
+
if _adrift:
|
|
4589
|
+
store.update_run(run_id, attachment_drift=_adrift)
|
|
4590
|
+
_names = "、".join(d["name"] for d in _adrift[:3]) + (
|
|
4591
|
+
" 等 %d 项" % len(_adrift) if len(_adrift) > 3 else "")
|
|
4592
|
+
_warns2 = (store.get_run(run_id) or {}).get("warnings") or []
|
|
4593
|
+
store.update_run(run_id, warnings=_warns2 + [
|
|
4594
|
+
"附件在任务创建后已变化(%s):%s;本次执行使用当前文件内容"
|
|
4595
|
+
% (", ".join(sorted({d["status"] for d in _adrift})), _names)])
|
|
4596
|
+
print("[CodeBee] 附件漂移:%s(%s)" % (run_id, _names), flush=True)
|
|
4597
|
+
except Exception:
|
|
4598
|
+
pass
|
|
4390
4599
|
try:
|
|
4391
4600
|
_ensure_budget(run_id)
|
|
4392
4601
|
except TaskTimeout:
|