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.
Files changed (110) hide show
  1. package/CHANGELOG.md +520 -513
  2. package/LICENSE +21 -21
  3. package/README.md +494 -487
  4. package/app/core/aiflavor.py +197 -197
  5. package/app/core/artifacts.py +148 -148
  6. package/app/core/attachments.py +532 -532
  7. package/app/core/automation.py +758 -758
  8. package/app/core/backup.py +470 -470
  9. package/app/core/beekeeper.py +183 -183
  10. package/app/core/board.py +209 -209
  11. package/app/core/bookmeta.py +796 -759
  12. package/app/core/bookmeta_catalog.py +180 -180
  13. package/app/core/branching.py +137 -137
  14. package/app/core/builtin_agent.py +1234 -1191
  15. package/app/core/capability.py +144 -144
  16. package/app/core/catalog.py +23 -24
  17. package/app/core/chaptersafe.py +32 -32
  18. package/app/core/cleanup.py +303 -303
  19. package/app/core/compaction.py +201 -186
  20. package/app/core/covergen.py +356 -356
  21. package/app/core/diagnostics.py +114 -114
  22. package/app/core/dispatch.py +173 -173
  23. package/app/core/dispatch_log.py +113 -113
  24. package/app/core/env_scrub.py +85 -85
  25. package/app/core/error_codes.py +220 -65
  26. package/app/core/errorlog.py +137 -137
  27. package/app/core/evaluation.py +174 -0
  28. package/app/core/failure_notify.py +290 -0
  29. package/app/core/flows.py +355 -355
  30. package/app/core/goal_service.py +167 -167
  31. package/app/core/health.py +374 -342
  32. package/app/core/history.py +38 -38
  33. package/app/core/hooks.py +162 -156
  34. package/app/core/jobs.py +1034 -1022
  35. package/app/core/knowledge.py +6 -2
  36. package/app/core/manager.py +2115 -1996
  37. package/app/core/market.py +362 -362
  38. package/app/core/market_remote.py +66 -66
  39. package/app/core/mocks.py +64 -64
  40. package/app/core/modelhub.py +3517 -3332
  41. package/app/core/notify.py +106 -106
  42. package/app/core/paihang.py +109 -109
  43. package/app/core/paths.py +2 -2
  44. package/app/core/pipeline.py +4614 -4494
  45. package/app/core/planner.py +33 -31
  46. package/app/core/portguard.py +111 -111
  47. package/app/core/preview.py +293 -293
  48. package/app/core/publish/__init__.py +7 -7
  49. package/app/core/publish/auto.py +367 -367
  50. package/app/core/publish/browser.py +533 -533
  51. package/app/core/publish/fanqie.py +128 -128
  52. package/app/core/publish/flow.py +432 -406
  53. package/app/core/publish/flows-fanqie-calibrated.json +17 -9
  54. package/app/core/publish/flows-qimao-calibrated.json +6 -2
  55. package/app/core/publish/ledger.py +198 -198
  56. package/app/core/publish/manager.py +623 -590
  57. package/app/core/publish/qimao.py +141 -141
  58. package/app/core/publish/ws.py +139 -139
  59. package/app/core/redact.py +38 -38
  60. package/app/core/repeat_guard.py +129 -124
  61. package/app/core/router.py +119 -81
  62. package/app/core/runner.py +2050 -2042
  63. package/app/core/selfupdate.py +384 -384
  64. package/app/core/session_log.py +161 -161
  65. package/app/core/sessions.py +312 -312
  66. package/app/core/settings.py +149 -149
  67. package/app/core/settings_schema.py +262 -262
  68. package/app/core/share_page.py +114 -114
  69. package/app/core/skill_scan.py +114 -106
  70. package/app/core/skillpacks/fanqie-novel.md +80 -80
  71. package/app/core/skillpacks/market/character-bible.md +66 -66
  72. package/app/core/skillpacks/market/code-risk-checklist.md +58 -58
  73. package/app/core/skillpacks/market/git-workflow.md +57 -57
  74. package/app/core/skillpacks/market/release-notes.md +72 -72
  75. package/app/core/skillpacks/market/weekly-report.md +71 -71
  76. package/app/core/skillpacks/market/worldview-consistency.md +70 -70
  77. package/app/core/skills.py +221 -72
  78. package/app/core/step_runner.py +132 -132
  79. package/app/core/store.py +23 -0
  80. package/app/core/task_compile.py +199 -199
  81. package/app/core/telemetry.py +292 -292
  82. package/app/core/tlsctx.py +64 -64
  83. package/app/core/token_meter.py +147 -147
  84. package/app/core/upstream.py +41 -0
  85. package/app/core/usage.py +114 -3
  86. package/app/core/volumes.py +355 -355
  87. package/app/core/wxdigest.py +858 -858
  88. package/app/core/zentao.py +2447 -2447
  89. package/app/main.py +2838 -2832
  90. package/app/pick_dialog.py +110 -110
  91. package/app/ui/app.js +13994 -13977
  92. package/app/ui/board.css +20 -41
  93. package/app/ui/board.html +162 -162
  94. package/app/ui/board.js +305 -305
  95. package/app/ui/i18n.js +2716 -2711
  96. package/app/ui/icons/bee.svg +79 -79
  97. package/app/ui/index.html +1 -1
  98. package/app/ui/manifest.json +16 -16
  99. package/app/ui/qrcode.js +2297 -2297
  100. package/app/ui/style.css +6107 -6125
  101. package/bin/tutti.js +147 -147
  102. package/package.json +46 -41
  103. package/tools/wx_reader.py +261 -261
  104. package/app/.playwright-cli/console-2026-09-23T08-26-03-635Z.log +0 -88
  105. package/app/.playwright-cli/page-2026-09-23T08-26-03-845Z.yml +0 -126
  106. package/app/.playwright-cli/page-2026-09-23T08-29-44-203Z.png +0 -0
  107. package/app/.playwright-cli/page-2026-09-23T08-35-42-046Z.yml +0 -210
  108. package/app/.playwright-cli/page-2026-09-23T08-36-52-858Z.png +0 -0
  109. package/app/.playwright-cli/page-2026-09-23T08-37-33-938Z.png +0 -0
  110. package/app/.playwright-cli/run-detail-after.png +0 -0
