codebee 0.1.62 → 0.1.64

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 (101) hide show
  1. package/CHANGELOG.md +507 -490
  2. package/LICENSE +21 -21
  3. package/README.md +487 -486
  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/aiflavor.py +197 -197
  12. package/app/core/artifacts.py +148 -0
  13. package/app/core/attachments.py +532 -532
  14. package/app/core/automation.py +758 -758
  15. package/app/core/backup.py +470 -470
  16. package/app/core/beekeeper.py +183 -183
  17. package/app/core/board.py +209 -209
  18. package/app/core/bookmeta.py +215 -19
  19. package/app/core/bookmeta_catalog.py +180 -180
  20. package/app/core/branching.py +137 -137
  21. package/app/core/builtin_agent.py +1191 -1191
  22. package/app/core/capability.py +144 -144
  23. package/app/core/catalog.py +19 -14
  24. package/app/core/chaptersafe.py +32 -32
  25. package/app/core/cleanup.py +303 -303
  26. package/app/core/compaction.py +186 -186
  27. package/app/core/covergen.py +356 -356
  28. package/app/core/diagnostics.py +114 -114
  29. package/app/core/dispatch.py +173 -173
  30. package/app/core/dispatch_log.py +113 -113
  31. package/app/core/env_scrub.py +85 -85
  32. package/app/core/error_codes.py +64 -64
  33. package/app/core/errorlog.py +137 -137
  34. package/app/core/flows.py +355 -355
  35. package/app/core/goal_service.py +167 -167
  36. package/app/core/health.py +341 -341
  37. package/app/core/history.py +38 -38
  38. package/app/core/hooks.py +156 -156
  39. package/app/core/jobs.py +1022 -1022
  40. package/app/core/manager.py +1996 -1996
  41. package/app/core/market.py +362 -362
  42. package/app/core/market_remote.py +66 -66
  43. package/app/core/mocks.py +64 -64
  44. package/app/core/modelhub.py +3332 -3266
  45. package/app/core/notify.py +106 -106
  46. package/app/core/paihang.py +109 -109
  47. package/app/core/paths.py +2 -2
  48. package/app/core/pipeline.py +4483 -4426
  49. package/app/core/planner.py +27 -27
  50. package/app/core/portguard.py +111 -111
  51. package/app/core/preview.py +293 -293
  52. package/app/core/publish/__init__.py +7 -7
  53. package/app/core/publish/auto.py +367 -367
  54. package/app/core/publish/browser.py +533 -533
  55. package/app/core/publish/fanqie.py +128 -128
  56. package/app/core/publish/flow.py +406 -406
  57. package/app/core/publish/ledger.py +198 -198
  58. package/app/core/publish/manager.py +590 -590
  59. package/app/core/publish/qimao.py +141 -141
  60. package/app/core/publish/ws.py +139 -139
  61. package/app/core/redact.py +38 -38
  62. package/app/core/repeat_guard.py +124 -124
  63. package/app/core/router.py +20 -3
  64. package/app/core/runner.py +2019 -1991
  65. package/app/core/selfupdate.py +384 -384
  66. package/app/core/session_log.py +161 -161
  67. package/app/core/sessions.py +312 -312
  68. package/app/core/settings.py +149 -149
  69. package/app/core/settings_schema.py +262 -262
  70. package/app/core/share_page.py +114 -114
  71. package/app/core/skill_scan.py +106 -106
  72. package/app/core/skillpacks/fanqie-novel.md +80 -80
  73. package/app/core/skillpacks/market/character-bible.md +66 -66
  74. package/app/core/skillpacks/market/code-risk-checklist.md +58 -58
  75. package/app/core/skillpacks/market/git-workflow.md +57 -57
  76. package/app/core/skillpacks/market/release-notes.md +72 -72
  77. package/app/core/skillpacks/market/weekly-report.md +71 -71
  78. package/app/core/skillpacks/market/worldview-consistency.md +70 -70
  79. package/app/core/step_runner.py +132 -132
  80. package/app/core/store.py +13 -70
  81. package/app/core/task_compile.py +199 -199
  82. package/app/core/telemetry.py +292 -292
  83. package/app/core/tlsctx.py +64 -64
  84. package/app/core/token_meter.py +147 -147
  85. package/app/core/volumes.py +355 -355
  86. package/app/core/wxdigest.py +858 -858
  87. package/app/core/zentao.py +2447 -2090
  88. package/app/main.py +2832 -2826
  89. package/app/pick_dialog.py +110 -110
  90. package/app/ui/app.js +13977 -13977
  91. package/app/ui/board.html +162 -162
  92. package/app/ui/board.js +305 -305
  93. package/app/ui/i18n.js +2711 -2711
  94. package/app/ui/icons/bee.svg +79 -79
  95. package/app/ui/index.html +1 -1
  96. package/app/ui/manifest.json +16 -16
  97. package/app/ui/qrcode.js +2297 -2297
  98. package/app/ui/style.css +6125 -6125
  99. package/bin/tutti.js +147 -147
  100. package/package.json +41 -41
  101. package/tools/wx_reader.py +261 -261
