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,38 +1,38 @@
1
- # -*- coding: utf-8 -*-
2
- """历史统计:从过往运行记录提取每个智能体在每种任务类型上的胜率,反哺路由。"""
3
- from __future__ import annotations
4
-
5
- from . import store
6
-
7
-
8
- def agent_stats(limit=200):
9
- """返回 {agent_id: {task_type: {"runs": n, "wins": w}}}。"""
10
- stats = {}
11
-
12
- def bump(aid, ttype, win):
13
- if not aid:
14
- return
15
- s = stats.setdefault(aid, {}).setdefault(ttype, {"runs": 0, "wins": 0})
16
- s["runs"] += 1
17
- if win:
18
- s["wins"] += 1
19
-
20
- for run in store.list_runs(limit):
21
- if run.get("kind") != "orchestration":
22
- continue
23
- # 失败运行也要进历史(记为全负):以前只数 done——常挂的 CLI 永远
24
- # 「无历史记录」满血参选,2026-09-17 opencode 当日秒挂 3 次仍被换将
25
- # 选中。cancelled 不算(用户取消非智能体之过)。
26
- if run.get("status") not in ("done", "failed"):
27
- continue
28
- verdict = run.get("verdict") or {}
29
- task = store.get_task(run.get("task_id")) if run.get("task_id") else None
30
- ttype = (task or {}).get("type") or verdict.get("type")
31
- if not ttype:
32
- continue
33
- win = run.get("status") == "done" and bool(
34
- verdict.get("pass") or verdict.get("publishable"))
35
- for s in run.get("steps") or []:
36
- if s.get("role") in ("implement", "draft"):
37
- bump(s.get("agent"), ttype, win)
38
- return stats
1
+ # -*- coding: utf-8 -*-
2
+ """历史统计:从过往运行记录提取每个智能体在每种任务类型上的胜率,反哺路由。"""
3
+ from __future__ import annotations
4
+
5
+ from . import store
6
+
7
+
8
+ def agent_stats(limit=200):
9
+ """返回 {agent_id: {task_type: {"runs": n, "wins": w}}}。"""
10
+ stats = {}
11
+
12
+ def bump(aid, ttype, win):
13
+ if not aid:
14
+ return
15
+ s = stats.setdefault(aid, {}).setdefault(ttype, {"runs": 0, "wins": 0})
16
+ s["runs"] += 1
17
+ if win:
18
+ s["wins"] += 1
19
+
20
+ for run in store.list_runs(limit):
21
+ if run.get("kind") != "orchestration":
22
+ continue
23
+ # 失败运行也要进历史(记为全负):以前只数 done——常挂的 CLI 永远
24
+ # 「无历史记录」满血参选,2026-09-17 opencode 当日秒挂 3 次仍被换将
25
+ # 选中。cancelled 不算(用户取消非智能体之过)。
26
+ if run.get("status") not in ("done", "failed"):
27
+ continue
28
+ verdict = run.get("verdict") or {}
29
+ task = store.get_task(run.get("task_id")) if run.get("task_id") else None
30
+ ttype = (task or {}).get("type") or verdict.get("type")
31
+ if not ttype:
32
+ continue
33
+ win = run.get("status") == "done" and bool(
34
+ verdict.get("pass") or verdict.get("publishable"))
35
+ for s in run.get("steps") or []:
36
+ if s.get("role") in ("implement", "draft"):
37
+ bump(s.get("agent"), ttype, win)
38
+ return stats
package/app/core/hooks.py CHANGED
@@ -1,156 +1,156 @@
1
- # -*- coding: utf-8 -*-
2
- """任务生命周期钩子(借鉴 ZCode hooks,收窄为三个事件——2026-09-22 用户立项):
3
-
4
- - task_start 任务开跑前(编排/直连都触发):stdout 可注入文本,追加进任务上下文
5
- - message_submit 用户消息送达前:stdout 可注入文本,随消息进提示词头
6
- - run_end 每轮运行结束:纯副作用型(回写知识库等),输出被忽略
7
-
8
- 协议:钩子命令从 stdin 收 JSON(事件+上下文);stdout 若为 {"inject": "..."}
9
- 取该字段为注入文本,否则把非空 stdout 整体当注入文本。
10
- 失败/超时一律静默跳过(外部脚本不稳绝不拖死编排主流程)。
11
-
12
- 命令执行:shlex 分词(保 Windows 反斜杠)后走参数列表,不经 shell——
13
- 不支持管道/重定向等 shell 语法,需要时写 .py/.bat 包装脚本。
14
- 配置:DATA_DIR/hooks.json,纯本机设置。"""
15
- from __future__ import annotations
16
-
17
- import json
18
- import logging
19
- import shlex
20
- import subprocess
21
-
22
- from . import paths
23
-
24
- log = logging.getLogger(__name__)
25
-
26
- EVENTS = ("task_start", "message_submit", "run_end")
27
- _DEFAULT_TIMEOUT = 10
28
- _FILE = None # 测试重绑(同 modelhub._FILE 惯例)
29
-
30
- _CREATE_NO_WINDOW = 0x08000000 if hasattr(subprocess, "CREATE_NO_WINDOW") else 0
31
-
32
-
33
- def _path():
34
- return _FILE or (paths.DATA_DIR / "hooks.json")
35
-
36
-
37
- def _default_list():
38
- return []
39
-
40
-
41
- def load_list():
42
- """读钩子配置列表;损坏/缺省回落空表。"""
43
- try:
44
- data = json.loads(_path().read_text(encoding="utf-8"))
45
- return data if isinstance(data, list) else _default_list()
46
- except Exception:
47
- return _default_list()
48
-
49
-
50
- def save_list(items):
51
- """整表保存(UI 全量提交)。字段归一 + 事件白名单。"""
52
- if not isinstance(items, list):
53
- raise ValueError("hooks 必须是数组")
54
- out = []
55
- for i, h in enumerate(items):
56
- if not isinstance(h, dict):
57
- continue
58
- name = str(h.get("name") or "").strip()[:60]
59
- event = str(h.get("event") or "").strip()
60
- cmd = str(h.get("cmd") or "").strip()
61
- if not cmd:
62
- continue
63
- if event not in EVENTS:
64
- raise ValueError("第 %d 条钩子 event 非法(%s)" % (i + 1, event))
65
- try:
66
- timeout = max(1, min(120, int(h.get("timeout_s") or _DEFAULT_TIMEOUT)))
67
- except (TypeError, ValueError):
68
- timeout = _DEFAULT_TIMEOUT
69
- out.append({"id": str(h.get("id") or ("hk-%d" % (i + 1)))[:40],
70
- "name": name or ("钩子 %d" % (i + 1)),
71
- "event": event, "cmd": cmd,
72
- "enabled": bool(h.get("enabled", True)),
73
- "timeout_s": timeout})
74
- tmp = _path().with_suffix(".tmp")
75
- tmp.write_text(json.dumps(out, ensure_ascii=False, indent=1), encoding="utf-8")
76
- tmp.replace(_path())
77
- return out
78
-
79
-
80
- def _argv(cmd):
81
- """命令串 → 参数列表。Windows 反斜杠路径保真(posix=False),支持引号分组;
82
- 分词后剥掉 token 首尾引号(CreateProcess 不认字面引号路径)。"""
83
- toks = shlex.split(str(cmd or ""), posix=False)
84
- out = []
85
- for t in toks:
86
- if len(t) >= 2 and t[0] == t[-1] and t[0] in ('"', "'"):
87
- t = t[1:-1]
88
- if t:
89
- out.append(t)
90
- return out
91
-
92
-
93
- def _run_one(h, payload):
94
- """跑单条钩子。返回注入文本或 None(失败/超时/空输出)。"""
95
- try:
96
- argv = _argv(h.get("cmd"))
97
- except ValueError as e:
98
- log.warning("hook[%s %s] 命令解析失败:%s", h.get("name"), h.get("event"), str(e)[:120])
99
- return None
100
- if not argv:
101
- return None
102
- try:
103
- proc = subprocess.run(
104
- argv, shell=False,
105
- input=json.dumps(payload, ensure_ascii=False).encode("utf-8"),
106
- capture_output=True, timeout=int(h.get("timeout_s") or _DEFAULT_TIMEOUT),
107
- creationflags=_CREATE_NO_WINDOW)
108
- except subprocess.TimeoutExpired:
109
- log.warning("hook[%s %s] 超时,已跳过", h.get("name"), h.get("event"))
110
- return None
111
- except Exception as e:
112
- log.warning("hook[%s %s] 执行失败:%s", h.get("name"), h.get("event"), str(e)[:120])
113
- return None
114
- out = (proc.stdout or b"").decode("utf-8", "replace").strip()
115
- if not out:
116
- return None
117
- try:
118
- data = json.loads(out)
119
- if isinstance(data, dict):
120
- return str(data.get("inject") or "") or None
121
- except Exception:
122
- pass
123
- return out
124
-
125
-
126
- def run_event(event, ctx=None, task_id=None, run_id=None, text=None):
127
- """触发事件:依次跑启用的钩子,合并注入文本。任何异常不外抛。
128
-
129
- ctx 附带任务/运行摘要;text 仅 message_submit 时传用户消息原文。
130
- 返回拼接好的注入文本(可为空串)。"""
131
- if event not in EVENTS:
132
- return ""
133
- try:
134
- items = [h for h in load_list() if h.get("enabled") and h.get("event") == event]
135
- except Exception:
136
- return ""
137
- if not items:
138
- return ""
139
- payload = {"event": event, "task_id": task_id or "", "run_id": run_id or "",
140
- "text": text or ""}
141
- if ctx is not None:
142
- payload["ctx"] = ctx
143
- outs = []
144
- for h in items:
145
- piece = _run_one(h, payload)
146
- if piece:
147
- outs.append(piece.strip())
148
- return "\n\n".join(outs)
149
-
150
-
151
- def test_one(cmd, event="task_start", timeout_s=None):
152
- """设置页「测试」:手动跑一条钩子,返回 (ok, 输出/错误人话)。"""
153
- h = {"name": "test", "event": event if event in EVENTS else "task_start",
154
- "cmd": cmd, "timeout_s": timeout_s or _DEFAULT_TIMEOUT}
155
- piece = _run_one(h, {"event": h["event"], "task_id": "", "run_id": "", "text": ""})
156
- return True, piece if piece else "(无输出,未注入)"
1
+ # -*- coding: utf-8 -*-
2
+ """任务生命周期钩子(借鉴 ZCode hooks,收窄为三个事件——2026-09-22 用户立项):
3
+
4
+ - task_start 任务开跑前(编排/直连都触发):stdout 可注入文本,追加进任务上下文
5
+ - message_submit 用户消息送达前:stdout 可注入文本,随消息进提示词头
6
+ - run_end 每轮运行结束:纯副作用型(回写知识库等),输出被忽略
7
+
8
+ 协议:钩子命令从 stdin 收 JSON(事件+上下文);stdout 若为 {"inject": "..."}
9
+ 取该字段为注入文本,否则把非空 stdout 整体当注入文本。
10
+ 失败/超时一律静默跳过(外部脚本不稳绝不拖死编排主流程)。
11
+
12
+ 命令执行:shlex 分词(保 Windows 反斜杠)后走参数列表,不经 shell——
13
+ 不支持管道/重定向等 shell 语法,需要时写 .py/.bat 包装脚本。
14
+ 配置:DATA_DIR/hooks.json,纯本机设置。"""
15
+ from __future__ import annotations
16
+
17
+ import json
18
+ import logging
19
+ import shlex
20
+ import subprocess
21
+
22
+ from . import paths
23
+
24
+ log = logging.getLogger(__name__)
25
+
26
+ EVENTS = ("task_start", "message_submit", "run_end")
27
+ _DEFAULT_TIMEOUT = 10
28
+ _FILE = None # 测试重绑(同 modelhub._FILE 惯例)
29
+
30
+ _CREATE_NO_WINDOW = 0x08000000 if hasattr(subprocess, "CREATE_NO_WINDOW") else 0
31
+
32
+
33
+ def _path():
34
+ return _FILE or (paths.DATA_DIR / "hooks.json")
35
+
36
+
37
+ def _default_list():
38
+ return []
39
+
40
+
41
+ def load_list():
42
+ """读钩子配置列表;损坏/缺省回落空表。"""
43
+ try:
44
+ data = json.loads(_path().read_text(encoding="utf-8"))
45
+ return data if isinstance(data, list) else _default_list()
46
+ except Exception:
47
+ return _default_list()
48
+
49
+
50
+ def save_list(items):
51
+ """整表保存(UI 全量提交)。字段归一 + 事件白名单。"""
52
+ if not isinstance(items, list):
53
+ raise ValueError("hooks 必须是数组")
54
+ out = []
55
+ for i, h in enumerate(items):
56
+ if not isinstance(h, dict):
57
+ continue
58
+ name = str(h.get("name") or "").strip()[:60]
59
+ event = str(h.get("event") or "").strip()
60
+ cmd = str(h.get("cmd") or "").strip()
61
+ if not cmd:
62
+ continue
63
+ if event not in EVENTS:
64
+ raise ValueError("第 %d 条钩子 event 非法(%s)" % (i + 1, event))
65
+ try:
66
+ timeout = max(1, min(120, int(h.get("timeout_s") or _DEFAULT_TIMEOUT)))
67
+ except (TypeError, ValueError):
68
+ timeout = _DEFAULT_TIMEOUT
69
+ out.append({"id": str(h.get("id") or ("hk-%d" % (i + 1)))[:40],
70
+ "name": name or ("钩子 %d" % (i + 1)),
71
+ "event": event, "cmd": cmd,
72
+ "enabled": bool(h.get("enabled", True)),
73
+ "timeout_s": timeout})
74
+ tmp = _path().with_suffix(".tmp")
75
+ tmp.write_text(json.dumps(out, ensure_ascii=False, indent=1), encoding="utf-8")
76
+ tmp.replace(_path())
77
+ return out
78
+
79
+
80
+ def _argv(cmd):
81
+ """命令串 → 参数列表。Windows 反斜杠路径保真(posix=False),支持引号分组;
82
+ 分词后剥掉 token 首尾引号(CreateProcess 不认字面引号路径)。"""
83
+ toks = shlex.split(str(cmd or ""), posix=False)
84
+ out = []
85
+ for t in toks:
86
+ if len(t) >= 2 and t[0] == t[-1] and t[0] in ('"', "'"):
87
+ t = t[1:-1]
88
+ if t:
89
+ out.append(t)
90
+ return out
91
+
92
+
93
+ def _run_one(h, payload):
94
+ """跑单条钩子。返回注入文本或 None(失败/超时/空输出)。"""
95
+ try:
96
+ argv = _argv(h.get("cmd"))
97
+ except ValueError as e:
98
+ log.warning("hook[%s %s] 命令解析失败:%s", h.get("name"), h.get("event"), str(e)[:120])
99
+ return None
100
+ if not argv:
101
+ return None
102
+ try:
103
+ proc = subprocess.run(
104
+ argv, shell=False,
105
+ input=json.dumps(payload, ensure_ascii=False).encode("utf-8"),
106
+ capture_output=True, timeout=int(h.get("timeout_s") or _DEFAULT_TIMEOUT),
107
+ creationflags=_CREATE_NO_WINDOW)
108
+ except subprocess.TimeoutExpired:
109
+ log.warning("hook[%s %s] 超时,已跳过", h.get("name"), h.get("event"))
110
+ return None
111
+ except Exception as e:
112
+ log.warning("hook[%s %s] 执行失败:%s", h.get("name"), h.get("event"), str(e)[:120])
113
+ return None
114
+ out = (proc.stdout or b"").decode("utf-8", "replace").strip()
115
+ if not out:
116
+ return None
117
+ try:
118
+ data = json.loads(out)
119
+ if isinstance(data, dict):
120
+ return str(data.get("inject") or "") or None
121
+ except Exception:
122
+ pass
123
+ return out
124
+
125
+
126
+ def run_event(event, ctx=None, task_id=None, run_id=None, text=None):
127
+ """触发事件:依次跑启用的钩子,合并注入文本。任何异常不外抛。
128
+
129
+ ctx 附带任务/运行摘要;text 仅 message_submit 时传用户消息原文。
130
+ 返回拼接好的注入文本(可为空串)。"""
131
+ if event not in EVENTS:
132
+ return ""
133
+ try:
134
+ items = [h for h in load_list() if h.get("enabled") and h.get("event") == event]
135
+ except Exception:
136
+ return ""
137
+ if not items:
138
+ return ""
139
+ payload = {"event": event, "task_id": task_id or "", "run_id": run_id or "",
140
+ "text": text or ""}
141
+ if ctx is not None:
142
+ payload["ctx"] = ctx
143
+ outs = []
144
+ for h in items:
145
+ piece = _run_one(h, payload)
146
+ if piece:
147
+ outs.append(piece.strip())
148
+ return "\n\n".join(outs)
149
+
150
+
151
+ def test_one(cmd, event="task_start", timeout_s=None):
152
+ """设置页「测试」:手动跑一条钩子,返回 (ok, 输出/错误人话)。"""
153
+ h = {"name": "test", "event": event if event in EVENTS else "task_start",
154
+ "cmd": cmd, "timeout_s": timeout_s or _DEFAULT_TIMEOUT}
155
+ piece = _run_one(h, {"event": h["event"], "task_id": "", "run_id": "", "text": ""})
156
+ return True, piece if piece else "(无输出,未注入)"