codebee 0.1.27 → 0.1.28

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
@@ -6,6 +6,12 @@ README 元数据带回,供老版本在「发现新版本」时展示新版更
6
6
 
7
7
  ## 未发布
8
8
 
9
+ ## v0.1.28(2026-09-21)
10
+
11
+ ### 🛠 问题修复
12
+
13
+ - 「关于与更新」页的版本信息与更新说明改为左对齐阅读,不再整块居中(加载占位的居中样式误留在了正式内容上)。
14
+
9
15
  ## v0.1.27(2026-09-21)
10
16
 
11
17
  ### ✨ 新功能
package/README.md CHANGED
@@ -21,6 +21,11 @@ Kimi Code、MiMo Code、Grok Build、Pi、DeepSeek Harness……),提供
21
21
  不会把你的任务内容交给任何第三方。
22
22
 
23
23
  <!-- relnotes:start -->
24
+ ### 🛠 问题修复 · Fixes
25
+
26
+ - 「关于与更新」页的版本信息与更新说明改为左对齐阅读,不再整块居中。
27
+ - The version info and release notes on the About page are now left-aligned for easier reading.
28
+
24
29
  ### ✨ 新功能 · New
25
30
 
26
31
  - 连载任务防穿帮:新增「资源账本」——每章评审自动登记新出现的道具、伤情、承诺与伏笔,写下一章前自动注入提醒核对。
@@ -31,18 +31,18 @@ _LOCK = threading.RLock()
31
31
  _STATE = {"detected": {}, "versions": {}, "detect_ts": 0.0, "detect_ev": None}
32
32
 
33
33
  # 能自动写入默认模型的 config.format(其余格式只能手动编辑)
34
- _WRITABLE_FORMATS = ("toml-line", "toml-section", "json", "json-path", "jsonc",
35
- "yaml-line")
36
-
37
-
38
- def _read_text(path, preserve_newlines=False):
39
- """读取文本并及时关闭句柄;改写配置前可保留原始换行符。"""
40
- newline = "" if preserve_newlines else None
41
- with open(path, encoding="utf-8", errors="replace", newline=newline) as fh:
42
- return fh.read()
43
-
44
-
45
- def _expand(p):
34
+ _WRITABLE_FORMATS = ("toml-line", "toml-section", "json", "json-path", "jsonc",
35
+ "yaml-line")
36
+
37
+
38
+ def _read_text(path, preserve_newlines=False):
39
+ """读取文本并及时关闭句柄;改写配置前可保留原始换行符。"""
40
+ newline = "" if preserve_newlines else None
41
+ with open(path, encoding="utf-8", errors="replace", newline=newline) as fh:
42
+ return fh.read()
43
+
44
+
45
+ def _expand(p):
46
46
  return os.path.abspath(os.path.expanduser(os.path.expandvars(p)))
47
47
 
48
48
 
@@ -482,7 +482,7 @@ def read_model(entry):
482
482
  if not path or not os.path.isfile(path) or not cfg.get("format"):
483
483
  return None
484
484
  try:
485
- text = _read_text(path)
485
+ text = _read_text(path)
486
486
  except Exception:
487
487
  return None
488
488
  if cfg["format"] == "toml-line":
@@ -554,7 +554,7 @@ def write_model(entry, model):
554
554
  # mimo(mimocode.jsonc)有注释,整体重解析会丢注释——复用
555
555
  # _jsonc_set 做就地片段改写(只动目标键,其余原样保留)
556
556
  keys = (cfg.get("model_key") or "model").split(".")
557
- text = _read_text(path, preserve_newlines=True)
557
+ text = _read_text(path, preserve_newlines=True)
558
558
  new_text, ok = _jsonc_set(text, tuple(keys),
559
559
  json.dumps(model, ensure_ascii=False))
560
560
  if not ok:
@@ -562,7 +562,7 @@ def write_model(entry, model):
562
562
  "error": "jsonc 结构异常,未能就地写入 %s(已避免覆盖)" % path}
563
563
  Path(path).write_bytes(new_text.encode("utf-8"))
564
564
  elif fmt == "toml-line":
565
- text = _read_text(path)
565
+ text = _read_text(path)
566
566
  new_line = 'model = "%s"' % model
567
567
  if re.search(r'(?m)^\s*model\s*=\s*"[^"]*"', text):
568
568
  text = re.sub(r'(?m)^\s*model\s*=\s*"[^"]*"', new_line, text)