@@ -1,124 +1,124 @@
1
- # -*- coding: utf-8 -*-
2
- """重复 CLI 调用检测:防止编排者用同一份 prompt 反复调同一 CLI 死循环。
3
-
4
- 设计稿:docs/migration/01-defense-patterns.md §5C。
5
- 参考 dsh packages/guard/repeat-tool-reminder/src/index.ts:
6
- 监听 tools/post-execute,按 (tool, canonical_args) 跟踪,3/5/8 阈值注入 user-message 提醒。
7
-
8
- Tutti 的 "tool" 对应到 "step role","canonical_args" 对应到 prompt 模板指纹。
9
- 步骤级调用代价远高于普通工具调用,因此第 3 次提醒、第 5 次强制停止。
10
- """
11
- from __future__ import annotations
12
-
13
- import hashlib
14
- import logging
15
- import threading
16
- import time
17
-
18
- log = logging.getLogger(__name__)
19
-
20
- # 渐进阈值:第 N 次命中时给提醒;最后一次及以上时强制停止。
21
- DEFAULT_THRESHOLDS = (3, 5)
22
-
23
- REMINDER_TEMPLATE = (
24
- "⚠️ 提醒:上一步使用了几乎相同的输入(已连续 {count} 次调用 {role})。"
25
- "请换一种方法:读不同的文件、用更具体的指令、或明确询问用户澄清。"
26
- )
27
-
28
- STOP_TEMPLATE = (
29
- "已连续 {count} 次使用相同输入调用 {role},超过 {last_thr} 次阈值,"
30
- "强制停止本 step 以防止无限循环。请用户检查输入或调整 prompt 模板。"
31
- )
32
-
33
-
34
- def _prompt_fingerprint(prompt: str) -> str:
35
- """prompt 指纹:用前 200 字符 + 总长度(CLI prompt 头几行通常是模板部分)。
36
-
37
- 不做严格 canonical args 比对(CLI prompt 通常很长,且 dsh 也用 JSON.stringify);
38
- 指纹命中说明是同一个模板路径走过多次,已经够分辨。
39
-
40
- 用 SHA-256 取前 12 字符(96 bit):48 亿分之一碰撞概率对去重足够;不追求密码学强度。
41
- """
42
- head = (prompt or "")[:200]
43
- return hashlib.sha256(f"{head}|{len(prompt or '')}".encode("utf-8")).hexdigest()[:12]
44
-
45
-
46
- class RepeatGuard:
47
- """每 (run_id, step_role) 独立计数;key = prompt 指纹。
48
-
49
- 计数仅统计相同 key 连续出现;新 prompt 会重置。
50
- """
51
-
52
- def __init__(self, thresholds=DEFAULT_THRESHOLDS, *, max_history=50):
53
- self.thresholds = tuple(sorted(set(thresholds)))
54
- self.max_history = max_history
55
- self._lock = threading.Lock()
56
- # chain[(run_id, role)] = [(ts, fingerprint)]
57
- self._chains: dict = {}
58
-
59
- def check(self, run_id: str, role: str, prompt: str) -> dict:
60
- """检查并更新 (run_id, role) 的连续计数。
61
-
62
- Returns:
63
- {"count": int, "reminder": str | None, "should_stop": bool}
64
- """
65
- fp = _prompt_fingerprint(prompt)
66
- chain_key = (run_id, role)
67
- with self._lock:
68
- chain = self._chains.setdefault(chain_key, [])
69
- chain.append((time.time(), fp))
70
- # 仅统计相同 fp 连续出现
71
- count = 1
72
- for _, prev_fp in reversed(chain[:-1]):
73
- if prev_fp == fp:
74
- count += 1
75
- else:
76
- break
77
- # 截尾避免内存膨胀
78
- if len(chain) > self.max_history:
79
- self._chains[chain_key] = chain[-self.max_history:]
80
-
81
- reminder = None
82
- stop = False
83
- last_thr = self.thresholds[-1]
84
- if count >= last_thr:
85
- stop = True
86
- reminder = STOP_TEMPLATE.format(count=count, role=role, last_thr=last_thr)
87
- elif count in self.thresholds:
88
- reminder = REMINDER_TEMPLATE.format(count=count, role=role)
89
-
90
- if reminder:
91
- log.warning("repeat-guard %s/%s count=%d stop=%s reminder=%s",
92
- run_id, role, count, stop, reminder[:80])
93
- return {"count": count, "reminder": reminder, "should_stop": stop}
94
-
95
- def reset(self, run_id: str) -> int:
96
- """重置某 run_id 的所有 (run_id, *) 链。返回清除的链条数。"""
97
- with self._lock:
98
- cleared = 0
99
- for k in list(self._chains.keys()):
100
- if k[0] == run_id:
101
- del self._chains[k]
102
- cleared += 1
103
- return cleared
104
-
105
- def stats(self, run_id: str, role: str) -> dict:
106
- """读取 (run_id, role) 当前状态(不更新)。用于 UI 调试。"""
107
- chain_key = (run_id, role)
108
- with self._lock:
109
- chain = self._chains.get(chain_key, [])
110
- if not chain:
111
- return {"chain_len": 0, "last_fp": ""}
112
- # 当前连续计数
113
- last_fp = chain[-1][1]
114
- count = 1
115
- for _, fp in reversed(chain[:-1]):
116
- if fp == last_fp:
117
- count += 1
118
- else:
119
- break
120
- return {"chain_len": len(chain), "last_fp": last_fp, "count": count}
121
-
122
-
123
- # 模块级默认实例(pipeline 接线用;run_id 入键天然隔离多任务,测试无需替换)
124
- guard = RepeatGuard()
1
+ # -*- coding: utf-8 -*-
2
+ """重复 CLI 调用检测:防止编排者用同一份 prompt 反复调同一 CLI 死循环。
3
+
4
+ 设计稿:docs/migration/01-defense-patterns.md §5C。
5
+ 参考 dsh packages/guard/repeat-tool-reminder/src/index.ts:
6
+ 监听 tools/post-execute,按 (tool, canonical_args) 跟踪,3/5/8 阈值注入 user-message 提醒。
7
+
8
+ Tutti 的 "tool" 对应到 "step role","canonical_args" 对应到 prompt 模板指纹。
9
+ 步骤级调用代价远高于普通工具调用,因此第 3 次提醒、第 5 次强制停止。
10
+ """
11
+ from __future__ import annotations
12
+
13
+ import hashlib
14
+ import logging
15
+ import threading
16
+ import time
17
+
18
+ log = logging.getLogger(__name__)
19
+
20
+ # 渐进阈值:第 N 次命中时给提醒;最后一次及以上时强制停止。
21
+ DEFAULT_THRESHOLDS = (3, 5)
22
+
23
+ REMINDER_TEMPLATE = (
24
+ "⚠️ 提醒:上一步使用了几乎相同的输入(已连续 {count} 次调用 {role})。"
25
+ "请换一种方法:读不同的文件、用更具体的指令、或明确询问用户澄清。"
26
+ )
27
+
28
+ STOP_TEMPLATE = (
29
+ "已连续 {count} 次使用相同输入调用 {role},超过 {last_thr} 次阈值,"
30
+ "强制停止本 step 以防止无限循环。请用户检查输入或调整 prompt 模板。"
31
+ )
32
+
33
+
34
+ def _prompt_fingerprint(prompt: str) -> str:
35
+ """prompt 指纹:用前 200 字符 + 总长度(CLI prompt 头几行通常是模板部分)。
36
+
37
+ 不做严格 canonical args 比对(CLI prompt 通常很长,且 dsh 也用 JSON.stringify);
38
+ 指纹命中说明是同一个模板路径走过多次,已经够分辨。
39
+
40
+ 用 SHA-256 取前 12 字符(96 bit):48 亿分之一碰撞概率对去重足够;不追求密码学强度。
41
+ """
42
+ head = (prompt or "")[:200]
43
+ return hashlib.sha256(f"{head}|{len(prompt or '')}".encode("utf-8")).hexdigest()[:12]
44
+
45
+
46
+ class RepeatGuard:
47
+ """每 (run_id, step_role) 独立计数;key = prompt 指纹。
48
+
49
+ 计数仅统计相同 key 连续出现;新 prompt 会重置。
50
+ """
51
+
52
+ def __init__(self, thresholds=DEFAULT_THRESHOLDS, *, max_history=50):
53
+ self.thresholds = tuple(sorted(set(thresholds)))
54
+ self.max_history = max_history
55
+ self._lock = threading.Lock()
56
+ # chain[(run_id, role)] = [(ts, fingerprint)]
57
+ self._chains: dict = {}
58
+
59
+ def check(self, run_id: str, role: str, prompt: str) -> dict:
60
+ """检查并更新 (run_id, role) 的连续计数。
61
+
62
+ Returns:
63
+ {"count": int, "reminder": str | None, "should_stop": bool}
64
+ """
65
+ fp = _prompt_fingerprint(prompt)
66
+ chain_key = (run_id, role)
67
+ with self._lock:
68
+ chain = self._chains.setdefault(chain_key, [])
69
+ chain.append((time.time(), fp))
70
+ # 仅统计相同 fp 连续出现
71
+ count = 1
72
+ for _, prev_fp in reversed(chain[:-1]):
73
+ if prev_fp == fp:
74
+ count += 1
75
+ else:
76
+ break
77
+ # 截尾避免内存膨胀
78
+ if len(chain) > self.max_history:
79
+ self._chains[chain_key] = chain[-self.max_history:]
80
+
81
+ reminder = None
82
+ stop = False
83
+ last_thr = self.thresholds[-1]
84
+ if count >= last_thr:
85
+ stop = True
86
+ reminder = STOP_TEMPLATE.format(count=count, role=role, last_thr=last_thr)
87
+ elif count in self.thresholds:
88
+ reminder = REMINDER_TEMPLATE.format(count=count, role=role)
89
+
90
+ if reminder:
91
+ log.warning("repeat-guard %s/%s count=%d stop=%s reminder=%s",
92
+ run_id, role, count, stop, reminder[:80])
93
+ return {"count": count, "reminder": reminder, "should_stop": stop}
94
+
95
+ def reset(self, run_id: str) -> int:
96
+ """重置某 run_id 的所有 (run_id, *) 链。返回清除的链条数。"""
97
+ with self._lock:
98
+ cleared = 0
99
+ for k in list(self._chains.keys()):
100
+ if k[0] == run_id:
101
+ del self._chains[k]
102
+ cleared += 1
103
+ return cleared
104
+
105
+ def stats(self, run_id: str, role: str) -> dict:
106
+ """读取 (run_id, role) 当前状态(不更新)。用于 UI 调试。"""
107
+ chain_key = (run_id, role)
108
+ with self._lock:
109
+ chain = self._chains.get(chain_key, [])
110
+ if not chain:
111
+ return {"chain_len": 0, "last_fp": ""}
112
+ # 当前连续计数
113
+ last_fp = chain[-1][1]
114
+ count = 1
115
+ for _, fp in reversed(chain[:-1]):
116
+ if fp == last_fp:
117
+ count += 1
118
+ else:
119
+ break
120
+ return {"chain_len": len(chain), "last_fp": last_fp, "count": count}
121
+
122
+
123
+ # 模块级默认实例(pipeline 接线用;run_id 入键天然隔离多任务,测试无需替换)
124
+ guard = RepeatGuard()
@@ -264,13 +264,30 @@ def route_plan(agents, role, task_spec, stats=None, exclude=(), selected=None,
264
264
  if x["agent_id"] not in active_ids]}
