codebee 0.1.59 → 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 CHANGED
@@ -24,6 +24,11 @@ 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
+
27
32
  ## v0.1.59(2026-09-23)
28
33
 
29
34
  - 评审器故障不再伪装「评审未通过」:评审器鉴权失效/崩溃时(实案:Invalid API Key 整晚空转修复轮),任务以「评审器故障(评审未执行)」明确收口,不再烧无意义的修复轮
package/README.md CHANGED
@@ -21,10 +21,9 @@ Kimi Code、MiMo Code、Grok Build、Pi、DeepSeek Harness……),提供
21
21
  不会把你的任务内容交给任何第三方。
22
22
 
23
23
  <!-- relnotes:start -->
24
- ### 最新版更新内容(v0.1.59)
24
+ ### 最新版更新内容(v0.1.60)
25
25
 
26
- - 评审器故障(如 Key 失效)明确报错,不再伪装成「评审未通过」空转修复轮
27
- - 同工作目录并行任务自动告警;后台标签页让出连接,多标签不再互相挤超时
26
+ - 同工作目录并行硬限制:后来任务自动排队等待(60 秒重试、先来者完成自动放行),不再互相覆盖
28
27
  <!-- relnotes:end -->
29
28
 
30
29
  ---
@@ -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
- # 同工作目录并行检测:任务计划(.codebee/task_plan.md)、项目记忆与任务分支
4161
- # 检出都是工作目录级共享状态,两任务同目录并行会互相覆盖/干扰(2026-09-23
4162
- # 禅道双单实案:A 的评审 diff 混进 B 的任务计划)。只留警告不阻断——
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
- _wd = (task.get("workdir") or "").rstrip("/\\")
4166
- _others = [t for t in store.list_tasks(200)
4167
- if t.get("id") != task.get("id")
4168
- and (t.get("workdir") or "").rstrip("/\\") == _wd
4169
- and t.get("status") in ("running", "queued") and _wd]
4170
- if _others:
4171
- _names = "、".join((t.get("title") or t.get("id") or "")[:24]
4172
- for t in _others[:3])
4173
- store.update_run(run_id, warnings=[
4174
- "工作目录与运行中任务并行:%s——任务计划/项目记忆/分支检出会互相干扰,建议错开或使用独立工作目录" % _names])
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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codebee",
3
- "version": "0.1.59",
3
+ "version": "0.1.60",
4
4
  "description": "CodeBee · 多智能体编排台 — 统一编排本机 AI 编码 CLI(Codex / Claude Code / QwenCode / OpenCode / Aider…):目标→拆解→路由→执行→验证→跨厂商评审→修复→报告。纯 Python 标准库实现,本地 Web UI。",
5
5
  "license": "MIT",
6
6
  "bin": {