@@ -30,10 +30,10 @@ import subprocess
30
30
  import tarfile
31
31
  import tempfile
32
32
  import time
33
- import urllib.error
34
- import urllib.request
35
- import urllib.parse
36
- import zipfile
33
+ import urllib.error
34
+ import urllib.request
35
+ import urllib.parse
36
+ import zipfile
37
37
  from pathlib import Path
38
38
  from urllib.parse import urlparse
39
39
 
@@ -94,8 +94,8 @@ _CAP_DOWNLOAD = 80 * 1024 * 1024
94
94
  _CAP_UNPACKED = 120 * 1024 * 1024
95
95
  _CAP_FILES = 500
96
96
  _CAP_FILE_TEXT = 512 * 1024
97
- _CAP_TOTAL_TEXT = 4 * 1024 * 1024
98
- _MAX_REDIRECTS = 3
97
+ _CAP_TOTAL_TEXT = 4 * 1024 * 1024
98
+ _MAX_REDIRECTS = 3
99
99
 
100
100
 
101
101
  def _cache_dir():
@@ -132,52 +132,52 @@ def assert_public_url(url):
132
132
  return host, port
133
133
 
134
134
 
135
- def _fetch(url, cap=_CAP_MANIFEST):
135
+ def _fetch(url, cap=_CAP_MANIFEST):
136
136
  """带体量上限的 https GET;返回 bytes。
137
137
 
138
138
  两段式网络策略:先走默认通道(Windows 上 urllib 会读注册表系统代理,
139
139
  依赖代理上网的环境靠它),失败再自动直连重试一次(实测本机代理对部分
140
140
  CDN 文件返回 404/篡改,直连正常;反过来需要代理的网络第一段就成功)。
141
141
  体量超限属确定性错误,不重试。"""
