codebee 0.1.61 → 0.1.62

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 (96) hide show
  1. package/CHANGELOG.md +490 -496
  2. package/LICENSE +21 -21
  3. package/README.md +486 -486
  4. package/app/core/aiflavor.py +197 -197
  5. package/app/core/attachments.py +532 -532
  6. package/app/core/automation.py +758 -758
  7. package/app/core/backup.py +470 -470
  8. package/app/core/beekeeper.py +183 -183
  9. package/app/core/board.py +209 -209
  10. package/app/core/bookmeta_catalog.py +180 -180
  11. package/app/core/branching.py +137 -137
  12. package/app/core/builtin_agent.py +1191 -1191
  13. package/app/core/capability.py +144 -144
  14. package/app/core/chaptersafe.py +32 -32
  15. package/app/core/cleanup.py +303 -303
  16. package/app/core/compaction.py +186 -186
  17. package/app/core/covergen.py +356 -356
  18. package/app/core/diagnostics.py +114 -114
  19. package/app/core/dispatch.py +173 -173
  20. package/app/core/dispatch_log.py +113 -113
  21. package/app/core/env_scrub.py +85 -85
  22. package/app/core/error_codes.py +64 -64
  23. package/app/core/errorlog.py +137 -137
  24. package/app/core/flows.py +355 -355
  25. package/app/core/goal_service.py +167 -167
  26. package/app/core/health.py +341 -341
  27. package/app/core/history.py +38 -38
  28. package/app/core/hooks.py +156 -156
  29. package/app/core/jobs.py +1022 -1022
  30. package/app/core/manager.py +1996 -1996
  31. package/app/core/market.py +362 -362
  32. package/app/core/market_remote.py +66 -66
  33. package/app/core/mocks.py +64 -64
  34. package/app/core/modelhub.py +3266 -3266
  35. package/app/core/notify.py +106 -106
  36. package/app/core/paihang.py +109 -109
  37. package/app/core/paths.py +2 -2
  38. package/app/core/pipeline.py +4426 -4384
  39. package/app/core/planner.py +27 -27
  40. package/app/core/portguard.py +111 -111
  41. package/app/core/preview.py +293 -293
  42. package/app/core/publish/__init__.py +7 -7
  43. package/app/core/publish/auto.py +367 -367
  44. package/app/core/publish/browser.py +533 -533
  45. package/app/core/publish/fanqie.py +128 -128
  46. package/app/core/publish/flow.py +406 -406
  47. package/app/core/publish/ledger.py +198 -198
  48. package/app/core/publish/manager.py +590 -590
  49. package/app/core/publish/qimao.py +141 -141
  50. package/app/core/publish/ws.py +139 -139
  51. package/app/core/redact.py +38 -38
  52. package/app/core/repeat_guard.py +124 -124
  53. package/app/core/router.py +307 -307
  54. package/app/core/selfupdate.py +384 -384
  55. package/app/core/session_log.py +161 -161
  56. package/app/core/sessions.py +312 -312
  57. package/app/core/settings.py +149 -149
  58. package/app/core/settings_schema.py +262 -262
  59. package/app/core/share_page.py +114 -114
  60. package/app/core/skill_scan.py +106 -95
  61. package/app/core/skillpacks/fanqie-novel.md +80 -80
  62. package/app/core/skillpacks/market/character-bible.md +66 -66
  63. package/app/core/skillpacks/market/code-risk-checklist.md +58 -58
  64. package/app/core/skillpacks/market/git-workflow.md +57 -57
  65. package/app/core/skillpacks/market/release-notes.md +72 -72
  66. package/app/core/skillpacks/market/weekly-report.md +71 -71
  67. package/app/core/skillpacks/market/worldview-consistency.md +70 -70
  68. package/app/core/step_runner.py +132 -132
  69. package/app/core/task_compile.py +199 -199
  70. package/app/core/telemetry.py +292 -292
  71. package/app/core/tlsctx.py +64 -64
  72. package/app/core/token_meter.py +147 -147
  73. package/app/core/volumes.py +355 -355
  74. package/app/core/wxdigest.py +858 -858
  75. package/app/core/zentao.py +2090 -2090
  76. package/app/main.py +2826 -2826
  77. package/app/pick_dialog.py +110 -110
  78. package/app/ui/app.js +13977 -13977
  79. package/app/ui/board.html +162 -162
  80. package/app/ui/board.js +305 -305
  81. package/app/ui/i18n.js +2711 -2711
  82. package/app/ui/icons/bee.svg +79 -79
  83. package/app/ui/index.html +1 -1
  84. package/app/ui/manifest.json +16 -16
  85. package/app/ui/qrcode.js +2297 -2297
  86. package/app/ui/style.css +6125 -6125
  87. package/bin/tutti.js +147 -147
  88. package/package.json +41 -41
  89. package/tools/wx_reader.py +261 -261
  90. package/app/.playwright-cli/console-2026-09-23T08-26-03-635Z.log +0 -88
  91. package/app/.playwright-cli/page-2026-09-23T08-26-03-845Z.yml +0 -126
  92. package/app/.playwright-cli/page-2026-09-23T08-29-44-203Z.png +0 -0
  93. package/app/.playwright-cli/page-2026-09-23T08-35-42-046Z.yml +0 -210
  94. package/app/.playwright-cli/page-2026-09-23T08-36-52-858Z.png +0 -0
  95. package/app/.playwright-cli/page-2026-09-23T08-37-33-938Z.png +0 -0
  96. package/app/.playwright-cli/run-detail-after.png +0 -0
@@ -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()