@@ -570,19 +570,19 @@ def write_model(entry, model):
570
570
  text = text.rstrip("\n") + "\n" + new_line + "\n"
571
571
  Path(path).write_bytes(text.encode("utf-8"))
572
572
  elif fmt == "toml-section":
573
- text = _read_text(path, preserve_newlines=True)
573
+ text = _read_text(path, preserve_newlines=True)
574
574
  text = _toml_write_value(text, *_dotted_key(cfg), value=model)
575
575
  Path(path).write_bytes(text.encode("utf-8"))
576
576
  elif fmt == "yaml-line":
577
577
  # newline="" 关掉通用换行转换:文本层面看不出 \r\n 就会被静默改写成 LF,
578
578
  # 用户的 Windows 配置不该因为写个模型名而整篇换行符被替换
579
- text = _read_text(path, preserve_newlines=True)
579
+ text = _read_text(path, preserve_newlines=True)
580
580
  text = _yaml_write_value(text, *_yaml_model_path(cfg), value=model)
581
581
  Path(path).write_bytes(text.encode("utf-8"))
582
582
  elif fmt == "json-path":
583
583
  # openclaw(agents.defaults.model.primary):默认模型藏在嵌套对象里,
584
584
  # 且 openclaw 对未知顶层键直接拒绝启动——绝不能写顶层 "model"
585
- text = _read_text(path, preserve_newlines=True)
585
+ text = _read_text(path, preserve_newlines=True)
586
586
  try:
587
587
  data = json.loads(text) if text.strip() else {}
588
588
  except Exception:
@@ -604,7 +604,7 @@ def write_model(entry, model):
604
604
  json.dumps(data, ensure_ascii=False, indent=2).encode("utf-8"))
605
605
  else:
606
606
  try:
607
- data = json.loads(_read_text(path))
607
+ data = json.loads(_read_text(path))
608
608
  except FileNotFoundError:
609
609
  return {"ok": False,
610
610
  "error": "配置文件尚未生成(%s 首次运行后才有),暂无法写入" % path}
@@ -662,7 +662,7 @@ def _launch_log_path(entry):
662
662
  def _best_url(log_path, port):
663
663
  """从启动日志提取该端口的信任 URL(含 token 优先)。无日志/未匹配返回 None。"""
664
664
  try:
665
- text = _read_text(str(log_path))
665
+ text = _read_text(str(log_path))
666
666
  except Exception:
667
667
  return None
668
668
  urls = re.findall(r"https?://[^\s\"'<>]+", text)
@@ -762,7 +762,7 @@ def _sync_dsh_settings(entry, model, base_url):
762
762
  return "dsh 配置路径越出用户主目录,已拒绝"
763
763
  try:
764
764
  if os.path.isfile(path):
765
- text = _read_text(path, preserve_newlines=True)
765
+ text = _read_text(path, preserve_newlines=True)
766
766
  else:
767
767
  Path(path).parent.mkdir(parents=True, exist_ok=True)
768
768
  text = ""
@@ -785,7 +785,7 @@ def _dsh_selfcheck_model(entry):
785
785
  if not path or not os.path.isfile(path):
786
786
  return None, ""
787
787
  try:
788
- text = _read_text(path, preserve_newlines=True)
788
+ text = _read_text(path, preserve_newlines=True)
789
789
  except Exception:
790
790
  return None, ""
791
791
  cur = _yaml_read_value(text, "agent-default-model", "model")
@@ -809,7 +809,7 @@ def _dsh_key_present():
809
809
  return True
810
810
  try:
811
811
  envfile = os.path.join(os.path.expanduser("~"), ".dsh", ".env")
812
- return "DEEPSEEK_API_KEY" in _read_text(envfile)
812
+ return "DEEPSEEK_API_KEY" in _read_text(envfile)
813
813
  except Exception:
814
814
  return False
815
815
 
@@ -915,7 +915,7 @@ def _sync_codex_settings(entry, model, cp):
915
915
  ("wire_api", q(cp.get("wire_api", "responses")))]
916
916
  try:
917
917
  if os.path.isfile(path):
918
- text = _read_text(path, preserve_newlines=True)
918
+ text = _read_text(path, preserve_newlines=True)
919
919
  else:
920
920
  Path(path).parent.mkdir(parents=True, exist_ok=True)
921
921
  text = ""
