codebee 0.1.67 → 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.
Files changed (113) hide show
  1. package/CHANGELOG.md +543 -532
  2. package/LICENSE +21 -21
  3. package/README.md +11 -7
  4. package/app/.playwright-cli/console-2026-09-23T08-26-03-635Z.log +88 -0
  5. package/app/.playwright-cli/page-2026-09-23T08-26-03-845Z.yml +126 -0
  6. package/app/.playwright-cli/page-2026-09-23T08-29-44-203Z.png +0 -0
  7. package/app/.playwright-cli/page-2026-09-23T08-35-42-046Z.yml +210 -0
  8. package/app/.playwright-cli/page-2026-09-23T08-36-52-858Z.png +0 -0
  9. package/app/.playwright-cli/page-2026-09-23T08-37-33-938Z.png +0 -0
  10. package/app/.playwright-cli/run-detail-after.png +0 -0
  11. package/app/core/acceptance.py +100 -100
  12. package/app/core/aiflavor.py +197 -197
  13. package/app/core/artifacts.py +148 -148
  14. package/app/core/assets.py +110 -110
  15. package/app/core/attachments.py +623 -623
  16. package/app/core/automation.py +758 -758
  17. package/app/core/backup.py +470 -470
  18. package/app/core/beekeeper.py +183 -183
  19. package/app/core/board.py +209 -209
  20. package/app/core/bookmeta.py +930 -930
  21. package/app/core/bookmeta_catalog.py +180 -180
  22. package/app/core/branching.py +137 -137
  23. package/app/core/builtin_agent.py +1234 -1234
  24. package/app/core/capability.py +144 -144
  25. package/app/core/catalog.py +42 -0
  26. package/app/core/chaptersafe.py +32 -32
  27. package/app/core/cleanup.py +303 -303
  28. package/app/core/compaction.py +201 -201
  29. package/app/core/covergen.py +356 -356
  30. package/app/core/defectretro.py +43 -43
  31. package/app/core/diagnostics.py +114 -114
  32. package/app/core/dispatch.py +173 -173
  33. package/app/core/dispatch_log.py +128 -113
  34. package/app/core/env_scrub.py +85 -85
  35. package/app/core/error_codes.py +23 -23
  36. package/app/core/errorlog.py +137 -137
  37. package/app/core/evaluation.py +174 -174
  38. package/app/core/failure_notify.py +290 -290
  39. package/app/core/flows.py +2 -2
  40. package/app/core/goal_service.py +167 -167
  41. package/app/core/health.py +374 -374
  42. package/app/core/history.py +38 -38
  43. package/app/core/hooks.py +162 -162
  44. package/app/core/jobs.py +29 -25
  45. package/app/core/knowledge.py +7 -0
  46. package/app/core/manager.py +2122 -2122
  47. package/app/core/market.py +362 -362
  48. package/app/core/mocks.py +64 -64
  49. package/app/core/modelhub.py +22 -3
  50. package/app/core/notify.py +106 -106
  51. package/app/core/operations.py +228 -228
  52. package/app/core/paihang.py +138 -138
  53. package/app/core/paths.py +2 -2
  54. package/app/core/pipeline.py +16 -1
  55. package/app/core/planner.py +2 -0
  56. package/app/core/portguard.py +111 -111
  57. package/app/core/preview.py +293 -293
  58. package/app/core/publish/__init__.py +7 -7
  59. package/app/core/publish/auto.py +367 -367
  60. package/app/core/publish/browser.py +539 -539
  61. package/app/core/publish/fanqie.py +159 -159
  62. package/app/core/publish/flow.py +440 -440
  63. package/app/core/publish/flows-fanqie-calibrated.json +332 -332
  64. package/app/core/publish/flows-qimao-calibrated.json +7 -3
  65. package/app/core/publish/ledger.py +203 -203
  66. package/app/core/publish/manager.py +692 -692
  67. package/app/core/publish/qimao.py +141 -141
  68. package/app/core/publish/ws.py +139 -139
  69. package/app/core/redact.py +38 -38
  70. package/app/core/repeat_guard.py +129 -129
  71. package/app/core/router.py +398 -398
  72. package/app/core/runner.py +2067 -2065
  73. package/app/core/selfupdate.py +384 -384
  74. package/app/core/session_log.py +161 -161
  75. package/app/core/sessions.py +312 -312
  76. package/app/core/settings.py +149 -149
  77. package/app/core/settings_schema.py +262 -262
  78. package/app/core/share_page.py +114 -114
  79. package/app/core/skill_scan.py +122 -114
  80. package/app/core/skillpacks/fanqie-novel.md +80 -80
  81. package/app/core/skillpacks/market/character-bible.md +66 -66
  82. package/app/core/skillpacks/market/code-risk-checklist.md +58 -58
  83. package/app/core/skillpacks/market/git-workflow.md +57 -57
  84. package/app/core/skillpacks/market/release-notes.md +72 -72
  85. package/app/core/skillpacks/market/weekly-report.md +71 -71
  86. package/app/core/skillpacks/market/worldview-consistency.md +70 -70
  87. package/app/core/skills.py +15 -1
  88. package/app/core/step_runner.py +132 -132
  89. package/app/core/store.py +75 -34
  90. package/app/core/task_compile.py +199 -199
  91. package/app/core/telemetry.py +292 -292
  92. package/app/core/tlsctx.py +64 -64
  93. package/app/core/token_meter.py +147 -147
  94. package/app/core/tracing.py +144 -0
  95. package/app/core/upstream.py +41 -41
  96. package/app/core/usage.py +862 -858
  97. package/app/core/volumes.py +389 -389
  98. package/app/core/wxdigest.py +858 -858
  99. package/app/core/zentao.py +2465 -2465
  100. package/app/main.py +2981 -2957
  101. package/app/pick_dialog.py +110 -110
  102. package/app/ui/app.js +14399 -14384
  103. package/app/ui/artdiff.js +74 -74
  104. package/app/ui/board.html +162 -162
  105. package/app/ui/board.js +305 -305
  106. package/app/ui/i18n.js +2770 -2766
  107. package/app/ui/icons/bee.svg +79 -79
  108. package/app/ui/manifest.json +16 -16
  109. package/app/ui/qrcode.js +2297 -2297
  110. package/app/ui/style.css +16 -16
  111. package/bin/tutti.js +147 -147
  112. package/package.json +46 -46
  113. package/tools/wx_reader.py +261 -261
