codebee 0.1.58 → 0.1.60
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 +11 -0
- package/README.md +2 -4
- package/app/core/pipeline.py +83 -4
- package/app/core/runner.py +2 -1
- package/app/ui/app.js +13968 -13951
- package/app/ui/i18n.js +2 -0
- package/app/ui/style.css +6118 -6106
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -24,6 +24,17 @@ README 元数据带回,供老版本在「发现新版本」时展示新版更
|
|
|
24
24
|
|
|
25
25
|
- **首选 KEY 超限自动切备用了**:同厂商多密钥此前只认欠费类文案——首选 KEY 撞上 429 限流(智谱「并发数超过限制」「每分钟Token数已超过上限」、claude 的 rate_limit_error、codex 的 "429 Too Many Requests" 等)只记一笔错误、不进冷却,每个新步骤仍从超限的首选 KEY 重新烧起。现在限流文案与欠费同口径:进冷却 30 分钟、密钥镜像自动切下一把、链展开跳过冷却中的 KEY(到期或「恢复」按钮一键回首选)。顺带补一处漏账:无显式链时「换模型回退」的链条目不带 KEY 标记,这类重试失败此前完全不记 KEY 账。
|
|
26
26
|
|
|
27
|
+
## v0.1.60(2026-09-23)
|
|
28
|
+
|
|
29
|
+
- 同工作目录并行硬限制:两个任务共用同一工作目录时,后来者自动排队等待(每 60 秒重试,先来者完成自动放行)——不再只是告警,彻底杜绝任务计划/项目记忆/分支检出互相覆盖(禅道双单实案)
|
|
30
|
+
- 工作目录被占用时任务卡片显示「将于 HH:MM 自动续跑」,排队原因可见
|
|
31
|
+
|
|
32
|
+
## v0.1.59(2026-09-23)
|
|
33
|
+
|
|
34
|
+
- 评审器故障不再伪装「评审未通过」:评审器鉴权失效/崩溃时(实案:Invalid API Key 整晚空转修复轮),任务以「评审器故障(评审未执行)」明确收口,不再烧无意义的修复轮
|
|
35
|
+
- 同工作目录并行告警:两个任务共用同一工作目录时(任务计划/项目记忆/分支检出会互相覆盖),运行详情顶部显示⚠警告
|
|
36
|
+
- 后台页签让出 SSE 连接:多标签打开不再把前端接口全部挤到超时
|
|
37
|
+
|
|
27
38
|
## v0.1.58(2026-09-23)
|
|
28
39
|
|
|
29
40
|
- 修复「服务过一会儿就访问不到」:测试实例(自动化测试启动的服务)会通过启动清场误杀真实服务并鸠占鹊巢——现在测试实例拒绝拉起真实端口服务、启动清场自动跳过、禁止使用默认端口 8765
|
package/README.md
CHANGED
|
@@ -21,11 +21,9 @@ Kimi Code、MiMo Code、Grok Build、Pi、DeepSeek Harness……),提供
|
|
|
21
21
|
不会把你的任务内容交给任何第三方。
|
|
22
22
|
|
|
23
23
|
<!-- relnotes:start -->
|
|
24
|
-
### 最新版更新内容(v0.1.
|
|
24
|
+
### 最新版更新内容(v0.1.60)
|
|
25
25
|
|
|
26
|
-
-
|
|
27
|
-
- 重复调用守卫判死现在真正终态生效(不再继续空转烧重试)
|
|
28
|
-
- 任务详情历史运行分页 + 打开提速
|
|
26
|
+
- 同工作目录并行硬限制:后来任务自动排队等待(60 秒重试、先来者完成自动放行),不再互相覆盖
|
|
29
27
|
<!-- relnotes:end -->
|
|
30
28
|
|
|
31
29
|
---
|
package/app/core/pipeline.py
CHANGED
|
@@ -818,6 +818,12 @@ def _run_verify(run_id, task, workdir, ev):
|
|
|
818
818
|
return ok, True
|
|
819
819
|
|
|
820
820
|
|
|
821
|
+
# 评审器鉴权/致命故障签名:命中即「评审未执行」,不得伪装成质量未通过
|
|
822
|
+
_AUTH_RE = re.compile(
|
|
823
|
+
r"Invalid API Key|invalid api key|API [Kk]ey 不可用|API key not valid|"
|
|
824
|
+
r"unauthorized|Unauthorized|401 Unauthorized|鉴权失败|认证失败", re.I)
|
|
825
|
+
|
|
826
|
+
|
|
821
827
|
def _run_review(run_id, task, workdir, reviewer, ev):
|
|
822
828
|
diff = _git_diff(workdir)
|
|
823
829
|
prompt = (CODE_REVIEW_PROMPT
|
|
@@ -830,11 +836,31 @@ def _run_review(run_id, task, workdir, reviewer, ev):
|
|
|
830
836
|
images=_task_images(task, workdir))
|
|
831
837
|
if reviewer.get("mode") == "mock":
|
|
832
838
|
return mocks.review(task, True)
|
|
839
|
+
# 评审器自身故障 ≠ 评审未通过:2026-09-23 禅道双单实案——评审器 Key 失效
|
|
840
|
+
# (mimo 退出码 0 但输出 Invalid API Key),被解析成「评审未通过」整晚空转
|
|
841
|
+
# 修复轮。鉴权/崩溃签名命中时返回 reviewer_error,调用方以明确错误收口。
|
|
842
|
+
_head = (res.get("text") or "")[:2000]
|
|
843
|
+
_auth_hit = bool(_AUTH_RE.search(_head)) or bool((res.get("raw") or {}).get("auth_error"))
|
|
844
|
+
if not res.get("ok") or _auth_hit:
|
|
845
|
+
_why = (res.get("error") or "").strip() or _head[:200]
|
|
846
|
+
return {"pass": False, "scores": {}, "issues": [], "reviewer_error": True,
|
|
847
|
+
"summary": "评审器执行失败(评审未执行):%s" % _why[:280]}
|
|
833
848
|
parsed = runner.extract_json(res.get("text") or "")
|
|
834
|
-
if
|
|
835
|
-
return
|
|
836
|
-
|
|
837
|
-
|
|
849
|
+
if isinstance(parsed, dict):
|
|
850
|
+
return parsed
|
|
851
|
+
# 评审 JSON 解析失败 → 第 5 道网(借鉴连载评审的 extract_scores_from_text):
|
|
852
|
+
# 模型偶尔在 JSON 前后加说明文字或格式不合法,但分数仍散落在文本中。
|
|
853
|
+
# 此前直接判「无法解析」→ pass=False 空分 → 白白触发修复轮(修复者
|
|
854
|
+
# 只拿到一条报错文本,无从修起)。先尝试从原文提取分数;至少拿到分
|
|
855
|
+
# 数就能正确判定过/不过,避免把「格式错」当「质量差」误触发修复。
|
|
856
|
+
scores = runner.extract_scores_from_text(res.get("text") or "")
|
|
857
|
+
if scores:
|
|
858
|
+
# 有分无 issue:不触发修复循环(没有可修的具体问题),只记录
|
|
859
|
+
passed = all(float(v) >= 7.0 for v in scores.values())
|
|
860
|
+
return {"pass": passed, "scores": scores, "issues": [],
|
|
861
|
+
"summary": "评审 JSON 解析失败,分数从原文提取(issues 不可用)"}
|
|
862
|
+
return {"pass": False, "scores": {}, "issues": [],
|
|
863
|
+
"summary": "评审输出无法解析为 JSON:%s" % (res.get("text") or "")[:200]}
|
|
838
864
|
|
|
839
865
|
|
|
840
866
|
def _format_issues(review_json, verify_pass, verify_failed_note):
|
|
@@ -1387,6 +1413,13 @@ def _run_code(run, task, agents, ev, stats, mode):
|
|
|
1387
1413
|
if impl.get("mode") == "mock" and res["ok"]:
|
|
1388
1414
|
pass # mock 不产生真实变更
|
|
1389
1415
|
review_json, verify_pass, verify_ran = review_and_score()
|
|
1416
|
+
if review_json.get("reviewer_error"):
|
|
1417
|
+
# 评审器故障(Key 失效/崩溃):没有可修的质量问题,修复轮与换将
|
|
1418
|
+
# 都无意义(换将也走同一个失效网关)——以明确错误收口,原因可见。
|
|
1419
|
+
_why = (review_json.get("summary") or "评审器执行失败")[:300]
|
|
1420
|
+
store.update_run(run_id, expected_status="running", status="failed",
|
|
1421
|
+
ended_at=_now(), error="评审器故障(评审未执行):%s" % _why)
|
|
1422
|
+
return
|
|
1390
1423
|
passed = verify_pass and bool(review_json.get("pass"))
|
|
1391
1424
|
repairs.append({"round": round_no, "kind": "switch" if switched else (
|
|
1392
1425
|
"initial" if round_no == 0 else "repair"),
|
|
@@ -4081,6 +4114,23 @@ def _write_task_plan(task, workdir, plan):
|
|
|
4081
4114
|
return ""
|
|
4082
4115
|
|
|
4083
4116
|
|
|
4117
|
+
def _workdir_blocker(task, run_id):
|
|
4118
|
+
"""同工作目录互斥:返回占用该目录的运行中/排队任务(无则 None)。
|
|
4119
|
+
任务计划/项目记忆/任务分支检出都是工作目录级共享状态,并行会互相覆盖
|
|
4120
|
+
(2026-09-23 禅道双单实案),故同目录只允许一个任务在跑。"""
|
|
4121
|
+
wd = (task.get("workdir") or "").rstrip("/\\")
|
|
4122
|
+
if not wd:
|
|
4123
|
+
return None
|
|
4124
|
+
for t in store.list_tasks(200):
|
|
4125
|
+
if (t.get("id") == task.get("id")
|
|
4126
|
+
or t.get("status") not in ("running", "queued")
|
|
4127
|
+
or (t.get("workdir") or "").rstrip("/\\") != wd):
|
|
4128
|
+
continue
|
|
4129
|
+
if jobs._task_active_run(t.get("id"), exclude_run_id=run_id):
|
|
4130
|
+
return t
|
|
4131
|
+
return None
|
|
4132
|
+
|
|
4133
|
+
|
|
4084
4134
|
def execute_run(run_id):
|
|
4085
4135
|
run = store.get_run(run_id)
|
|
4086
4136
|
if not run:
|
|
@@ -4124,6 +4174,35 @@ def execute_run(run_id):
|
|
|
4124
4174
|
task["difficulty"] = task_spec["difficulty"]
|
|
4125
4175
|
# 同理,历史任务可能保存非法/过期 engine;执行以编译后的流程引擎为准。
|
|
4126
4176
|
task["engine"] = task_spec["engine"]
|
|
4177
|
+
# 同工作目录互斥(2026-09-23 用户拍板「不能并行就要限制」):任务计划
|
|
4178
|
+
# (.codebee/task_plan.md)、项目记忆与任务分支检出都是工作目录级共享状态,
|
|
4179
|
+
# 两任务同目录并行会互相覆盖/干扰(禅道双单实案:A 的评审 diff 混进 B 的
|
|
4180
|
+
# 任务计划)。后来者回滚 queued 并挂 60s 重试 Timer——先来者完成后的
|
|
4181
|
+
# 下一拍自动放行(「将于 HH:MM 自动续跑」对用户可见)。
|
|
4182
|
+
try:
|
|
4183
|
+
_blocker = _workdir_blocker(task, run_id)
|
|
4184
|
+
if True:
|
|
4185
|
+
_wd = (task.get("workdir") or "").rstrip("/\\")
|
|
4186
|
+
if _blocker is not None:
|
|
4187
|
+
_resume_at = time.strftime("%Y-%m-%d %H:%M:%S",
|
|
4188
|
+
time.localtime(time.time() + 60))
|
|
4189
|
+
store.update_run(run_id, expected_status="running", status="queued",
|
|
4190
|
+
started_at="", resume_enqueue_at=_resume_at,
|
|
4191
|
+
warnings=["工作目录被运行中任务「%s」占用,排队等待(每 60 秒自动重试)"
|
|
4192
|
+
% ((_blocker.get("title") or _blocker.get("id") or "")[:24])])
|
|
4193
|
+
try:
|
|
4194
|
+
store.update_task_status(task.get("id"), "queued")
|
|
4195
|
+
except Exception:
|
|
4196
|
+
pass
|
|
4197
|
+
jobs._schedule_enqueue(
|
|
4198
|
+
{"kind": "orchestration", "run_id": run_id,
|
|
4199
|
+
"task_id": task.get("id")}, 60)
|
|
4200
|
+
print("[CodeBee] 工作目录被「%s」占用:%s 排队等待,60 秒后自动重试"
|
|
4201
|
+
% ((_blocker.get("title") or _blocker.get("id") or "")[:24],
|
|
4202
|
+
task.get("title") or task.get("id")), flush=True)
|
|
4203
|
+
return
|
|
4204
|
+
except Exception:
|
|
4205
|
+
pass # 检测失败不挡主流程
|
|
4127
4206
|
# 代码版本检出:任务指定了基线版本时,先检出任务分支 tutti/<task-id> 再跑流水线。
|
|
4128
4207
|
# 显式意图不容静默降级——仓库缺失/脏工作区/引用不存在一律中止运行并报错,
|
|
4129
4208
|
# 绝不带着用户未提交改动切分支、也不悄悄退回当前 HEAD。
|
package/app/core/runner.py
CHANGED
|
@@ -224,7 +224,8 @@ def _drain_streams(proc, t_out, t_err, *, timeout=10):
|
|
|
224
224
|
# ``returncode``; otherwise long-running timeout tests can leak a live
|
|
225
225
|
# Popen handle and emit ResourceWarning during interpreter shutdown.
|
|
226
226
|
remaining = max(0.0, deadline - time.time())
|
|
227
|
-
|
|
227
|
+
# proc 可为 None(旧契约:只收线程不收割进程的调用方,tests/test_runner_drain)
|
|
228
|
+
if proc is not None and remaining > 0 and proc.poll() is None:
|
|
228
229
|
try:
|
|
229
230
|
proc.wait(timeout=remaining)
|
|
230
231
|
except (subprocess.TimeoutExpired, OSError):
|