142
- assert_public_url(url)
143
-
144
- class _NoRedirect(urllib.request.HTTPRedirectHandler):
145
- def redirect_request(self, req, fp, code, msg, headers, newurl):
146
- return None
147
-
148
- def _close_quietly(resp):
149
- try:
150
- resp.close()
151
- except Exception:
152
- pass
153
-
154
- def _open(opener, target):
155
- """逐跳 GET;重定向目标在发起下一跳前重新过 SSRF 网关。"""
156
- current = target
157
- for hop in range(_MAX_REDIRECTS + 1):
158
- assert_public_url(current)
159
- req = urllib.request.Request(current, headers={
160
- "User-Agent": "CodeBee-Market/1.0",
161
- "Accept": "*/*",
162
- })
163
- try:
164
- resp = opener.open(req, timeout=30)
165
- code = getattr(resp, "status", getattr(resp, "code", 200))
166
- except urllib.error.HTTPError as e:
167
- code, resp = e.code, e
168
- if code not in (301, 302, 303, 307, 308):
169
- if isinstance(resp, urllib.error.HTTPError):
170
- raise resp
171
- return resp
172
- location = resp.headers.get("Location")
173
- if not location:
174
- _close_quietly(resp)
175
- raise ValueError("重定向缺少 Location: %s" % current)
176
- _close_quietly(resp)
177
- current = urllib.parse.urljoin(current, location)
178
- if hop >= _MAX_REDIRECTS:
179
- raise ValueError("重定向次数超过上限: %s" % target)
180
- raise ValueError("重定向失败: %s" % target)
142
+ assert_public_url(url)
143
+
144
+ class _NoRedirect(urllib.request.HTTPRedirectHandler):
145
+ def redirect_request(self, req, fp, code, msg, headers, newurl):
146
+ return None
147
+
148
+ def _close_quietly(resp):
149
+ try:
150
+ resp.close()
151
+ except Exception:
152
+ pass
153
+
154
+ def _open(opener, target):
155
+ """逐跳 GET;重定向目标在发起下一跳前重新过 SSRF 网关。"""
156
+ current = target
157
+ for hop in range(_MAX_REDIRECTS + 1):
158
+ assert_public_url(current)
159
+ req = urllib.request.Request(current, headers={
160
+ "User-Agent": "CodeBee-Market/1.0",
161
+ "Accept": "*/*",
162
+ })
163
+ try:
164
+ resp = opener.open(req, timeout=30)
165
+ code = getattr(resp, "status", getattr(resp, "code", 200))
166
+ except urllib.error.HTTPError as e:
167
+ code, resp = e.code, e
168
+ if code not in (301, 302, 303, 307, 308):
169
+ if isinstance(resp, urllib.error.HTTPError):
170
+ raise resp
171
+ return resp
172
+ location = resp.headers.get("Location")
173
+ if not location:
174
+ _close_quietly(resp)
175
+ raise ValueError("重定向缺少 Location: %s" % current)
176
+ _close_quietly(resp)
177
+ current = urllib.parse.urljoin(current, location)
178
+ if hop >= _MAX_REDIRECTS:
179
+ raise ValueError("重定向次数超过上限: %s" % target)
180
+ raise ValueError("重定向失败: %s" % target)
181
181
 
182
182
  def _read(open_fn):
183
183
  with open_fn() as resp:
@@ -192,17 +192,17 @@ def _fetch(url, cap=_CAP_MANIFEST):
192
192
  chunks.append(chunk)
193
193
  return b"".join(chunks)
194
194
 
195
- try:
196
- # 默认 opener 仍读取系统代理,但显式禁用自动重定向。
197
- opener = urllib.request.build_opener(
198
- _NoRedirect(), urllib.request.HTTPSHandler(context=tlsctx.context()))
199
- return _read(lambda: _open(opener, url))
200
- except (urllib.error.HTTPError, urllib.error.URLError):
201
- # 直连重试:ProxyHandler({}) 显式清空代理;每一跳仍经过 SSRF 校验。
202
- opener = urllib.request.build_opener(
203
- _NoRedirect(), urllib.request.ProxyHandler({}),
204
- urllib.request.HTTPSHandler(context=tlsctx.context()))
205
- return _read(lambda: _open(opener, url))
195
+ try:
196
+ # 默认 opener 仍读取系统代理,但显式禁用自动重定向。
197
+ opener = urllib.request.build_opener(
198
+ _NoRedirect(), urllib.request.HTTPSHandler(context=tlsctx.context()))
199
+ return _read(lambda: _open(opener, url))
200
+ except (urllib.error.HTTPError, urllib.error.URLError):
201
+ # 直连重试:ProxyHandler({}) 显式清空代理;每一跳仍经过 SSRF 校验。
202
+ opener = urllib.request.build_opener(
203
+ _NoRedirect(), urllib.request.ProxyHandler({}),
204
+ urllib.request.HTTPSHandler(context=tlsctx.context()))
205
+ return _read(lambda: _open(opener, url))
206
206
 
207
207
 
208
208
  # ---------------------------------------------------------------- 清单解析
@@ -810,22 +810,22 @@ def _gh_fetch_files(entry, tmp):
810
810
  raise ValueError("jsdelivr 拉取失败: %s/%s(%s)" % (owner, repo, last_err))