@@ -1122,7 +1122,7 @@ def _sync_settings_env(path, updates, remove_keys=()):
1122
1122
  不覆盖;改动前 .bak。返回错误串或 None。path 必须已过主目录围栏校验。"""
1123
1123
  try:
1124
1124
  if os.path.isfile(path):
1125
- text = _read_text(path)
1125
+ text = _read_text(path)
1126
1126
  try:
1127
1127
  data = json.loads(text)
1128
1128
  except Exception:
@@ -1242,7 +1242,7 @@ def _sync_opencode_settings(entry, model, prov):
1242
1242
  errs.append("路径越出主目录:%s" % path)
1243
1243
  continue
1244
1244
  if os.path.isfile(path):
1245
- text = _read_text(path, preserve_newlines=True)
1245
+ text = _read_text(path, preserve_newlines=True)
1246
1246
  else:
1247
1247
  Path(path).parent.mkdir(parents=True, exist_ok=True)
1248
1248
  text = ""
@@ -1333,7 +1333,7 @@ def _sync_kimi_settings(entry, model, prov):
1333
1333
  Path(path).parent.mkdir(parents=True, exist_ok=True)
1334
1334
  text = ""
1335
1335
  if os.path.isfile(path):
1336
- text = _read_text(path, preserve_newlines=True)
1336
+ text = _read_text(path, preserve_newlines=True)
1337
1337
  shutil.copyfile(path, path + ".bak")
1338
1338
  # 移除旧托管块与旧顶层键(防重复/防键落进别的表)
1339
1339
  text = _re.sub(r"# >>> CodeBee managed.*?# <<< CodeBee managed <<<\n?",
@@ -1410,7 +1410,7 @@ def _sync_agent_injection(entry, binding):
1410
1410
  if entry["id"] in _NO_CHANNEL_HINT:
1411
1411
  if binding.get("env"):
1412
1412
  return "已按绑定注入 env,但该 CLI 未必认 CodeBee 的凭据通道,打开后若要求登录请在其界面内登录"
1413
- return "未指定可用供应商:打开后使用 CLI 自带登录;需要固定注入请到「模型调度(可选)」页指定"
1413
+ return "未指定可用供应商:打开后使用 CLI 自带登录;需要固定注入请到「模型调度(可选)」页指定"
1414
1414
  return None
1415
1415
 
1416
1416
 
@@ -1418,8 +1418,9 @@ def _sync_launch_model(entry, binding):
1418
1418
  """打开前把「CLI 绑定」页选中的模型落到该 CLI 自己的配置文件——保证
1419
1419
  交互/网页版启动即选中绑定模型(绑定页是运行时模型唯一真源,目录页的
1420
1420
  「默认模型」只是手动快照,会滞后)。dsh 额外同步端点与 models 目录;
1421
- codex 额外落 provider 段(交互 TUI 只认 config.toml,不认编排的 -c 覆盖
1422
- 与 ORCH_API_KEY env)。返回给用户看的同步笔记列表。"""
1421
+ codex 已改为不落盘(2026-09-21 起:全局 model_provider 归 CC Switch/
1422
+ 用户管理,编排任务的绑定走 runner 的 -c 一次性注入)。返回给用户
1423
+ 看的同步笔记列表。"""
1423
1424
  notes = []
1424
1425
  model = (binding.get("model") or "").strip()
1425
1426
  prov = binding.get("provider") or {}
@@ -1427,6 +1428,14 @@ def _sync_launch_model(entry, binding):
1427
1428
  is_dsh = entry["id"] in ("deepseek-harness", "dsh")
1428
1429
  is_codex = entry["id"] in ("codex-cli", "codex")
1429
1430
  cp = binding.get("codex_provider")
1431
+ if is_codex:
1432
+ # codex 全局配置不落盘(2026-09-21 用户决策):全局 model_provider 交还
1433
+ # CC Switch/用户手动管理,一键打开不再顶掉外部选择。编排任务不受影响
1434
+ # ——runner 的 -c 一次性注入仍带绑定供应商;交互 TUI 打开即用外部当前
1435
+ # 配置,想用绑定供应商起编排任务即可。
1436
+ notes.append("codex 配置保持外部管理(绑定 %s 仅用于编排 -c 注入,未写 config.toml)"
1437
+ % (cp.get("name") or "orch"))
1438
+ return notes
1430
1439
  if model and fmt in _WRITABLE_FORMATS:
1431
1440
  w = write_model(entry, model)