265
265
 
266
266
 
267
- def pick_reviewer(agents, impl, ttype, stats=None):
267
+ def pick_reviewer(agents, impl, ttype, stats=None, exclude=(),
268
+ dead_upstreams=()):
268
269
  """评审者:跨厂商是硬规则(Codeband 的对抗式配对)——同族评审有同款盲区,
269
270
  评审者必须来自与实现者不同的 kind;跨族池为空才回退同厂商并如实备注,
270
- 绝不把回退伪装成跨厂商。"""
271
+ 绝不把回退伪装成跨厂商。exclude 用于剔除运行时已知死候选(实现步走查证伪
272
+ 的 quota/限流死链),与 pick / pick_switch_candidate 的 exclude 同语义。
273
+ dead_upstreams 进一步剔除「与死者同上游」的候选——配额通常按网关/账号烧刻,
274
+ 同上游 = 同配额桶,评审者又是单点,撞死链会白烧一轮并误报「评审器故障」,
275
+ 故宁可少一个候选也不选已知-同上游的。上游未知不剔除(宁白试不误杀)。"""
271
276
  stats = stats or history.agent_stats()
277
+ excluded = set(exclude)
278
+ dead_sets = [set(d) for d in (dead_upstreams or ()) if d]
279
+
280
+ def _alive(a):
281
+ if a["id"] in excluded:
282
+ return False
283
+ if not dead_sets:
284
+ return True
285
+ ups = agent_upstreams(a.get("id"))
286
+ return not (ups and any(ups & d for d in dead_sets))
287
+
272
288
  impl_kind = impl.get("kind")
273
- real = [a for a in agents if a.get("mode") == "real" and a["id"] != impl["id"]]
289
+ real = [a for a in agents
290
+ if a.get("mode") == "real" and a["id"] != impl["id"] and _alive(a)]
274
291
  cross = [a for a in real if a.get("kind") != impl_kind]
275
292
  if cross:
276
293
  best = max(cross, key=lambda a: score(a, "review", ttype, stats)[0])