811
811
 
812
812
 
813
- def _download_git(entry, tmp):
813
+ def _download_git(entry, tmp):
814
814
  """git 浅克隆取子目录(Anthropic 生态的 git-subdir 来源)。"""
815
815
  if shutil.which("git") is None:
816
816
  raise ValueError("本机没有 git,无法安装 git-subdir 来源的插件")
817
- inst = entry["install"]
818
- # 非 GitHub 来源没有 codeload/jsdelivr 的安全收口,必须在 clone 前
819
- # 复用同一公网 HTTPS 网关,避免 git 自己跟随重定向或访问内网。
820
- assert_public_url(inst.get("url") or "")
817
+ inst = entry["install"]
818
+ # 非 GitHub 来源没有 codeload/jsdelivr 的安全收口,必须在 clone 前
819
+ # 复用同一公网 HTTPS 网关,避免 git 自己跟随重定向或访问内网。
820
+ assert_public_url(inst.get("url") or "")
821
821
  dst = tmp / "git"
822
822
  sub = str(inst.get("path") or "").replace("\\", "/")
823
823
  if sub.startswith("/") or ":" in sub or ".." in _rel_parts(sub):
824
824
  raise ValueError("插件子目录路径可疑: %s" % inst.get("path"))
825
- # git 会自行跟随 HTTP 重定向,而重定向目标无法再经过 assert_public_url;
826
- # ��装来源宁可明确失败,也不能让 clone 跳进内网。
827
- cmd = ["git", "-c", "http.followRedirects=false", "clone",
828
- "--depth", "1", "--single-branch", "--quiet"]
825
+ # git 会自行跟随 HTTP 重定向,而重定向目标无法再经过 assert_public_url;
826
+ # ��装来源宁可明确失败,也不能让 clone 跳进内网。
827
+ cmd = ["git", "-c", "http.followRedirects=false", "clone",
828
+ "--depth", "1", "--single-branch", "--quiet"]
829
829
  if inst.get("ref"):
830
830
  cmd += ["--branch", inst["ref"]]
831
831
  cmd += [inst["url"], str(dst)]
