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.
Files changed (116) hide show
  1. package/CHANGELOG.md +543 -520
  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 -0
  12. package/app/core/aiflavor.py +197 -197
  13. package/app/core/artifacts.py +148 -148
  14. package/app/core/assets.py +110 -0
  15. package/app/core/attachments.py +623 -532
  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 -796
  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 -0
  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 +48 -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 +46 -26
  45. package/app/core/knowledge.py +57 -3
  46. package/app/core/manager.py +2122 -2115
  47. package/app/core/market.py +362 -362
  48. package/app/core/market_remote.py +124 -26
  49. package/app/core/mocks.py +64 -64
  50. package/app/core/modelhub.py +51 -3
  51. package/app/core/notify.py +106 -106
  52. package/app/core/operations.py +228 -0
  53. package/app/core/paihang.py +138 -109
  54. package/app/core/paths.py +2 -2
  55. package/app/core/pipeline.py +221 -12
  56. package/app/core/planner.py +6 -1
  57. package/app/core/portguard.py +111 -111
  58. package/app/core/preview.py +293 -293
  59. package/app/core/publish/__init__.py +7 -7
  60. package/app/core/publish/auto.py +367 -367
  61. package/app/core/publish/browser.py +539 -533
  62. package/app/core/publish/fanqie.py +159 -128
  63. package/app/core/publish/flow.py +440 -432
  64. package/app/core/publish/flows-fanqie-calibrated.json +332 -332
  65. package/app/core/publish/flows-qimao-calibrated.json +7 -3
  66. package/app/core/publish/ledger.py +203 -198
  67. package/app/core/publish/manager.py +692 -623
  68. package/app/core/publish/qimao.py +141 -141
  69. package/app/core/publish/ws.py +139 -139
  70. package/app/core/redact.py +38 -38
  71. package/app/core/repeat_guard.py +129 -129
  72. package/app/core/revisions.py +51 -0
  73. package/app/core/router.py +398 -398
  74. package/app/core/runner.py +2067 -2050
  75. package/app/core/selfupdate.py +384 -384
  76. package/app/core/session_log.py +161 -161
  77. package/app/core/sessions.py +312 -312
  78. package/app/core/settings.py +149 -149
  79. package/app/core/settings_schema.py +262 -262
  80. package/app/core/share_page.py +114 -114
  81. package/app/core/skill_scan.py +122 -114
  82. package/app/core/skillpacks/fanqie-novel.md +80 -80
  83. package/app/core/skillpacks/market/character-bible.md +66 -66
  84. package/app/core/skillpacks/market/code-risk-checklist.md +58 -58
  85. package/app/core/skillpacks/market/git-workflow.md +57 -57
  86. package/app/core/skillpacks/market/release-notes.md +72 -72
  87. package/app/core/skillpacks/market/weekly-report.md +71 -71
  88. package/app/core/skillpacks/market/worldview-consistency.md +70 -70
  89. package/app/core/skills.py +109 -2
  90. package/app/core/step_runner.py +132 -132
  91. package/app/core/store.py +278 -41
  92. package/app/core/task_compile.py +199 -199
  93. package/app/core/telemetry.py +292 -292
  94. package/app/core/tlsctx.py +64 -64
  95. package/app/core/token_meter.py +147 -147
  96. package/app/core/tracing.py +144 -0
  97. package/app/core/upstream.py +41 -41
  98. package/app/core/usage.py +862 -858
  99. package/app/core/volumes.py +389 -355
  100. package/app/core/wxdigest.py +858 -858
  101. package/app/core/zentao.py +2465 -2447
  102. package/app/main.py +2981 -2838
  103. package/app/pick_dialog.py +110 -110
  104. package/app/ui/app.js +14399 -13994
  105. package/app/ui/artdiff.js +74 -0
  106. package/app/ui/board.html +162 -162
  107. package/app/ui/board.js +305 -305
  108. package/app/ui/i18n.js +2770 -2716
  109. package/app/ui/icons/bee.svg +79 -79
  110. package/app/ui/index.html +6 -0
  111. package/app/ui/manifest.json +16 -16
  112. package/app/ui/qrcode.js +2297 -2297
  113. package/app/ui/style.css +96 -24
  114. package/bin/tutti.js +147 -147
  115. package/package.json +46 -46
  116. package/tools/wx_reader.py +261 -261
