codebee 0.1.59 → 0.1.61
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 +4 -3
- package/app/core/branching.py +9 -2
- package/app/core/manager.py +1996 -1950
- package/app/core/pipeline.py +44 -15
- package/app/ui/app.js +8 -2
- package/app/ui/i18n.js +2711 -2709
- package/app/ui/style.css +7 -0
- package/package.json +1 -1
package/app/core/pipeline.py
CHANGED
|
@@ -4114,6 +4114,23 @@ def _write_task_plan(task, workdir, plan):
|
|
|
4114
4114
|
return ""
|
|
4115
4115
|
|
|
4116
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
|
+
|
|
4117
4134
|
def execute_run(run_id):
|
|
4118
4135
|
run = store.get_run(run_id)
|
|
4119
4136
|
if not run:
|
|
@@ -4157,23 +4174,35 @@ def execute_run(run_id):
|
|
|
4157
4174
|
task["difficulty"] = task_spec["difficulty"]
|
|
4158
4175
|
# 同理,历史任务可能保存非法/过期 engine;执行以编译后的流程引擎为准。
|
|
4159
4176
|
task["engine"] = task_spec["engine"]
|
|
4160
|
-
#
|
|
4161
|
-
#
|
|
4162
|
-
#
|
|
4163
|
-
#
|
|
4177
|
+
# 同工作目录互斥(2026-09-23 用户拍板「不能并行就要限制」):任务计划
|
|
4178
|
+
# (.codebee/task_plan.md)、项目记忆与任务分支检出都是工作目录级共享状态,
|
|
4179
|
+
# 两任务同目录并行会互相覆盖/干扰(禅道双单实案:A 的评审 diff 混进 B 的
|
|
4180
|
+
# 任务计划)。后来者回滚 queued 并挂 60s 重试 Timer——先来者完成后的
|
|
4181
|
+
# 下一拍自动放行(「将于 HH:MM 自动续跑」对用户可见)。
|
|
4164
4182
|
try:
|
|
4165
|
-
|
|
4166
|
-
|
|
4167
|
-
|
|
4168
|
-
|
|
4169
|
-
|
|
4170
|
-
|
|
4171
|
-
|
|
4172
|
-
|
|
4173
|
-
|
|
4174
|
-
|
|
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
|
|
4175
4204
|
except Exception:
|
|
4176
|
-
pass
|
|
4205
|
+
pass # 检测失败不挡主流程
|
|
4177
4206
|
# 代码版本检出:任务指定了基线版本时,先检出任务分支 tutti/<task-id> 再跑流水线。
|
|
4178
4207
|
# 显式意图不容静默降级——仓库缺失/脏工作区/引用不存在一律中止运行并报错,
|
|
4179
4208
|
# 绝不带着用户未提交改动切分支、也不悄悄退回当前 HEAD。
|
package/app/ui/app.js
CHANGED
|
@@ -988,8 +988,13 @@ function runStatusText(run) {
|
|
|
988
988
|
if (!isNaN(at) && Date.now() < at)
|
|
989
989
|
return t("将于 {0} 自动续跑", String(run.resume_enqueue_at).slice(11, 16));
|
|
990
990
|
}
|
|
991
|
-
|
|
991
|
+
const txt = { queued: t("排队中"), running: t("运行中"), done: t("完成"),
|
|
992
992
|
failed: t("失败"), cancelled: t("已取消"), timeout: t("超时") }[st] || st;
|
|
993
|
+
// 「跑完」≠「通过」:done 但评审未过(verdict.pass=false)必须一眼可辨——
|
|
994
|
+
// 此前一律显示「完成」,用户以为修好了(2026-09-23 禅道双单实案反馈)
|
|
995
|
+
if (st === "done" && run && run.verdict && run.verdict.pass === false)
|
|
996
|
+
return txt + t("·未达标");
|
|
997
|
+
return txt;
|
|
993
998
|
}
|
|
994
999
|
|
|
995
1000
|
/* 运行错误来源徽标:错误文案以「超时」打头(runner 统一格式)时标 TIMEOUT,
|
|
@@ -4773,7 +4778,8 @@ async function renderRunDetail() {
|
|
|
4773
4778
|
if (!rdCurrent(id, token) || fetchSeq !== S._rdFetchSeq) return; // 拉取期间已切走/被更新请求取代
|
|
4774
4779
|
$("rd-title").textContent = run.title;
|
|
4775
4780
|
const chip = $("rd-status");
|
|
4776
|
-
chip.className = "chip " + run.status
|
|
4781
|
+
chip.className = "chip " + run.status +
|
|
4782
|
+
(run.status === "done" && run.verdict && run.verdict.pass === false ? " unqualified" : "");
|
|
4777
4783
|
chip.textContent = runStatusText(run);
|
|
4778
4784
|
const active = run.status === "queued" || run.status === "running";
|
|
4779
4785
|
$("btn-cancel").classList.toggle("hidden", !active);
|