1432
1441
  notes.append("模型已同步为 %s" % w["model"] if w.get("ok")
@@ -1530,7 +1539,7 @@ def launch(entry, open_browser=True):
1530
1539
  notes = _sync_launch_model(entry, binding)
1531
1540
  if is_dsh and not (binding.get("env") or {}) and not _dsh_key_present():
1532
1541
  notes.append("未发现 dsh 密钥:打开后可能需在 dsh 内登录配置;"
1533
- "要打开即用,请到「模型调度(可选)」页给 DeepSeek Harness 指定 openai 协议供应商")
1542
+ "要打开即用,请到「模型调度(可选)」页给 DeepSeek Harness 指定 openai 协议供应商")
1534
1543
  name = entry.get("name", entry["id"])
1535
1544
  kind = (launch.get("kind") or "console").lower()
1536
1545
  extra = (";".join(notes)) if notes else ""
@@ -1637,7 +1646,7 @@ def _open_when_ready(port, url, log_path, timeout=30):
1637
1646
  # ---------------------------------------------------------------- 安装/升级
1638
1647
 
1639
1648
  def run_mgmt_command(entry, op, cancel_event=None, log_path=None):
1640
- """执行 install/upgrade/uninstall 命令(由任务执行器异步运行,日志实时落盘)。"""
1649
+ """执行 install/upgrade/uninstall 命令(由任务执行器异步运行,日志实时落盘)。"""
1641
1650
  if op == "uninstall":
1642
1651
  cmd = catalog.uninstall_command(entry)
1643
1652
  if not cmd:
package/app/ui/app.js CHANGED
@@ -9459,6 +9459,9 @@ function renderSu() {
9459
9459
  const info = $("su-info");
9460
9460
  if (!info) return;
9461
9461
  if (!SU) { info.textContent = t("无法获取版本信息(服务未连接)"); return; }
9462
+ // 真实内容就绪后摘掉「空状态」皮肤:.empty 是居中+虚线框的加载占位样式,
9463
+ // 版本信息与更新说明都应该左对齐阅读(用户反馈「能不能左对齐」)
9464
+ info.classList.remove("empty");
9462
9465
  const modeTxt = t(SU_MODE_TXT[SU.mode] || "未知安装方式");
9463
9466
  const cur = SU.current ? "v" + SU.current : t("(未同步版本号)");
9464
9467
  let html = "<p class=\"hint\">" + t("当前版本") + t(":") + "<b>" + cur + "</b>" + t(" · ") + t("安装方式") + t(":") + modeTxt + "</p>";
package/app/ui/i18n.js CHANGED
@@ -1963,6 +1963,7 @@
1963
1963
  "产品档案": "Product Profiles",
1964
1964
  "+ 添加产品": "+ Add product",
1965
1965
  "+ 从禅道添加产品": "+ Add from ZenTao",
1966
+ "测试并保存连接": "Test & save connection",
1966
1967
  "刷新产品列表": "Refresh products",
1967
1968
  "重新从禅道拉取产品与账号": "Reload products and accounts from ZenTao",
1968
1969
  "先测试连接,再点「从禅道添加产品」直接选择产品;无需自己查 ID。每个产品只需配置我方仓库,负责人和模块路由均可稍后补充。": "Test the connection, then choose Add from ZenTao — no ID lookup needed. Configure only your repository first; owners and module routes can be added later.",
package/app/ui/style.css CHANGED
@@ -5508,3 +5508,16 @@ button.request-busy > * { opacity: .18; }
5508
5508
  @media (prefers-reduced-motion: reduce) {
5509
5509
  .op-pill.running .ico, .op-row.running .op-state { animation: none; }
5510
5510
  }
5511
+
5512
+ /* 关于与更新:版本信息面板常态样式(加载中才挂 .empty 居中虚线皮肤;
5513
+ renderSu 填充内容后移除 empty,改用这里的左对齐阅读样式——用户反馈) */
5514
+ #su-info {
5515
+ padding: 12px 14px;
5516
+ border: 1px solid var(--border, rgba(128, 128, 128, .25));
5517
+ border-radius: 8px;
5518
+ background: var(--bg-soft, rgba(128, 128, 128, .06));
5519
+ text-align: left;
5520
+ font-size: 13px;
5521
+ line-height: 1.6;
5522
+ }
5523
+ #su-info.empty { text-align: center; }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codebee",
3
- "version": "0.1.27",
3
+ "version": "0.1.28",
4
4
  "description": "CodeBee · 多智能体编排台 — 统一编排本机 AI 编码 CLI(Codex / Claude Code / QwenCode / OpenCode / Aider…):目标→拆解→路由→执行→验证→跨厂商评审→修复→报告。纯 Python 标准库实现,本地 Web UI。",
5
5
  "license": "MIT",
6
6
  "bin": {