@@ -1,129 +1,129 @@
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
- 模板指纹。不同 CLI 间的换将是有效的回退,不能共享同一个重复预算。步骤级调用
10
- 代价远高于普通工具调用,因此第 3 次提醒、第 5 次强制停止。
11
- """
12
- from __future__ import annotations
13
-
14
- import hashlib
15
- import logging
16
- import threading
17
- import time
18
-
19
- log = logging.getLogger(__name__)
20
-
21
- # 渐进阈值:第 N 次命中时给提醒;最后一次及以上时强制停止。
22
- DEFAULT_THRESHOLDS = (3, 5)
23
-
24
- REMINDER_TEMPLATE = (
25
- "⚠️ 提醒:上一步使用了几乎相同的输入(已连续 {count} 次调用 {role})。"
26
- "请换一种方法:读不同的文件、用更具体的指令、或明确询问用户澄清。"
27
- )
28
-
29
- STOP_TEMPLATE = (
30
- "已连续 {count} 次使用相同输入调用 {role},超过 {last_thr} 次阈值,"
31
- "强制停止本 step 以防止无限循环。请用户检查输入或调整 prompt 模板。"
32
- )
33
-
34
-
35
- def _prompt_fingerprint(prompt: str, identity: str = "") -> str:
36
- """prompt 指纹:用前 200 字符 + 总长度(CLI prompt 头几行通常是模板部分)。
37
-
38
- 不做严格 canonical args 比对(CLI prompt 通常很长,且 dsh 也用 JSON.stringify);
39
- 指纹命中说明是同一个模板路径走过多次,已经够分辨。
40
-
41
- 用 SHA-256 取前 12 字符(96 bit):48 亿分之一碰撞概率对去重足够;不追求密码学强度。
42
- """
43
- head = (prompt or "")[:200]
44
- return hashlib.sha256(
45
- f"{identity}\0{head}|{len(prompt or '')}".encode("utf-8")).hexdigest()[:12]
46
-
47
-
48
- class RepeatGuard:
49
- """每 (run_id, step_role) 的最近执行链独立计数;指纹包含执行者身份。
50
-
51
- 计数仅统计相同 key 连续出现;新 prompt 会重置。
52
- """
53
-
54
- def __init__(self, thresholds=DEFAULT_THRESHOLDS, *, max_history=50):
55
- self.thresholds = tuple(sorted(set(thresholds)))
56
- self.max_history = max_history
57
- self._lock = threading.Lock()
58
- # chain[(run_id, role)] = [(ts, fingerprint)]
59
- self._chains: dict = {}
60
-
61
- def check(self, run_id: str, role: str, prompt: str, *, identity: str = "") -> dict:
62
- """检查并更新 (run_id, role) 的连续计数。
63
-
64
- identity 标识实际执行 CLI/agent。相同 prompt 换到其他执行者属于回退,
65
- 会从新的连续链计数;同一执行者重复相同调用仍按阈值停止。
66
-
67
- Returns:
68
- {"count": int, "reminder": str | None, "should_stop": bool}
69
- """
70
- fp = _prompt_fingerprint(prompt, str(identity or ""))
71
- chain_key = (run_id, role)
72
- with self._lock:
73
- chain = self._chains.setdefault(chain_key, [])
74
- chain.append((time.time(), fp))
75
- # 仅统计相同 fp 连续出现
76
- count = 1
77
- for _, prev_fp in reversed(chain[:-1]):
78
- if prev_fp == fp:
79
- count += 1
80
- else:
81
- break
82
- # 截尾避免内存膨胀
83
- if len(chain) > self.max_history:
84
- self._chains[chain_key] = chain[-self.max_history:]
85
-
86
- reminder = None
87
- stop = False
88
- last_thr = self.thresholds[-1]
89
- if count >= last_thr:
90
- stop = True
91
- reminder = STOP_TEMPLATE.format(count=count, role=role, last_thr=last_thr)
92
- elif count in self.thresholds:
93
- reminder = REMINDER_TEMPLATE.format(count=count, role=role)
94
-
95
- if reminder:
96
- log.warning("repeat-guard %s/%s count=%d stop=%s reminder=%s",
97
- run_id, role, count, stop, reminder[:80])
98
- return {"count": count, "reminder": reminder, "should_stop": stop}
99
-
100
- def reset(self, run_id: str) -> int:
101
- """重置某 run_id 的所有 (run_id, *) 链。返回清除的链条数。"""
102
- with self._lock:
103
- cleared = 0
104
- for k in list(self._chains.keys()):
105
- if k[0] == run_id:
106
- del self._chains[k]
107
- cleared += 1
108
- return cleared
109
-
110
- def stats(self, run_id: str, role: str) -> dict:
111
- """读取 (run_id, role) 当前状态(不更新)。用于 UI 调试。"""
112
- chain_key = (run_id, role)
113
- with self._lock:
114
- chain = self._chains.get(chain_key, [])
115
- if not chain:
116
- return {"chain_len": 0, "last_fp": ""}
117
- # 当前连续计数
118
- last_fp = chain[-1][1]
119
- count = 1
120
- for _, fp in reversed(chain[:-1]):
121
- if fp == last_fp:
122
- count += 1
123
- else:
124
- break
125
- return {"chain_len": len(chain), "last_fp": last_fp, "count": count}
126
-
127
-
128
- # 模块级默认实例(pipeline 接线用;run_id 入键天然隔离多任务,测试无需替换)
129
- 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
+ 模板指纹。不同 CLI 间的换将是有效的回退,不能共享同一个重复预算。步骤级调用
10
+ 代价远高于普通工具调用,因此第 3 次提醒、第 5 次强制停止。
11
+ """
12
+ from __future__ import annotations
13
+
14
+ import hashlib
15
+ import logging
16
+ import threading
17
+ import time
18
+
19
+ log = logging.getLogger(__name__)
20
+
21
+ # 渐进阈值:第 N 次命中时给提醒;最后一次及以上时强制停止。
22
+ DEFAULT_THRESHOLDS = (3, 5)
23
+
24
+ REMINDER_TEMPLATE = (
25
+ "⚠️ 提醒:上一步使用了几乎相同的输入(已连续 {count} 次调用 {role})。"
26
+ "请换一种方法:读不同的文件、用更具体的指令、或明确询问用户澄清。"
27
+ )
28
+
29
+ STOP_TEMPLATE = (
30
+ "已连续 {count} 次使用相同输入调用 {role},超过 {last_thr} 次阈值,"
31
+ "强制停止本 step 以防止无限循环。请用户检查输入或调整 prompt 模板。"
32
+ )
33
+
34
+
35
+ def _prompt_fingerprint(prompt: str, identity: str = "") -> str:
36
+ """prompt 指纹:用前 200 字符 + 总长度(CLI prompt 头几行通常是模板部分)。
37
+
38
+ 不做严格 canonical args 比对(CLI prompt 通常很长,且 dsh 也用 JSON.stringify);
39
+ 指纹命中说明是同一个模板路径走过多次,已经够分辨。
40
+
41
+ 用 SHA-256 取前 12 字符(96 bit):48 亿分之一碰撞概率对去重足够;不追求密码学强度。
42
+ """
43
+ head = (prompt or "")[:200]
44
+ return hashlib.sha256(
45
+ f"{identity}\0{head}|{len(prompt or '')}".encode("utf-8")).hexdigest()[:12]
46
+
47
+
48
+ class RepeatGuard:
49
+ """每 (run_id, step_role) 的最近执行链独立计数;指纹包含执行者身份。
50
+
51
+ 计数仅统计相同 key 连续出现;新 prompt 会重置。
52
+ """
53
+
54
+ def __init__(self, thresholds=DEFAULT_THRESHOLDS, *, max_history=50):
55
+ self.thresholds = tuple(sorted(set(thresholds)))
56
+ self.max_history = max_history
57
+ self._lock = threading.Lock()
58
+ # chain[(run_id, role)] = [(ts, fingerprint)]
59
+ self._chains: dict = {}
60
+
61
+ def check(self, run_id: str, role: str, prompt: str, *, identity: str = "") -> dict:
62
+ """检查并更新 (run_id, role) 的连续计数。
63
+
64
+ identity 标识实际执行 CLI/agent。相同 prompt 换到其他执行者属于回退,
65
+ 会从新的连续链计数;同一执行者重复相同调用仍按阈值停止。
66
+
67
+ Returns:
68
+ {"count": int, "reminder": str | None, "should_stop": bool}
69
+ """
70
+ fp = _prompt_fingerprint(prompt, str(identity or ""))
71
+ chain_key = (run_id, role)
72
+ with self._lock:
73
+ chain = self._chains.setdefault(chain_key, [])
74
+ chain.append((time.time(), fp))
75
+ # 仅统计相同 fp 连续出现
76
+ count = 1
77
+ for _, prev_fp in reversed(chain[:-1]):
78
+ if prev_fp == fp:
79
+ count += 1
80
+ else:
81
+ break
82
+ # 截尾避免内存膨胀
83
+ if len(chain) > self.max_history:
84
+ self._chains[chain_key] = chain[-self.max_history:]
85
+
86
+ reminder = None
87
+ stop = False
88
+ last_thr = self.thresholds[-1]
89
+ if count >= last_thr:
90
+ stop = True
91
+ reminder = STOP_TEMPLATE.format(count=count, role=role, last_thr=last_thr)
92
+ elif count in self.thresholds:
93
+ reminder = REMINDER_TEMPLATE.format(count=count, role=role)
94
+
95
+ if reminder:
96
+ log.warning("repeat-guard %s/%s count=%d stop=%s reminder=%s",
97
+ run_id, role, count, stop, reminder[:80])
98
+ return {"count": count, "reminder": reminder, "should_stop": stop}
99
+
100
+ def reset(self, run_id: str) -> int:
101
+ """重置某 run_id 的所有 (run_id, *) 链。返回清除的链条数。"""
102
+ with self._lock:
103
+ cleared = 0
104
+ for k in list(self._chains.keys()):
105
+ if k[0] == run_id:
106
+ del self._chains[k]
107
+ cleared += 1
108
+ return cleared
109
+
110
+ def stats(self, run_id: str, role: str) -> dict:
111
+ """读取 (run_id, role) 当前状态(不更新)。用于 UI 调试。"""
112
+ chain_key = (run_id, role)
113
+ with self._lock:
114
+ chain = self._chains.get(chain_key, [])
115
+ if not chain:
116
+ return {"chain_len": 0, "last_fp": ""}
117
+ # 当前连续计数
118
+ last_fp = chain[-1][1]
119
+ count = 1
120
+ for _, fp in reversed(chain[:-1]):
121
+ if fp == last_fp:
122
+ count += 1
123
+ else:
124
+ break
125
+ return {"chain_len": len(chain), "last_fp": last_fp, "count": count}
126
+
127
+
128
+ # 模块级默认实例(pipeline 接线用;run_id 入键天然隔离多任务,测试无需替换)
129
+ guard = RepeatGuard()
@@ -0,0 +1,51 @@
1
+ # -*- coding: utf-8 -*-
2
+ """Small, content-addressed revision helpers.
3
+
4
+ The execution layer stores revision identifiers and digests instead of copying
5
+ skill/knowledge bodies into every run. A digest is deliberately based on the
6
+ semantic payload supplied by the caller, so formatting-only metadata changes do
7
+ not silently change the meaning of an existing run.
8
+ """
9
+ from __future__ import annotations
10
+
11
+ import hashlib
12
+ import json
13
+ import time
14
+
15
+
16
+ def canonical_json(value) -> str:
17
+ return json.dumps(value, ensure_ascii=False, sort_keys=True,
18
+ separators=(",", ":"), default=str)
19
+
20
+
21
+ def content_sha256(value) -> str:
22
+ if not isinstance(value, str):
23
+ value = canonical_json(value)
24
+ return hashlib.sha256(value.encode("utf-8")).hexdigest()
25
+
26
+
27
+ def revision_id(prefix: str, digest: str) -> str:
28
+ return "%s-%s" % (str(prefix or "rev"), str(digest or "")[:16])
29
+
30
+
31
+ def make_revision(prefix: str, content, *, source="", created_at=None) -> dict:
32
+ digest = content_sha256(content)
33
+ return {
34
+ "revision_id": revision_id(prefix, digest),
35
+ "content_sha256": digest,
36
+ "source": str(source or "")[:160],
37
+ "created_at": created_at or time.strftime("%Y-%m-%d %H:%M:%S"),
38
+ }
39
+
40
+
41
+ def revision_record(prefix: str, content, *, source="", created_at=None,
42
+ extra=None) -> dict:
43
+ out = make_revision(prefix, content, source=source, created_at=created_at)
44
+ if isinstance(extra, dict):
45
+ out.update(extra)
46
+ return out
47
+
48
+
49
+ def stable_digest(items) -> str:
50
+ """Digest a list/dict of revision metadata without embedding content."""
51
+ return content_sha256(items)