package/app/core/mocks.py CHANGED
@@ -1,64 +1,64 @@
1
- # -*- coding: utf-8 -*-
2
- """内置演示/测试智能体(mock):不消耗任何配额即可跑通全流程。"""
3
- from __future__ import annotations
4
-
5
- import hashlib
6
-
7
-
8
- def _seed(agent_id):
9
- return int(hashlib.sha256(agent_id.encode("utf-8")).hexdigest()[:6], 16)
10
-
11
-
12
- def draft_manuscript(task, round_no):
13
- """生成一份示例稿件(mock 起草/修订直接落盘的内容)。"""
14
- title = task.get("title") or "未命名章节"
15
- goal = task.get("goal") or ""
16
- return (
17
- "# {title}\n\n"
18
- "这是第 {r} 轮 mock 草稿(演示用,不消耗模型配额)。\n\n"
19
- "## 任务目标\n{goal}\n\n"
20
- "## 正文\n"
21
- "夜色沿着山脊线缓慢退去,主角终于看清了石门上的刻痕——那不是文字,"
22
- "而是一段被反复涂抹又反复显影的名字。他伸手触碰的瞬间,耳边响起的"
23
- "却是自己昨夜说过的话。{extra}\n\n"
24
- "(本轮修订关注:{focus})\n"
25
- ).format(
26
- title=title, r=round_no, goal=goal,
27
- extra="这一段在第 2 轮修订中补充了人物动机的伏笔。" if round_no >= 2 else "",
28
- focus="节奏与悬念铺排" if round_no == 1 else "人物弧光与首尾呼应",
29
- )
30
-
31
-
32
- def implement_note(task):
33
- return "(mock 实现)已在目标目录写入 mock-impl.txt 作为变更占位。"
34
-
35
-
36
- def critique(agent_id, round_no, dims, threshold):
37
- """确定性评分:第 1 轮略低于阈值,第 2 轮达标,用于验证发布门禁逻辑。"""
38
- s = _seed(agent_id)
39
- scores = {}
40
- for i, d in enumerate(dims):
41
- jitter = ((s >> i) % 5) * 0.1
42
- scores[d] = round(min(9.5, 4.8 + 1.2 * round_no + jitter), 1)
43
- passed = all(v >= threshold for v in scores.values())
44
- issues = []
45
- if round_no == 1:
46
- issues = [
47
- {"dim": dims[0], "severity": "major", "note": "开篇信息密度偏低,钩子出现太晚(mock 意见)"},
48
- {"dim": dims[-1], "severity": "minor", "note": "段落长短交替不够,读感偏平(mock 意见)"},
49
- ]
50
- return {
51
- "scores": scores,
52
- "issues": issues,
53
- "summary": "mock 总评:第 %d 轮稿件%s发布阈值" % (round_no, "已达到" if passed else "尚未达到"),
54
- }
55
-
56
-
57
- def review(task, verify_passed):
58
- return {
59
- "pass": bool(verify_passed),
60
- "scores": {"正确性": 8.0 if verify_passed else 5.0, "可维护性": 8.0, "安全": 8.5},
61
- "issues": [] if verify_passed else [
62
- {"severity": "major", "title": "验证命令未通过", "detail": "请检查测试输出(mock 意见)"}],
63
- "summary": "mock 代码评审:%s" % ("通过" if verify_passed else "未通过"),
64
- }
1
+ # -*- coding: utf-8 -*-
2
+ """内置演示/测试智能体(mock):不消耗任何配额即可跑通全流程。"""
3
+ from __future__ import annotations
4
+
5
+ import hashlib
6
+
7
+
8
+ def _seed(agent_id):
9
+ return int(hashlib.sha256(agent_id.encode("utf-8")).hexdigest()[:6], 16)
10
+
11
+
12
+ def draft_manuscript(task, round_no):
13
+ """生成一份示例稿件(mock 起草/修订直接落盘的内容)。"""
14
+ title = task.get("title") or "未命名章节"
15
+ goal = task.get("goal") or ""
16
+ return (
17
+ "# {title}\n\n"
18
+ "这是第 {r} 轮 mock 草稿(演示用,不消耗模型配额)。\n\n"
19
+ "## 任务目标\n{goal}\n\n"
20
+ "## 正文\n"
21
+ "夜色沿着山脊线缓慢退去,主角终于看清了石门上的刻痕——那不是文字,"
22
+ "而是一段被反复涂抹又反复显影的名字。他伸手触碰的瞬间,耳边响起的"
23
+ "却是自己昨夜说过的话。{extra}\n\n"
24
+ "(本轮修订关注:{focus})\n"
25
+ ).format(
26
+ title=title, r=round_no, goal=goal,
27
+ extra="这一段在第 2 轮修订中补充了人物动机的伏笔。" if round_no >= 2 else "",
28
+ focus="节奏与悬念铺排" if round_no == 1 else "人物弧光与首尾呼应",
29
+ )
30
+
31
+
32
+ def implement_note(task):
33
+ return "(mock 实现)已在目标目录写入 mock-impl.txt 作为变更占位。"
34
+
35
+
36
+ def critique(agent_id, round_no, dims, threshold):
37
+ """确定性评分:第 1 轮略低于阈值,第 2 轮达标,用于验证发布门禁逻辑。"""
38
+ s = _seed(agent_id)
39
+ scores = {}
40
+ for i, d in enumerate(dims):
41
+ jitter = ((s >> i) % 5) * 0.1
42
+ scores[d] = round(min(9.5, 4.8 + 1.2 * round_no + jitter), 1)
43
+ passed = all(v >= threshold for v in scores.values())
44
+ issues = []
45
+ if round_no == 1:
46
+ issues = [
47
+ {"dim": dims[0], "severity": "major", "note": "开篇信息密度偏低,钩子出现太晚(mock 意见)"},
48
+ {"dim": dims[-1], "severity": "minor", "note": "段落长短交替不够,读感偏平(mock 意见)"},
49
+ ]
50
+ return {
51
+ "scores": scores,
52
+ "issues": issues,
53
+ "summary": "mock 总评:第 %d 轮稿件%s发布阈值" % (round_no, "已达到" if passed else "尚未达到"),
54
+ }
55
+
56
+
57
+ def review(task, verify_passed):
58
+ return {
59
+ "pass": bool(verify_passed),
60
+ "scores": {"正确性": 8.0 if verify_passed else 5.0, "可维护性": 8.0, "安全": 8.5},
61
+ "issues": [] if verify_passed else [
62
+ {"severity": "major", "title": "验证命令未通过", "detail": "请检查测试输出(mock 意见)"}],
63
+ "summary": "mock 代码评审:%s" % ("通过" if verify_passed else "未通过"),
64
+ }