codebee 0.1.30 → 0.1.32
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.
- package/CHANGELOG.md +17 -0
- package/README.md +11 -0
- package/app/core/builtin_agent.py +3 -1
- package/app/core/modelhub.py +11 -5
- package/app/core/pipeline.py +8 -6
- package/app/core/planner.py +34 -10
- package/app/core/repeat_guard.py +3 -3
- package/app/core/store.py +10 -2
- package/app/core/task_compile.py +3 -2
- package/app/main.py +3 -1
- package/app/pet.py +1564 -1564
- package/app/ui/app.js +110 -61
- package/app/ui/i18n.js +15 -0
- package/app/ui/index.html +1174 -1185
- package/app/ui/style.css +30 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,23 @@ README 元数据带回,供老版本在「发现新版本」时展示新版更
|
|
|
6
6
|
|
|
7
7
|
## 未发布
|
|
8
8
|
|
|
9
|
+
## v0.1.32(2026-09-21)
|
|
10
|
+
|
|
11
|
+
### ✨ 新功能
|
|
12
|
+
|
|
13
|
+
- 连载任务详情新增「章节评审」卡片:每章的维度评分(低分标红)、达标状态、字数与修订轮数一目了然;每章按大纲逐项核对的判定结果(已完成/未完成/待核实)以红绿灰行直接展示——哪章哪个要点没写到一眼可查。
|
|
14
|
+
|
|
15
|
+
## v0.1.31(2026-09-21)
|
|
16
|
+
|
|
17
|
+
### ✨ 新功能
|
|
18
|
+
|
|
19
|
+
- 群摘要搬进正式页面:设置 →「微信群聊汇报」现在是完整页面——配置(监控文件夹/微信直连/Python 扫描与一键安装/监听群)和各群摘要列表一体呈现,不再挤在右下角悬浮小窗里。
|
|
20
|
+
- 桌面蜜蜂右键「群摘要」直达该页面(已打开的页面只聚焦不重复开)。
|
|
21
|
+
|
|
22
|
+
### 🛠 问题修复
|
|
23
|
+
|
|
24
|
+
- 修复微信直连「选不中 Python」:此前每分钟的自动刷新会把用户刚选好但还没保存的路径悄悄冲回旧值,现在改过路径后不再回填覆盖,保存成功后才更新。
|
|
25
|
+
|
|
9
26
|
## v0.1.30(2026-09-21)
|
|
10
27
|
|
|
11
28
|
### ✨ 新功能
|
package/README.md
CHANGED
|
@@ -23,6 +23,17 @@ Kimi Code、MiMo Code、Grok Build、Pi、DeepSeek Harness……),提供
|
|
|
23
23
|
<!-- relnotes:start -->
|
|
24
24
|
### ✨ 新功能 · New
|
|
25
25
|
|
|
26
|
+
- 连载任务详情新增「章节评审」卡片:每章维度评分(低分标红)与达标状态,大纲要点逐项核对结果红绿灰直读。
|
|
27
|
+
- Serial runs now show a Chapter Reviews card: per-chapter dimension scores (low ones in red) plus beat-by-beat checks color-coded green/red/gray.
|
|
28
|
+
|
|
29
|
+
- 群摘要搬进正式页面:设置 →「微信群聊汇报」里配置与摘要列表一体呈现,右下角悬浮小窗已移除;桌面蜜蜂右键「群摘要」直达。
|
|
30
|
+
- Group digests now live on a proper settings page (config + digest list in one place); the floating bottom-right widget is gone, and the desktop bee links straight to it.
|
|
31
|
+
|
|
32
|
+
### 🛠 问题修复 · Fixes
|
|
33
|
+
|
|
34
|
+
- 修复「选不中 Python」:自动刷新不再冲掉你刚选好但还没保存的路径。
|
|
35
|
+
- Fixed "can't pick a Python": background refresh no longer wipes a freshly chosen but unsaved path.
|
|
36
|
+
|
|
26
37
|
- 微信直连配置更省事:「扫描」自动找本机 Python(标注位数与依赖状态,点选即用),选中后可一键安装 wechatauto-replica。
|
|
27
38
|
- WeChat setup is now effortless: a Scan button finds local Pythons (with bitness and dependency badges), and one click installs wechatauto-replica.
|
|
28
39
|
|
|
@@ -566,7 +566,9 @@ def run(bi, prompt, workdir, timeout=180, cancel_event=None, log=None, images=No
|
|
|
566
566
|
url, headers, body = _build_request(proto, pbase, kk["key"], model,
|
|
567
567
|
system, msgs, tools_ok)
|
|
568
568
|
effort = str(bi.get("reasoning_effort") or "").strip().lower()
|
|
569
|
-
|
|
569
|
+
# reasoning_effort 是 OpenAI wire 字段;Anthropic thinking 使用
|
|
570
|
+
# 另一套对象结构,向兼容网关硬塞该字段会直接得到 400。
|
|
571
|
+
if effort in ("low", "medium", "high") and proto == "openai":
|
|
570
572
|
body["reasoning_effort"] = effort
|
|
571
573
|
status, data, err = _post_interruptible(url, headers, body,
|
|
572
574
|
allow_private, timeout,
|
package/app/core/modelhub.py
CHANGED
|
@@ -3003,16 +3003,17 @@ def resolve_orchestrator():
|
|
|
3003
3003
|
return prov, model
|
|
3004
3004
|
|
|
3005
3005
|
|
|
3006
|
-
def _chat_cache_path(provider_id, model_name, prompt, max_tokens):
|
|
3006
|
+
def _chat_cache_path(provider_id, model_name, prompt, max_tokens, reasoning_effort=""):
|
|
3007
3007
|
"""§07 T2.2:精确匹配响应缓存的落盘路径(只缓存 ok 的幂等调用)。"""
|
|
3008
3008
|
import hashlib as _h
|
|
3009
|
-
key = "|".join([str(provider_id), str(model_name), str(max_tokens),
|
|
3009
|
+
key = "|".join([str(provider_id), str(model_name), str(max_tokens),
|
|
3010
|
+
str(reasoning_effort or ""), str(prompt)])
|
|
3010
3011
|
name = _h.sha256(key.encode("utf-8")).hexdigest()[:24]
|
|
3011
3012
|
return paths.DATA_DIR / "chat_cache" / (name + ".json")
|
|
3012
3013
|
|
|
3013
3014
|
|
|
3014
3015
|
def chat(provider_id, model_name, prompt, max_tokens=2048, timeout=120, cache_ttl=0,
|
|
3015
|
-
on_delta=None):
|
|
3016
|
+
on_delta=None, reasoning_effort=""):
|
|
3016
3017
|
"""直连供应商 API 做一次对话(编排者规划 / 连通性测试)。
|
|
3017
3018
|
|
|
3018
3019
|
支持 anthropic / openai / google 三种协议;复用 SSRF 防护。
|
|
@@ -3026,7 +3027,8 @@ def chat(provider_id, model_name, prompt, max_tokens=2048, timeout=120, cache_tt
|
|
|
3026
3027
|
"""
|
|
3027
3028
|
if cache_ttl > 0:
|
|
3028
3029
|
try:
|
|
3029
|
-
cache_path = _chat_cache_path(provider_id, model_name, prompt, max_tokens
|
|
3030
|
+
cache_path = _chat_cache_path(provider_id, model_name, prompt, max_tokens,
|
|
3031
|
+
reasoning_effort)
|
|
3030
3032
|
if cache_path.is_file():
|
|
3031
3033
|
age = time.time() - cache_path.stat().st_mtime
|
|
3032
3034
|
if age <= cache_ttl:
|
|
@@ -3073,6 +3075,9 @@ def chat(provider_id, model_name, prompt, max_tokens=2048, timeout=120, cache_tt
|
|
|
3073
3075
|
headers = {"Authorization": "Bearer " + use_key}
|
|
3074
3076
|
body = {"model": model_name, "max_tokens": max_tokens,
|
|
3075
3077
|
"messages": [{"role": "user", "content": prompt}]}
|
|
3078
|
+
effort = str(reasoning_effort or "").strip().lower()
|
|
3079
|
+
if proto == "openai" and effort in ("low", "medium", "high"):
|
|
3080
|
+
body["reasoning_effort"] = effort
|
|
3076
3081
|
return url, headers, body
|
|
3077
3082
|
|
|
3078
3083
|
last_err = ""
|
|
@@ -3156,7 +3161,8 @@ def chat(provider_id, model_name, prompt, max_tokens=2048, timeout=120, cache_tt
|
|
|
3156
3161
|
# §07 T2.2:cache_ttl>0 时落盘缓存(仅 ok 结果,原子写)
|
|
3157
3162
|
if cache_ttl > 0:
|
|
3158
3163
|
try:
|
|
3159
|
-
cache_path = _chat_cache_path(provider_id, model_name, prompt, max_tokens
|
|
3164
|
+
cache_path = _chat_cache_path(provider_id, model_name, prompt, max_tokens,
|
|
3165
|
+
reasoning_effort)
|
|
3160
3166
|
cache_path.parent.mkdir(parents=True, exist_ok=True)
|
|
3161
3167
|
tmp = cache_path.with_suffix(".tmp")
|
|
3162
3168
|
import json as _json
|
package/app/core/pipeline.py
CHANGED
|
@@ -961,7 +961,7 @@ def _run_code(run, task, agents, ev, stats, mode):
|
|
|
961
961
|
and not task.get("attachments")
|
|
962
962
|
and user_context_len < 600))
|
|
963
963
|
planned = False
|
|
964
|
-
if mode
|
|
964
|
+
if mode in ("auto", "expert") and not fast_path:
|
|
965
965
|
_wait_gate(run_id, ev)
|
|
966
966
|
plan_step, plan_log = store.add_step(run_id, "plan", impl["id"], impl.get("label"),
|
|
967
967
|
note=route.get("implementer", ""))
|
|
@@ -1003,7 +1003,7 @@ def _run_code(run, task, agents, ev, stats, mode):
|
|
|
1003
1003
|
if not workflow["review_required"]:
|
|
1004
1004
|
reviewer = None
|
|
1005
1005
|
route["reviewer"] = workflow["reason"]
|
|
1006
|
-
elif mode
|
|
1006
|
+
elif mode in ("auto", "expert"):
|
|
1007
1007
|
reviewer, route["reviewer"] = router.pick_reviewer(agents, impl, "code", stats)
|
|
1008
1008
|
else:
|
|
1009
1009
|
reviewer, route["reviewer"] = _pick_reviewer_legacy(agents, impl)
|
|
@@ -1235,7 +1235,8 @@ def _run_code(run, task, agents, ev, stats, mode):
|
|
|
1235
1235
|
"# 代码任务报告:%s" % task["title"], "",
|
|
1236
1236
|
"- 结论:**%s**" % ("✅ 通过" if overall_pass else "❌ 未通过"),
|
|
1237
1237
|
"- 编排模式:%s 实现者:%s 评审者:%s" % (
|
|
1238
|
-
"
|
|
1238
|
+
{"auto": "自动", "fast": "快速", "expert": "专家",
|
|
1239
|
+
"manual": "手动"}.get(mode, mode), impl.get("label"),
|
|
1239
1240
|
reviewer.get("label") if reviewer else "按验证结果省略"),
|
|
1240
1241
|
"- 动态步骤:规划 %s;实现 %d 步;验证 %s;评审 %d 人;最多修复 %d 轮;换将 %s" % (
|
|
1241
1242
|
"独立执行" if workflow["planning"] == "llm" else "并入实现",
|
|
@@ -2941,7 +2942,8 @@ def _run_serial_review(run, task, agents, ev, stats, mode, critics, impl, route,
|
|
|
2941
2942
|
"通过" if chapters_pass else "未通过",
|
|
2942
2943
|
"通过" if global_pass else "未通过"),
|
|
2943
2944
|
"- 编排模式:%s 作者:%s 评审组:%s" % (
|
|
2944
|
-
"
|
|
2945
|
+
{"auto": "自动", "fast": "快速", "expert": "专家",
|
|
2946
|
+
"manual": "手动"}.get(mode, mode),
|
|
2945
2947
|
impl.get("label"), "、".join(a.get("label") for a in critics)),
|
|
2946
2948
|
"", "## 各章得分(章阈值 %.1f)" % threshold_ch, "",
|
|
2947
2949
|
"| 章 | 标题 | " + " | ".join(dims) + " | 均分 | 达标 | 轮次 | 字数 |",
|
|
@@ -3119,7 +3121,7 @@ def _run_content_review(run, task, agents, ev, stats, mode):
|
|
|
3119
3121
|
error="没有可用智能体", ended_at=_now())
|
|
3120
3122
|
return
|
|
3121
3123
|
if resume_ctx is not None:
|
|
3122
|
-
if mode
|
|
3124
|
+
if mode != "manual":
|
|
3123
3125
|
critics, route["critics"] = router.pick_critics(
|
|
3124
3126
|
agents, task.get("type") or "novel", stats, impl=impl)
|
|
3125
3127
|
else:
|
|
@@ -3151,7 +3153,7 @@ def _run_content_review(run, task, agents, ev, stats, mode):
|
|
|
3151
3153
|
except Exception:
|
|
3152
3154
|
return ""
|
|
3153
3155
|
|
|
3154
|
-
draft_note = route.get("author", "") if mode
|
|
3156
|
+
draft_note = route.get("author", "") if mode != "manual" else ""
|
|
3155
3157
|
|
|
3156
3158
|
# 编排者大纲:只对真实执行有意义;失败静默退回无大纲(喂入带指令的任务副本)
|
|
3157
3159
|
outline = (planner.make_review_outline(_steered_task(run_id, task))
|
package/app/core/planner.py
CHANGED
|
@@ -327,8 +327,9 @@ def make_serial_outline(task, author_agent=None, workdir=None, ev=None, log_path
|
|
|
327
327
|
# 否则 stop_reason=max_tokens、正文为空(实测 2048 全被思考吞掉)
|
|
328
328
|
# 流式回调把增量实时写进步骤日志,直连生成不再是一行标题的黑箱
|
|
329
329
|
cb = _log_streamer(log_path)
|
|
330
|
-
res = modelhub.chat(prov["id"], model, prompt,
|
|
331
|
-
max_tokens=16000, timeout=300, on_delta=cb
|
|
330
|
+
res = modelhub.chat(prov["id"], model, prompt,
|
|
331
|
+
max_tokens=16000, timeout=300, on_delta=cb,
|
|
332
|
+
reasoning_effort=_reasoning_effort(task))
|
|
332
333
|
cb.flush()
|
|
333
334
|
_log_usage("outline", "outline", task, res, model=model,
|
|
334
335
|
provider=prov.get("name", prov.get("id", "")),
|
|
@@ -431,12 +432,33 @@ def _plan_workflow(data):
|
|
|
431
432
|
return out or None
|
|
432
433
|
|
|
433
434
|
|
|
434
|
-
def _orchestrator():
|
|
435
|
-
"""编排者可用时返回 (provider, model),否则 None。"""
|
|
436
|
-
try:
|
|
437
|
-
return modelhub.resolve_orchestrator()
|
|
438
|
-
except Exception:
|
|
439
|
-
return None
|
|
435
|
+
def _orchestrator():
|
|
436
|
+
"""编排者可用时返回 (provider, model),否则 None。"""
|
|
437
|
+
try:
|
|
438
|
+
return modelhub.resolve_orchestrator()
|
|
439
|
+
except Exception:
|
|
440
|
+
return None
|
|
441
|
+
|
|
442
|
+
|
|
443
|
+
def _reasoning_effort(task):
|
|
444
|
+
"""把任务级思考程度映射为直连编排者支持的 reasoning 参数。"""
|
|
445
|
+
task = task if isinstance(task, dict) else {}
|
|
446
|
+
selected = str(task.get("thinking") or "standard").strip().lower()
|
|
447
|
+
if selected in ("low", "standard", "high"):
|
|
448
|
+
return {"low": "low", "standard": "medium", "high": "high"}[selected]
|
|
449
|
+
mode = str(task.get("mode") or "auto").lower()
|
|
450
|
+
difficulty = str(task.get("difficulty") or "auto").lower()
|
|
451
|
+
ttype = str(task.get("type") or "").lower()
|
|
452
|
+
try:
|
|
453
|
+
threshold = float(task.get("threshold") or 7.0)
|
|
454
|
+
except (TypeError, ValueError):
|
|
455
|
+
threshold = 7.0
|
|
456
|
+
if mode == "fast" or difficulty == "easy" or threshold <= 6.0:
|
|
457
|
+
return "low"
|
|
458
|
+
if (mode == "expert" or difficulty == "hard" or threshold >= 8.5
|
|
459
|
+
or ttype in ("research", "tech_proposal", "serial_novel")):
|
|
460
|
+
return "high"
|
|
461
|
+
return "medium"
|
|
440
462
|
|
|
441
463
|
|
|
442
464
|
def make_code_plan(task, planner_agent, workdir, ev=None, resume=None, log_path=None):
|
|
@@ -479,7 +501,8 @@ def _orch_code_plan(task, prov, model, log_path=None):
|
|
|
479
501
|
.replace("__GOAL__", task["goal"])
|
|
480
502
|
.replace("__CONTEXT__", task.get("context") or "(无)")
|
|
481
503
|
.replace("__VERIFY__", task.get("verify_command") or "(未配置)")),
|
|
482
|
-
max_tokens=8000, timeout=300, on_delta=cb
|
|
504
|
+
max_tokens=8000, timeout=300, on_delta=cb,
|
|
505
|
+
reasoning_effort=_reasoning_effort(task))
|
|
483
506
|
cb.flush()
|
|
484
507
|
_log_usage("plan", "plan", task, res, model=model,
|
|
485
508
|
provider=prov.get("name", prov.get("id", "")))
|
|
@@ -507,7 +530,8 @@ def make_review_outline(task):
|
|
|
507
530
|
(REVIEW_OUTLINE_PROMPT
|
|
508
531
|
.replace("__GOAL__", task["goal"])
|
|
509
532
|
.replace("__CONTEXT__", task.get("context") or "(无)")),
|
|
510
|
-
max_tokens=8000, timeout=300
|
|
533
|
+
max_tokens=8000, timeout=300,
|
|
534
|
+
reasoning_effort=_reasoning_effort(task))
|
|
511
535
|
_log_usage("outline", "outline", task, res, model=model,
|
|
512
536
|
provider=prov.get("name", prov.get("id", "")))
|
|
513
537
|
if not res["ok"]:
|
package/app/core/repeat_guard.py
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
监听 tools/post-execute,按 (tool, canonical_args) 跟踪,3/5/8 阈值注入 user-message 提醒。
|
|
7
7
|
|
|
8
8
|
Tutti 的 "tool" 对应到 "step role","canonical_args" 对应到 prompt 模板指纹。
|
|
9
|
-
|
|
9
|
+
步骤级调用代价远高于普通工具调用,因此第 3 次提醒、第 5 次强制停止。
|
|
10
10
|
"""
|
|
11
11
|
from __future__ import annotations
|
|
12
12
|
|
|
@@ -18,7 +18,7 @@ import time
|
|
|
18
18
|
log = logging.getLogger(__name__)
|
|
19
19
|
|
|
20
20
|
# 渐进阈值:第 N 次命中时给提醒;最后一次及以上时强制停止。
|
|
21
|
-
DEFAULT_THRESHOLDS = (
|
|
21
|
+
DEFAULT_THRESHOLDS = (3, 5)
|
|
22
22
|
|
|
23
23
|
REMINDER_TEMPLATE = (
|
|
24
24
|
"⚠️ 提醒:上一步使用了几乎相同的输入(已连续 {count} 次调用 {role})。"
|
|
@@ -121,4 +121,4 @@ class RepeatGuard:
|
|
|
121
121
|
|
|
122
122
|
|
|
123
123
|
# 模块级默认实例(pipeline 接线用;run_id 入键天然隔离多任务,测试无需替换)
|
|
124
|
-
guard = RepeatGuard()
|
|
124
|
+
guard = RepeatGuard()
|
package/app/core/store.py
CHANGED
|
@@ -486,6 +486,15 @@ def load_all():
|
|
|
486
486
|
|
|
487
487
|
# ---------------------------------------------------------------- 运行(含管理操作)
|
|
488
488
|
|
|
489
|
+
_RUN_ESTIMATOR = None
|
|
490
|
+
|
|
491
|
+
|
|
492
|
+
def set_run_estimator(estimator):
|
|
493
|
+
"""由应用入口注入 ETA 估算器,保持存储层不反向依赖用量模块。"""
|
|
494
|
+
global _RUN_ESTIMATOR
|
|
495
|
+
_RUN_ESTIMATOR = estimator if callable(estimator) else None
|
|
496
|
+
|
|
497
|
+
|
|
489
498
|
def create_run(kind, title, task_id=None, entry_id=None, op=None):
|
|
490
499
|
run = {
|
|
491
500
|
"id": _new_id("r" if kind == "orchestration" else "m"),
|
|
@@ -501,8 +510,7 @@ def create_run(kind, title, task_id=None, entry_id=None, op=None):
|
|
|
501
510
|
if kind == "orchestration" and task_id:
|
|
502
511
|
task = get_task(task_id) or {}
|
|
503
512
|
try:
|
|
504
|
-
|
|
505
|
-
eta = usage.estimate(
|
|
513
|
+
eta = _RUN_ESTIMATOR(
|
|
506
514
|
task_type=task.get("type") or "", mode=task.get("mode") or "auto",
|
|
507
515
|
thinking=task.get("thinking") or "standard",
|
|
508
516
|
rounds=task.get("rounds"))
|
package/app/core/task_compile.py
CHANGED
|
@@ -103,8 +103,9 @@ def code_workflow(task, difficulty, plan=None, mode="auto", planned=True):
|
|
|
103
103
|
risk_text = " ".join(str(task.get(k) or "") for k in ("title", "goal", "context")).lower()
|
|
104
104
|
high_risk = bool(any(k in risk_text for k in (
|
|
105
105
|
"安全", "权限", "鉴权", "认证", "加密", "密钥", "注入", "迁移", "数据库",
|
|
106
|
-
"并发", "竞态", "支付", "
|
|
107
|
-
"security", "permission", "auth", "migration",
|
|
106
|
+
"并发", "竞态", "支付", "架构", "公共接口", "public api", "公共 api",
|
|
107
|
+
"breaking change", "security", "permission", "auth", "migration",
|
|
108
|
+
"concurrency", "architecture")))
|
|
108
109
|
fast_mode = mode == "fast"
|
|
109
110
|
expert_mode = mode == "expert"
|
|
110
111
|
easy_auto = (mode == "auto" and difficulty == "easy") or fast_mode
|
package/app/main.py
CHANGED
|
@@ -2386,6 +2386,7 @@ def main():
|
|
|
2386
2386
|
except Exception:
|
|
2387
2387
|
pass
|
|
2388
2388
|
from core import usage
|
|
2389
|
+
store.set_run_estimator(usage.estimate)
|
|
2389
2390
|
_step("正在回填用量台账…")
|
|
2390
2391
|
n_bf = usage.backfill_from_runs() # 历史运行 token 回填台账(幂等,仅补缺失步骤)
|
|
2391
2392
|
if n_bf:
|
|
@@ -2536,7 +2537,8 @@ def main():
|
|
|
2536
2537
|
|
|
2537
2538
|
print("[CodeBee] 本机 http://127.0.0.1:%d" % args.port, flush=True)
|
|
2538
2539
|
print("[CodeBee] 数据目录 %s" % paths.DATA_DIR, flush=True)
|
|
2539
|
-
|
|
2540
|
+
# Windows 的默认 GBK 控制台无法编码部分符号,启动日志必须保持控制台安全。
|
|
2541
|
+
print("[CodeBee] [OK] 已就绪,浏览器即将自动打开;不要关闭本窗口。", flush=True)
|
|
2540
2542
|
try:
|
|
2541
2543
|
import faulthandler
|
|
2542
2544
|
faulthandler.cancel_dump_traceback_later() # 启动完成,看门狗退役(否则运行期每 20s 误报堆栈)
|