codebee 0.1.5 → 0.1.7
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 +16 -0
- package/README.md +431 -422
- package/app/core/automation.py +30 -5
- package/app/core/catalog.py +23 -0
- package/app/core/health.py +12 -1
- package/app/core/jobs.py +10 -2
- package/app/core/manager.py +87 -10
- package/app/core/modelhub.py +2883 -2845
- package/app/core/pipeline.py +120 -60
- package/app/core/registry.py +4 -0
- package/app/core/remote.py +310 -303
- package/app/core/router.py +5 -4
- package/app/core/runner.py +38 -6
- package/app/core/selfupdate.py +53 -16
- package/app/core/store.py +138 -47
- package/app/core/usage.py +51 -1
- package/app/main.py +219 -23
- package/app/ui/app.js +687 -222
- package/app/ui/i18n.js +43 -6
- package/app/ui/icons/bee.svg +79 -0
- package/app/ui/index.html +59 -46
- package/app/ui/style.css +1229 -328
- package/package.json +1 -1
package/app/core/pipeline.py
CHANGED
|
@@ -273,18 +273,45 @@ def _wait_gate(run_id, ev):
|
|
|
273
273
|
time.sleep(1.0)
|
|
274
274
|
|
|
275
275
|
|
|
276
|
+
def _binding_dead_msg(agent):
|
|
277
|
+
"""死链失败文案:委托 modelhub 单一真源(告警 sync 共用同一文案)。"""
|
|
278
|
+
try:
|
|
279
|
+
from . import modelhub
|
|
280
|
+
return modelhub.binding_dead_msg(agent.get("id") or "")
|
|
281
|
+
except Exception:
|
|
282
|
+
return ("绑定链全部失效,本步判失败、不回落 CLI 本机默认——"
|
|
283
|
+
"请在「CLI 绑定」页为该 CLI 绑定已启用的供应商")
|
|
284
|
+
|
|
285
|
+
|
|
276
286
|
def _run_step(run_id, role, agent, prompt, workdir, readonly, ev, timeout=runner.DEFAULT_TIMEOUT, note="", resume=None, images=None, require_tools=False):
|
|
277
287
|
"""执行一个智能体步骤并记录。返回 runner 统一结果。"""
|
|
278
288
|
_wait_gate(run_id, ev)
|
|
279
|
-
#
|
|
280
|
-
#
|
|
281
|
-
#
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
289
|
+
# 绑定解析为空分两种(2026-09-18 起 区分对待):
|
|
290
|
+
# · 从没配过链(binding_configured=False):回落 CLI 本机默认照跑——
|
|
291
|
+
# 用户根本没在 CodeBee 里配供应商,谈不到「烧自己配的配额」;判失败
|
|
292
|
+
# 反而把用本地登录的普通用户全挡在门外(0.1.6 真实装机误伤案例)。
|
|
293
|
+
# · 配过链但全死(binding_configured=True):本步判失败不静默降级——
|
|
294
|
+
# 宁可失败不偷跑本机默认;auto 流程实现步的既有换将会接手健康 CLI。
|
|
295
|
+
# 只记在步骤备注/错误里,不再产生全局健康告警胶囊(同上案例:红胶囊
|
|
296
|
+
# 吓不到也帮不到普通用户,2026-09-18 用户拍板移除)。
|
|
297
|
+
dead_binding = (agent.get("mode") == "real"
|
|
298
|
+
and agent.get("binding_configured")
|
|
299
|
+
and not (agent.get("call_chain") or agent.get("env")))
|
|
300
|
+
dead_msg = _binding_dead_msg(agent) if dead_binding else ""
|
|
301
|
+
if dead_binding:
|
|
302
|
+
note = ((note + ";") if note else "") + "⚠ " + dead_msg
|
|
285
303
|
step, log_abs = store.add_step(run_id, role, agent["id"],
|
|
286
304
|
agent.get("label", agent["id"]), note=note)
|
|
287
305
|
start = time.time()
|
|
306
|
+
if dead_binding:
|
|
307
|
+
from .error_codes import ErrorCode
|
|
308
|
+
res = {"ok": False, "text": "", "json": None, "cost_usd": 0.0,
|
|
309
|
+
"tokens": 0, "usage": None, "error": dead_msg,
|
|
310
|
+
"error_code": ErrorCode.ENV_BLOCK,
|
|
311
|
+
"raw": {"exit_code": None}, "kind": agent.get("kind", "generic"),
|
|
312
|
+
"model": agent.get("model")}
|
|
313
|
+
_finish_step_result(run_id, step, res, role, agent, start)
|
|
314
|
+
return res
|
|
288
315
|
if agent.get("mode") == "mock":
|
|
289
316
|
time.sleep(0.3)
|
|
290
317
|
res = {"ok": True, "text": "[mock] %s" % prompt[:80], "json": None,
|
|
@@ -1601,12 +1628,19 @@ def _run_serial_review(run, task, agents, ev, stats, mode, critics, impl, route,
|
|
|
1601
1628
|
res = None
|
|
1602
1629
|
good = False
|
|
1603
1630
|
txt = ""
|
|
1631
|
+
use_prompt = prompt
|
|
1604
1632
|
for draft_attempt in range(3):
|
|
1605
1633
|
if draft_attempt:
|
|
1606
1634
|
if ev is not None and ev.is_set():
|
|
1607
1635
|
break
|
|
1608
1636
|
time.sleep(30 * draft_attempt) # 30s / 60s 退避
|
|
1609
|
-
|
|
1637
|
+
if draft_attempt and len(prompt) > 12000 and sk_block and sk_block in prompt:
|
|
1638
|
+
# 长提示词在容量受限通道(讯飞托管 35B 等)上会挂起/秒拒
|
|
1639
|
+
# ——降级重试:经验库块截到 4K 字,保留大纲/前情/本章要点
|
|
1640
|
+
# (2026-09-17 七猫实测:全量 30KB 对讯飞必挂)
|
|
1641
|
+
use_prompt = prompt.replace(
|
|
1642
|
+
sk_block, sk_block[:4000] + "\n\n(经验库已因通道容量限制精简)")
|
|
1643
|
+
res = _run_step(run_id, "draft-c%d" % i, modelhub.bind_agent(impl, difficulty), use_prompt,
|
|
1610
1644
|
step_wd, readonly=False, ev=ev, timeout=2400,
|
|
1611
1645
|
resume=resume_ctx["session"] if resume_ctx else None,
|
|
1612
1646
|
images=_task_images(task, workdir),
|
|
@@ -1626,15 +1660,23 @@ def _run_serial_review(run, task, agents, ev, stats, mode, critics, impl, route,
|
|
|
1626
1660
|
break
|
|
1627
1661
|
if ev is not None and ev.is_set():
|
|
1628
1662
|
break
|
|
1629
|
-
# 同作者重试穷尽 →
|
|
1630
|
-
#
|
|
1663
|
+
# 同作者重试穷尽 → 起草换将:按路由分序逐个试备选(最多 2 个,
|
|
1664
|
+
# 只试一个会让第二名没机会——2026-09-17 c35 实测 opencode 顶在
|
|
1665
|
+
# 前面,能干活的 kimi 永远轮不上)。连载不断档优先,风格差异交
|
|
1666
|
+
# 评审门与后续 revise 拉回。
|
|
1631
1667
|
if not good:
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1668
|
+
tried = {impl["id"], "mock-a", "mock-b"}
|
|
1669
|
+
for _alt in range(2):
|
|
1670
|
+
if good or (ev is not None and ev.is_set()):
|
|
1671
|
+
break
|
|
1672
|
+
other, other_reason = router.pick(
|
|
1673
|
+
agents, "implement", task.get("type") or "serial", None,
|
|
1674
|
+
exclude=tried)
|
|
1675
|
+
if not (other and other.get("mode") == "real"):
|
|
1676
|
+
break
|
|
1677
|
+
tried.add(other["id"])
|
|
1636
1678
|
res = _run_step(run_id, "draft-c%d" % i,
|
|
1637
|
-
modelhub.bind_agent(other, difficulty),
|
|
1679
|
+
modelhub.bind_agent(other, difficulty), use_prompt,
|
|
1638
1680
|
step_wd, readonly=False, ev=ev, timeout=2400,
|
|
1639
1681
|
images=_task_images(task, workdir),
|
|
1640
1682
|
note="起草换将 %s → %s:%s" % (
|
|
@@ -1649,6 +1691,9 @@ def _run_serial_review(run, task, agents, ev, stats, mode, critics, impl, route,
|
|
|
1649
1691
|
pass
|
|
1650
1692
|
if good:
|
|
1651
1693
|
draft_sid = "" # 换将作者无本任会话,revise 另起
|
|
1694
|
+
if not good:
|
|
1695
|
+
time.sleep(3) # 落盘竞态宽限:CLI 崩溃退出前写的文件可能晚于
|
|
1696
|
+
good, txt = _chapter_state() # 退出检查零点几秒才可见(c34 实测)
|
|
1652
1697
|
if not good:
|
|
1653
1698
|
store.update_run(run_id, status="failed",
|
|
1654
1699
|
error="第 %d 章起草失败: %s" % (i, (res or {}).get("error")), ended_at=_now())
|
|
@@ -1668,53 +1713,68 @@ def _run_serial_review(run, task, agents, ev, stats, mode, critics, impl, route,
|
|
|
1668
1713
|
# 均分最高者为正稿。变体写隔离文件 chapter-XX-vK.md,赢家改名、
|
|
1669
1714
|
# 败稿删除;变体 0 = 本任作者(revise 会话沿用),其余取跨族优先的
|
|
1670
1715
|
# 其他真实智能体,不足时同作者开新会话凑数。
|
|
1671
|
-
pool = [impl]
|
|
1672
|
-
others = [a for a in agents if a.get("mode") == "real" and a["id"] != impl["id"]]
|
|
1673
|
-
others.sort(key=lambda a: 0 if a.get("kind") != impl.get("kind") else 1)
|
|
1674
|
-
pool += others[:n_variants - 1]
|
|
1675
|
-
while len(pool) < n_variants:
|
|
1676
|
-
pool.append(impl) # 不够就同作者再开一路(新会话天然出不同稿)
|
|
1677
|
-
results = {}
|
|
1678
|
-
|
|
1679
|
-
def _draft_one(kk, agent):
|
|
1680
|
-
vfile = "chapter-%02d-v%d.md" % (i, kk)
|
|
1681
|
-
r = _run_step(run_id, "draft-c%d-v%d" % (i, kk),
|
|
1682
|
-
modelhub.bind_agent(agent, difficulty),
|
|
1683
|
-
_draft_prompt(vfile), step_wd, readonly=False, ev=ev,
|
|
1684
|
-
timeout=2400,
|
|
1685
|
-
# 赛马只在全新起草时启用(无续会话),每路都是新会话
|
|
1686
|
-
images=_task_images(task, workdir),
|
|
1687
|
-
note="赛马变体 %d/%d(%s)" % (kk + 1, len(pool), agent.get("id")))
|
|
1688
|
-
results[kk] = (vfile, agent, r)
|
|
1689
|
-
|
|
1690
|
-
threads = []
|
|
1691
|
-
for kk, agent in enumerate(pool):
|
|
1692
|
-
th = threading.Thread(target=_draft_one, args=(kk, agent),
|
|
1693
|
-
name="race-%s-c%d-v%d" % (run_id, i, kk), daemon=True)
|
|
1694
|
-
threads.append(th)
|
|
1695
|
-
th.start()
|
|
1696
|
-
for th in threads:
|
|
1697
|
-
th.join(3000)
|
|
1698
|
-
_check_cancel(ev)
|
|
1699
|
-
|
|
1700
1716
|
scored_variants = []
|
|
1701
|
-
for
|
|
1702
|
-
|
|
1703
|
-
if
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1717
|
+
for race_round in range(2):
|
|
1718
|
+
# 全变体失败(网关突发限流)→ 60s 退避重赛一轮,别一章判死
|
|
1719
|
+
if race_round:
|
|
1720
|
+
if ev is not None and ev.is_set():
|
|
1721
|
+
break
|
|
1722
|
+
time.sleep(60)
|
|
1723
|
+
for kk in range(n_variants):
|
|
1724
|
+
# 清上一轮残稿:防陈旧半成品被本轮评分误认成新成品
|
|
1725
|
+
try:
|
|
1726
|
+
os.remove(os.path.join(workdir, "chapter-%02d-v%d.md" % (i, kk)))
|
|
1727
|
+
except OSError:
|
|
1728
|
+
pass
|
|
1729
|
+
pool = [impl]
|
|
1730
|
+
others = [a for a in agents if a.get("mode") == "real" and a["id"] != impl["id"]]
|
|
1731
|
+
others.sort(key=lambda a: 0 if a.get("kind") != impl.get("kind") else 1)
|
|
1732
|
+
pool += others[:n_variants - 1]
|
|
1733
|
+
while len(pool) < n_variants:
|
|
1734
|
+
pool.append(impl) # 不够就同作者再开一路(新会话天然出不同稿)
|
|
1735
|
+
results = {}
|
|
1736
|
+
|
|
1737
|
+
def _draft_one(kk, agent):
|
|
1738
|
+
vfile = "chapter-%02d-v%d.md" % (i, kk)
|
|
1739
|
+
r = _run_step(run_id, "draft-c%d-v%d" % (i, kk),
|
|
1740
|
+
modelhub.bind_agent(agent, difficulty),
|
|
1741
|
+
_draft_prompt(vfile), step_wd, readonly=False, ev=ev,
|
|
1742
|
+
timeout=2400,
|
|
1743
|
+
# 赛马只在全新起草时启用(无续会话),每路都是新会话
|
|
1744
|
+
images=_task_images(task, workdir),
|
|
1745
|
+
note="赛马变体 %d/%d(%s)" % (kk + 1, len(pool), agent.get("id")))
|
|
1746
|
+
results[kk] = (vfile, agent, r)
|
|
1747
|
+
|
|
1748
|
+
threads = []
|
|
1749
|
+
for kk, agent in enumerate(pool):
|
|
1750
|
+
th = threading.Thread(target=_draft_one, args=(kk, agent),
|
|
1751
|
+
name="race-%s-c%d-v%d" % (run_id, i, kk), daemon=True)
|
|
1752
|
+
threads.append(th)
|
|
1753
|
+
th.start()
|
|
1754
|
+
for th in threads:
|
|
1755
|
+
th.join(3000)
|
|
1756
|
+
_check_cancel(ev)
|
|
1757
|
+
|
|
1758
|
+
scored_variants = []
|
|
1759
|
+
for kk in range(len(pool)):
|
|
1760
|
+
vfile, agent, r = results.get(kk, (None, None, None))
|
|
1761
|
+
if vfile is None:
|
|
1762
|
+
continue
|
|
1763
|
+
txt = _read_variant(workdir, i, kk)
|
|
1764
|
+
ok_text = txt and _wc(txt) >= int(wpc * 0.6)
|
|
1765
|
+
if r is not None and not r["ok"] and not ok_text:
|
|
1766
|
+
continue # 这一路彻底失败(无成品也不够长)
|
|
1767
|
+
if not ok_text:
|
|
1768
|
+
continue
|
|
1769
|
+
cj_map, sc, sids2 = run_critique(
|
|
1770
|
+
txt, 1, note_extra="(本稿为同章赛马变体 %d/%d,只评这一份)" % (kk + 1, len(pool)))
|
|
1771
|
+
m = means_of(cj_map)
|
|
1772
|
+
avg = round(sum(m.values()) / max(1, len(m)), 2) if m else 0.0
|
|
1773
|
+
scored_variants.append({"variant": kk, "agent": agent.get("id"),
|
|
1774
|
+
"file": vfile, "means": m, "avg": avg,
|
|
1775
|
+
"cj": cj_map, "scored": sc, "sids": sids2})
|
|
1776
|
+
if scored_variants:
|
|
1777
|
+
break
|
|
1718
1778
|
if not scored_variants:
|
|
1719
1779
|
store.update_run(run_id, status="failed",
|
|
1720
1780
|
error="第 %d 章赛马全部变体起草失败" % i, ended_at=_now())
|
package/app/core/registry.py
CHANGED
|
@@ -66,6 +66,10 @@ def _build_agent(entry):
|
|
|
66
66
|
# 小时级 token 配额(可选,0/缺省=不限):路由时对本小时用量超标的
|
|
67
67
|
# 智能体降权(munder-difflin 式配额感知),订阅型 CLI 不至于被单任务打爆
|
|
68
68
|
"quota_tokens_per_hour": int(orch.get("quota_tokens_per_hour") or 0),
|
|
69
|
+
# 整包透传 orch:per-agent 运行时开关(timeout_ms 5E、stall_timeout_s
|
|
70
|
+
# 看门狗等)都在 runner 侧读取——此前只挑字段,catalog 上配的
|
|
71
|
+
# timeout_ms/stall 根本流不到 runner(2026-09-17 连载 c35 实测)
|
|
72
|
+
"orch": orch,
|
|
69
73
|
}
|
|
70
74
|
|
|
71
75
|
|