@@ -1,138 +1,138 @@
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 "(用户未指定方向,按大盘热门分析)"))
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"
@@ -829,6 +829,7 @@ __CONTEXT__
829
829
  不要自造评分标准或质量闸门;超出任务目标的「顺手加固」一律不做。
830
830
  真实风险照样要找:发现就写进总结,由人决定要不要修。
831
831
  - 完成后只用 3-5 句话总结你改了什么、为什么。
832
+ - 交付物从简:回复不写开场白、不复述任务、不堆与改动无关的解释——代码、说明、总结直接给干货(借鉴 claude-token-efficient:输出 token 也是真金白银)。
832
833
  __VERIFY_HINT__"""
833
834
 
834
835
  CODE_FIX_PROMPT = """你是一名高级工程师。你之前的实现没有通过验收,请在当前工作目录中直接修复(直接修改/创建文件)。创建/写入的文件一律以 UTF-8 编码保存(PowerShell 显式加 -Encoding UTF8,禁止依赖系统默认编码)。
@@ -2088,6 +2089,10 @@ CONTENT_DELIVERY_CONTRACTS = {
2088
2089
  "novel": ("小说作者", [
2089
2090
  "遵守用户给定的题材、篇幅、视角和风格;未给出的核心设定不要擅自扩张。",
2090
2091
  "用场景、行动和对话推进冲突,人物动机与前后因果保持一致。",
2092
+ # Madman-Architect-Carpenter 写作框架(writing-with-agents 借鉴;Judge=评审链已有):
2093
+ # 先发散倾倒念头再组织结构成文,不要边写边自我审查把点子掐死在半路
2094
+ "成文分三步:先把不设限的情节/画面/台词念头快速倾倒成素材清单,再挑出主线组织成"
2095
+ "结构,最后按结构成文——初稿期不做质量审判。",
2091
2096
  ]),
2092
2097
  "article": ("平台内容主编", [
2093
2098
  "标题、开头钩子、正文层级和结尾行动建议要适配目标平台与读者。",
@@ -2129,6 +2134,9 @@ CONTENT_DELIVERY_CONTRACTS = {
2129
2134
  # sepia 分场合规则(PR 回复体裁):先答再铺陈;篇幅与利害成正比
2130
2135
  "第一句/第一段先给结论或答复(对方要做什么、答应还是不答应),再给必要背景。",
2131
2136
  "请求具体到动作与截止时间;篇幅与事情轻重成正比,删掉礼节性空话与自我表扬。",
2137
+ # agentic-inbox 借鉴(线程级交付契约):回复是对话不是独白
2138
+ "回复往来邮件时逐条回应对方提出的每个问题与请求(可用清单),不漏问、不回避;"
2139
+ "行动项落到具体动作+负责人+截止时间。",
2132
2140
  ]),
2133
2141
  "tech_proposal": ("解决方案架构师", [
2134
2142
  "覆盖现状与目标、约束、候选方案对比、推荐架构、实施阶段、风险与回滚、验收指标。",
@@ -2167,7 +2175,9 @@ def _content_contract(task):
2167
2175
  return "\n\n## 本类型交付约束\n" + "\n".join("- " + item for item in spec[1])
2168
2176
 
2169
2177
  # 调研报告类稿件的追加要求(借鉴 gpt-researcher 迭代深研):有网络/读文件工具时
2170
- # 多源交叉验证,单源结论降权——调研的可信度来自证据链而非文采
2178
+ # 多源交叉验证,单源结论降权——调研的可信度来自证据链而非文采。
2179
+ # 来源冲突裁决借鉴 deepresearch-agent 记忆层矛盾检测(Majority/Source-Weight 消解);
2180
+ # 子问题缺口补查借鉴 dzhng/deep-research 迭代循环
2171
2181
  RESEARCH_APPENDIX = """
2172
2182
 
2173
2183
  ## 调研要求(证据链)
@@ -2175,6 +2185,11 @@ RESEARCH_APPENDIX = """
2175
2185
  单源信息要标注「仅单一来源」。
2176
2186
  - 引用来源在文中用行内链接或脚注标明(域名即可,不编造 URL)。
2177
2187
  - 区分「事实」与「观点」:数据/时间/版本号给来源,预测/评价标明是分析。
2188
+ - 来源冲突显式裁决:同一事实多个来源说法不一致时,写明分歧点与各方依据,给出
2189
+ 倾向判断和理由(多数来源一致取多数,权威差异按来源可信度加权)——不各说一半,
2190
+ 也不悄悄只取其一。
2191
+ - 缺口驱动补查:起草前先列出报告要回答的子问题清单,逐个核对证据是否到位;
2192
+ 没找到答案的子问题明确标注「证据不足」,不用泛泛而谈填空。
2178
2193
  - 结构硬性要求:报告第一段必须是「**核心结论**」三行以内的要点摘要(结论先行),
2179
2194
  之后才展开分层论证 → 风险与局限(说明哪些结论证据不足)。"""
2180
2195
 
@@ -140,6 +140,8 @@ hard 也不做任务没要的防御性扩展子任务(缓存/重试/监控/通
140
140
  拆解之前先用你的读文件/搜索工具**实际查看工作目录**,找到目标相关的真实文件与函数,再据此拆解。
141
141
  detail 必须点名真实存在的文件路径;files 列出该子任务会改动的文件(新建的写目标路径)。
142
142
  **没探索过就不要凭想象编路径**——计划的可执行性取决于它对真实代码库的贴合度。
143
+ detail 的清晰度按「初级工程师测试」校验:假设执行者没有项目上下文、不做隐含判断,
144
+ 只读 detail 就能照做不跑偏(借鉴 superpowers)。
143
145
  若工作目录为空(全新项目),files 写计划新建的文件路径。
144
146
 
145
147
  ## 开发目标
@@ -1,111 +1,111 @@
1
- # -*- coding: utf-8 -*-
2
- """启动端口清场(用户拍板 2026-09-21):端口被占时先清场再启动。
3
-
4
- - **自家旧实例**(命令行含本包 main.py 完整路径,或 npm 包内 app/main.py)
5
- → 杀树(TerminateProcess 直杀)——升级/重启最常见的占用者就是没退干净
6
- 的 CodeBee 自己;控制台进程对温和信号(WM_CLOSE)无反应,温和关不掉
7
- 正是用户「旧进程太难杀」的痛点,所以自家实例直接强杀。
8
- - **别人的进程** → 只报告占用者,不发送任何信号;启动流程不得替用户
9
- 关闭可能正在开发中的服务。
10
- - 系统/自身进程拒关(portscan 内置护栏)。
11
- """
12
- from __future__ import annotations
13
-
14
- import logging
15
- import os
16
- import shlex
17
- import subprocess
18
-
19
- from . import portscan, runner
20
-
21
- log = logging.getLogger(__name__)
22
-
23
- PS_EXE = "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe"
24
-
25
-
26
- def proc_cmdline(pid):
27
- """读进程命令行;失败返回空串。仅用于确认旧 CodeBee 实例身份。"""
28
- try:
29
- pid = int(pid)
30
- except (TypeError, ValueError):
31
- return ""
32
- if pid <= 0:
33
- return ""
34
- if os.name != "nt":
35
- try:
36
- with open("/proc/%d/cmdline" % pid, "rb") as fh:
37
- return fh.read().decode("utf-8", "replace").replace("\x00", " ").strip()
38
- except Exception:
39
- return ""
40
- try:
41
- r = subprocess.run(
42
- [PS_EXE, "-NoProfile", "-Command",
43
- "(Get-CimInstance Win32_Process -Filter 'ProcessId = %d').CommandLine" % pid],
44
- capture_output=True, timeout=10)
45
- return r.stdout.decode("utf-8", "replace").strip() if r.returncode == 0 else ""
46
- except Exception:
47
- return ""
48
-
49
-
50
- def _command_tokens(cmdline):
51
- try:
52
- return [token.strip().strip('"').strip("'")
53
- for token in shlex.split(str(cmdline or ""), posix=False)]
54
- except (TypeError, ValueError):
55
- return []
56
-
57
-
58
- def is_own_instance(cmdline, main_script, port=None):
59
- """按启动器、脚本独立参数和端口精确确认 CodeBee 实例。"""
60
- tokens = _command_tokens(cmdline)
61
- if len(tokens) < 2:
62
- return False
63
- launcher = os.path.splitext(os.path.basename(tokens[0]))[0].lower()
64
- if launcher not in ("python", "python3", "py"):
65
- return False
66
- script = ""
67
- for token in tokens[1:]:
68
- if token.startswith("-"):
69
- continue
70
- script = token
71
- break
72
- if not script or not os.path.isabs(script):
73
- return False
74
- actual = os.path.normcase(os.path.normpath(os.path.abspath(script)))
75
- expected = os.path.normcase(os.path.normpath(os.path.abspath(str(main_script))))
76
- packaged = actual.replace("\\", "/").lower().endswith(
77
- "/node_modules/codebee/app/main.py")
78
- if actual != expected and not packaged:
79
- return False
80
- if port is None:
81
- return True
82
- try:
83
- wanted = str(int(port))
84
- except (TypeError, ValueError):
85
- return False
86
- explicit_port = None
87
- for i, token in enumerate(tokens):
88
- if token == "--port" and i + 1 < len(tokens):
89
- explicit_port = tokens[i + 1]
90
- elif token.startswith("--port="):
91
- explicit_port = token.split("=", 1)[1]
92
- # argparse 默认端口可不出现在旧实例参数中;非默认端口必须显式相符。
93
- return ((explicit_port == wanted) if explicit_port is not None
94
- else wanted == "8765")
95
-
96
-
97
- def clear_stale_port(port, main_script):
98
- """清掉占用端口的进程。返回 (是否清掉, 人话说明)。"""
99
- holders = [p for p in portscan.listening_ports() if p.get("port") == int(port)]
100
- if not holders:
101
- return True, ""
102
- pid = int(holders[0].get("pid") or 0)
103
- if pid <= 4 or pid == os.getpid():
104
- return False, "占用者是系统进程/自身,拒绝清理"
105
- if is_own_instance(proc_cmdline(pid), main_script, port=port):
106
- try:
107
- runner._kill_tree(pid)
108
- return True, "已结束旧实例 PID %d" % pid
109
- except Exception as e:
110
- return False, "旧实例 PID %d 清理失败 %s" % (pid, str(e)[:80])
111
- return False, "占用者非 CodeBee(PID %d),未自动关闭" % pid
1
+ # -*- coding: utf-8 -*-
2
+ """启动端口清场(用户拍板 2026-09-21):端口被占时先清场再启动。
3
+
4
+ - **自家旧实例**(命令行含本包 main.py 完整路径,或 npm 包内 app/main.py)
5
+ → 杀树(TerminateProcess 直杀)——升级/重启最常见的占用者就是没退干净
6
+ 的 CodeBee 自己;控制台进程对温和信号(WM_CLOSE)无反应,温和关不掉
7
+ 正是用户「旧进程太难杀」的痛点,所以自家实例直接强杀。
8
+ - **别人的进程** → 只报告占用者,不发送任何信号;启动流程不得替用户
9
+ 关闭可能正在开发中的服务。
10
+ - 系统/自身进程拒关(portscan 内置护栏)。
11
+ """
12
+ from __future__ import annotations
13
+
14
+ import logging
15
+ import os
16
+ import shlex
17
+ import subprocess
18
+
19
+ from . import portscan, runner
20
+
21
+ log = logging.getLogger(__name__)
22
+
23
+ PS_EXE = "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe"
24
+
25
+
26
+ def proc_cmdline(pid):
27
+ """读进程命令行;失败返回空串。仅用于确认旧 CodeBee 实例身份。"""
28
+ try:
29
+ pid = int(pid)
30
+ except (TypeError, ValueError):
31
+ return ""
32
+ if pid <= 0:
33
+ return ""
34
+ if os.name != "nt":
35
+ try:
36
+ with open("/proc/%d/cmdline" % pid, "rb") as fh:
37
+ return fh.read().decode("utf-8", "replace").replace("\x00", " ").strip()
38
+ except Exception:
39
+ return ""
40
+ try:
41
+ r = subprocess.run(
42
+ [PS_EXE, "-NoProfile", "-Command",
43
+ "(Get-CimInstance Win32_Process -Filter 'ProcessId = %d').CommandLine" % pid],
44
+ capture_output=True, timeout=10)
45
+ return r.stdout.decode("utf-8", "replace").strip() if r.returncode == 0 else ""
46
+ except Exception:
47
+ return ""
48
+
49
+
50
+ def _command_tokens(cmdline):
51
+ try:
52
+ return [token.strip().strip('"').strip("'")
53
+ for token in shlex.split(str(cmdline or ""), posix=False)]
54
+ except (TypeError, ValueError):
55
+ return []
56
+
57
+
58
+ def is_own_instance(cmdline, main_script, port=None):
59
+ """按启动器、脚本独立参数和端口精确确认 CodeBee 实例。"""
60
+ tokens = _command_tokens(cmdline)
61
+ if len(tokens) < 2:
62
+ return False
63
+ launcher = os.path.splitext(os.path.basename(tokens[0]))[0].lower()
64
+ if launcher not in ("python", "python3", "py"):
65
+ return False
66
+ script = ""
67
+ for token in tokens[1:]:
68
+ if token.startswith("-"):
69
+ continue
70
+ script = token
71
+ break
72
+ if not script or not os.path.isabs(script):
73
+ return False
74
+ actual = os.path.normcase(os.path.normpath(os.path.abspath(script)))
75
+ expected = os.path.normcase(os.path.normpath(os.path.abspath(str(main_script))))
76
+ packaged = actual.replace("\\", "/").lower().endswith(
77
+ "/node_modules/codebee/app/main.py")
78
+ if actual != expected and not packaged:
79
+ return False
80
+ if port is None:
81
+ return True
82
+ try:
83
+ wanted = str(int(port))
84
+ except (TypeError, ValueError):
85
+ return False
86
+ explicit_port = None
87
+ for i, token in enumerate(tokens):
88
+ if token == "--port" and i + 1 < len(tokens):
89
+ explicit_port = tokens[i + 1]
90
+ elif token.startswith("--port="):
91
+ explicit_port = token.split("=", 1)[1]
92
+ # argparse 默认端口可不出现在旧实例参数中;非默认端口必须显式相符。
93
+ return ((explicit_port == wanted) if explicit_port is not None
94
+ else wanted == "8765")
95
+
96
+
97
+ def clear_stale_port(port, main_script):
98
+ """清掉占用端口的进程。返回 (是否清掉, 人话说明)。"""
99
+ holders = [p for p in portscan.listening_ports() if p.get("port") == int(port)]
100
+ if not holders:
101
+ return True, ""
102
+ pid = int(holders[0].get("pid") or 0)
103
+ if pid <= 4 or pid == os.getpid():
104
+ return False, "占用者是系统进程/自身,拒绝清理"
105
+ if is_own_instance(proc_cmdline(pid), main_script, port=port):
106
+ try:
107
+ runner._kill_tree(pid)
108
+ return True, "已结束旧实例 PID %d" % pid
109
+ except Exception as e:
110
+ return False, "旧实例 PID %d 清理失败 %s" % (pid, str(e)[:80])
111
+ return False, "占用者非 CodeBee(PID %d),未自动关闭" % pid