codebee 0.1.0 → 0.1.1

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.
@@ -1329,8 +1329,9 @@ def _src_ccswitch():
1329
1329
  except Exception:
1330
1330
  pass
1331
1331
  if skipped:
1332
- out["note"] = "跳过 %d 条(缺地址或格式不识别):%s" % (
1332
+ out["note"] = "跳过 {0} 条(缺地址或格式不识别):{1}".format(
1333
1333
  len(skipped), "、".join(sorted(set(skipped))[:4]))
1334
+ out["note_args"] = [len(skipped), "、".join(sorted(set(skipped))[:4])]
1334
1335
  return out
1335
1336
 
1336
1337
 
@@ -1414,7 +1415,9 @@ def _src_zcode():
1414
1415
  else:
1415
1416
  bad.append(str(p.get("name") or pid))
1416
1417
  if bad:
1417
- out["note"] = "跳过 %d 个(缺合法 baseURL):%s" % (len(bad), "、".join(bad[:4]))
1418
+ out["note"] = "跳过 {0} 个(缺合法 baseURL):{1}".format(
1419
+ len(bad), "、".join(bad[:4]))
1420
+ out["note_args"] = [len(bad), "、".join(bad[:4])]
1418
1421
  return out
1419
1422
 
1420
1423
 
@@ -1697,6 +1700,7 @@ def _collect(fn):
1697
1700
  res.setdefault("providers", [])
1698
1701
  res.setdefault("pricing", {})
1699
1702
  res.setdefault("note", "")
1703
+ res.setdefault("note_args", [])
1700
1704
  res.setdefault("error", "")
1701
1705
  res.setdefault("found", False)
1702
1706
  return res
@@ -1709,11 +1713,12 @@ def sources():
1709
1713
  ps = [p for p in paths() if p]
1710
1714
  found = any(os.path.isfile(p) for p in ps)
1711
1715
  row = {"id": sid, "name": name, "desc": desc, "paths": ps,
1712
- "found": found, "count": 0, "note": "", "error": ""}
1716
+ "found": found, "count": 0, "note": "", "note_args": [], "error": ""}
1713
1717
  if found:
1714
1718
  res = _collect(fn)
1715
1719
  row["count"] = len(res["providers"])
1716
1720
  row["note"] = res["note"]
1721
+ row["note_args"] = res.get("note_args", [])
1717
1722
  row["error"] = res["error"]
1718
1723
  rows.append(row)
1719
1724
  return rows
@@ -1754,7 +1759,8 @@ def import_sources(ids=None):
1754
1759
  out["sources"].append({
1755
1760
  "id": sid, "name": name, "found": res["found"],
1756
1761
  "added": added, "updated": updated, "duplicate": dup,
1757
- "count": added + updated, "note": res["note"], "error": res["error"]})
1762
+ "count": added + updated, "note": res["note"],
1763
+ "note_args": res.get("note_args", []), "error": res["error"]})
1758
1764
  if out["added"] or out["updated"] or out["pricing"]:
1759
1765
  _save(data)
1760
1766
  out["imported"] = out["added"] + out["updated"]
package/app/core/paths.py CHANGED
@@ -4,7 +4,9 @@
4
4
  数据目录选择顺序:
5
5
  1. TUTTI_DATA 环境变量(测试/多实例时整体指到别处,必须在任何模块使用前设置);
6
6
  2. 仓库内 data/ 且已有内容——开发仓库与老安装平滑沿用,升级不动数据;
7
- 3. 用户目录:Windows %APPDATA%\\Tutti,macOS/Linux ~/.tutti
7
+ 3. 用户目录:Windows %APPDATA%\\CodeBee,macOS/Linux ~/.codebee
8
+ 老安装的 %APPDATA%\\Tutti / ~/.tutti 若存在则自动沿用(零迁移),
9
+ 新安装一律落 CodeBee 目录。
8
10
  npm/pip 全局安装的包目录会在 `npm update -g` 时被整体替换,
9
11
  数据绝不能落在包内,故全新安装一律走用户目录。
10
12
  """
@@ -23,16 +25,20 @@ _DATA_SENTINELS = ("catalog.json", "models.json", "orchestration.json", "tasks",
23
25
 
24
26
 
25
27
  def _user_data_dir(platform=None, environ=None, home=None) -> Path:
26
- """全局安装场景的用户数据目录(不创建,仅计算)。home 供测试注入。"""
28
+ """全局安装场景的用户数据目录(不创建,仅计算)。home 供测试注入。
29
+ 新装落 CodeBee;老 Tutti 目录存在则沿用(改名零迁移)。"""
27
30
  platform = platform or sys.platform
28
31
  env = os.environ if environ is None else environ
29
32
  home = Path(home) if home else Path.home()
30
33
  if platform == "win32":
31
34
  appdata = env.get("APPDATA")
32
- if appdata:
33
- return Path(appdata) / "Tutti"
34
- return home / "AppData" / "Roaming" / "Tutti"
35
- return home / ".tutti"
35
+ base = Path(appdata) if appdata else home / "AppData" / "Roaming"
36
+ if (base / "Tutti").exists():
37
+ return base / "Tutti"
38
+ return base / "CodeBee"
39
+ if (home / ".tutti").exists():
40
+ return home / ".tutti"
41
+ return home / ".codebee"
36
42
 
37
43
 
38
44
  def default_data_dir(repo=None, environ=None) -> Path:
package/app/ui/app.js CHANGED
@@ -843,11 +843,20 @@ function renderBindings() {
843
843
  const protoWarn = bound && !bindable.some((p) => p.id === bound.id)
844
844
  ? '<p class="hint warn">' + t("该供应商协议为 ") + esc(protoLabel(bound)) +
845
845
  t(",当前没有可注入的 CLI,编排时会回落为 CLI 默认配置。") + '</p>' : "";
846
+ // 供应商停用但链上勾选了它家模型:链条目在解析时也会被整条跳过,
847
+ // 链空了照样回落本机默认——这是 2026-09-16 配额事故的隐藏形态,单说
848
+ // 「供应商下拉」警告不够,链本身的死活也要点名。
849
+ const chainDead = bindChain(b).some((c2) => c2.p && (() => {
850
+ const pv = provs.find((p) => p.id === c2.p);
851
+ return !pv || pv.enabled === false;
852
+ })());
853
+ const chainWarn = (!bound || bound.enabled !== false) && chainDead
854
+ ? '<p class="hint warn">' + t("模型链里有已停用/已删除的供应商:这些条目解析时会被跳过,链可能因此整体失效。") + '</p>' : "";
846
855
  return '<div class="card"><div class="head"><span class="name">' + esc(c.name) + "</span>" +
847
856
  '<span class="tag">' + esc(c.orch_kind) + "</span></div>" +
848
857
  '<div class="field"><label>' + t("供应商") + '</label><select id="bindprov-' + esc(c.id) + '">' + opts + "</select></div>" +
849
858
  '<p class="hint">' + t("绑定后编排调用会注入该供应商的 API key 与地址;不绑定则只按下方模型链传 -m 参数。") + '</p>' +
850
- bindModelBox(c, b.provider_id) + offWarn + protoWarn +
859
+ bindModelBox(c, b.provider_id) + offWarn + protoWarn + chainWarn +
851
860
  '<div class="ops"><label class="toggle"><input type="checkbox" id="binddiff-' + esc(c.id) + '"' +
852
861
  (b.difficulty_routing ? " checked" : "") + '>' + t(" 按难度自动选模型(简单/困难)") + '</label>' +
853
862
  '<button class="ghost small" onclick="saveBinding(\'' + esc(c.id) + '\')">' + t("保存") + '</button></div></div>';
@@ -1593,7 +1602,7 @@ async function openImportDialog() {
1593
1602
  '<div class="src-name">' + esc(s.name) + status + "</div>" +
1594
1603
  '<div class="src-desc">' + esc(t(s.desc)) + "</div>" +
1595
1604
  '<div class="src-path">' + esc((s.paths || []).join(" · ")) + "</div>" +
1596
- (s.note ? '<div class="src-note">' + esc(t(s.note)) + "</div>" : "") +
1605
+ (s.note ? '<div class="src-note">' + esc(t.apply(null, [s.note].concat(s.note_args || []))) + "</div>" : "") +
1597
1606
  (s.error ? '<div class="src-note bad">' + esc(s.error) + "</div>" : "") +
1598
1607
  "</div></label>";
1599
1608
  }).join("");
@@ -1646,7 +1655,7 @@ async function doImport() {
1646
1655
  if (s.error) return "<li>" + esc(s.name) + t(":") + '<span class="bad">' + esc(s.error) + "</span></li>";
1647
1656
  let txt = t("新增 ") + s.added + t(",更新 ") + s.updated;
1648
1657
  if (s.duplicate) txt += t(",跳过重复 ") + s.duplicate;
1649
- const extra = s.note ? t("(") + esc(t(s.note)) + t(")") : "";
1658
+ const extra = s.note ? t("(") + esc(t.apply(null, [s.note].concat(s.note_args || []))) + t(")") : "";
1650
1659
  return "<li>" + esc(s.name) + t(":") + txt + extra + "</li>";
1651
1660
  }).join("");
1652
1661
  $("import-result").innerHTML =
@@ -1862,7 +1871,7 @@ function onGoalPaste(e) {
1862
1871
 
1863
1872
  /* ---------------------------------------------------------- 代码版本(Git) */
1864
1873
  /* 工作目录指向 git 仓库时给出分支/标签/提交选择;创建任务后执行前由服务端
1865
- * 从所选版本检出任务分支 tutti/<task-id>(脏工作区会显式失败,不静默降级)。 */
1874
+ * 从所选版本检出任务分支 codebee/<task-id>(脏工作区会显式失败,不静默降级)。 */
1866
1875
  let _gitProbeTimer = null;
1867
1876
 
1868
1877
  function queueGitProbe() {
@@ -1911,7 +1920,7 @@ function renderGitHint() {
1911
1920
  const base = t("当前分支 %1 @ %2").replace("%1", info.branch).replace("%2", (info.head || "").slice(0, 8));
1912
1921
  const dirty = info.dirty ? t(";工作区有 %1 处未提交改动").replace("%1", info.dirty_count) : "";
1913
1922
  const act = kind
1914
- ? t(";运行时将从「%1」检出任务分支 tutti/&lt;任务ID&gt;").replace("%1", rev)
1923
+ ? t(";运行时将从「%1」检出任务分支 codebee/&lt;任务ID&gt;").replace("%1", rev)
1915
1924
  : "";
1916
1925
  $("git-hint").innerHTML = esc(base + dirty + act);
1917
1926
  }
@@ -3239,7 +3248,7 @@ async function renderRunDetail() {
3239
3248
  });
3240
3249
  }
3241
3250
 
3242
- /* 代码版本隔离面板:run 检出任务分支 tutti/<id> 后,产物提交在该分支上、
3251
+ /* 代码版本隔离面板:run 检出任务分支 codebee/<id> 后,产物提交在该分支上、
3243
3252
  * 用户工作区已切回原分支。分支是「每任务一条」,裁决(合并/丢弃)在任务级生效。
3244
3253
  * 数据源两路:任务级 side(详情页自拉,运行中实时 numstat、结束后最新 run 快照,
3245
3254
  * 续跑/重试的新 run 没带 git 字段也能从任务带出分支)优先;side 不可用
@@ -3561,7 +3570,7 @@ function _renderGitSnapshot(run, task) {
3561
3570
  '<div class="git-head"><svg class="ico" aria-hidden="true"><use href="#i-git-branch"></use></svg>' +
3562
3571
  '<span class="sec-title">' + t("代码版本隔离") + '</span>' +
3563
3572
  '<span class="chip failed">' + t("检出失败") + "</span>" +
3564
- '<code class="git-branch">tutti/' + esc(tid || t("(无主运行)")) + t("(") + esc(t("未创建")) + t(")") + "</code></div>" +
3573
+ '<code class="git-branch">codebee/' + esc(tid || t("(无主运行)")) + t("(") + esc(t("未创建")) + t(")") + "</code></div>" +
3565
3574
  (lastErr ? '<div class="hint warn">' + esc(lastErr) + "</div>" : "") +
3566
3575
  '<div class="hint">' + esc(t("处理后点「重试任务」,运行会重新检出任务分支。")) + "</div>";
3567
3576
  rdTabsSync();
@@ -3711,7 +3720,7 @@ function renderGitWbMain(d, task) {
3711
3720
  if (iso.rev && !iso.state) {
3712
3721
  html += '<div class="hint">' + esc(t("已配置代码版本隔离,但任务分支尚未创建(检出失败见运行错误)。")) + "</div>";
3713
3722
  } else if (iso.state === "isolated") {
3714
- html += '<div class="git-meta"><span>' + esc(t("任务分支")) + " <b>tutti/" + esc(tid) + "</b></span>" +
3723
+ html += '<div class="git-meta"><span>' + esc(t("任务分支")) + " <b>codebee/" + esc(tid) + "</b></span>" +
3715
3724
  '<span class="chip isolated">' + t("待裁决") + "</span></div>";
3716
3725
  if (!ro) {
3717
3726
  html += '<div class="git-actions">' +
@@ -3723,7 +3732,7 @@ function renderGitWbMain(d, task) {
3723
3732
  }
3724
3733
  } else if (iso.state) {
3725
3734
  const chip = GIT_STATE_CHIP[iso.state] || ["muted", "—"];
3726
- html += '<div class="git-meta"><span>' + esc(t("任务分支")) + " <b>tutti/" + esc(tid) + "</b></span>" +
3735
+ html += '<div class="git-meta"><span>' + esc(t("任务分支")) + " <b>codebee/" + esc(tid) + "</b></span>" +
3727
3736
  '<span class="chip ' + chip[0] + '">' + t(chip[1]) + "</span></div>";
3728
3737
  }
3729
3738
  box.classList.remove("hidden");
@@ -3974,7 +3983,7 @@ function drawInspector() {
3974
3983
  const lastErr = ((d.run || {}).error || "");
3975
3984
  if (revChosen) {
3976
3985
  chipEl.className = "chip failed"; chipEl.textContent = t("检出失败"); chipEl.classList.remove("hidden");
3977
- main.innerHTML = '<div class="insp-branch"><code>tutti/' + esc(d.task.id || "") +
3986
+ main.innerHTML = '<div class="insp-branch"><code>codebee/' + esc(d.task.id || "") +
3978
3987
  t("(") + esc(t("未创建")) + t(")") + '</code></div>' +
3979
3988
  (lastErr ? '<div class="hint warn">' + esc(lastErr) + "</div>" : "") +
3980
3989
  '<span class="insp-hint">' + esc(t("处理后点「重试任务」,运行会重新检出任务分支。")) + "</span>";
@@ -5498,7 +5507,9 @@ function bindModelBox(c, provId) {
5498
5507
  '" title="' + t("移除") + '" onclick="bindRemove(\'' + esc(c.id) + '\', this)">×</button>' +
5499
5508
  "</span>";
5500
5509
  }).join("")
5501
- : '<span class="hint">' + t("未设置") + (provId ? t("(按供应商/难度自动解析)") : t("(用 CLI 默认模型)")) + "</span>";
5510
+ : '<span class="hint">' + t("未设置") + (provId
5511
+ ? t("(按供应商/难度自动解析——供应商协议不匹配或被停用时解析为空,回落 CLI 本机默认)")
5512
+ : t("(用 CLI 默认模型——不会注入任何供应商凭据)")) + "</span>";
5502
5513
  return '<div class="field"><label>' + t("运行时模型链(跨厂商,最多 ") + MAX_ORCH_MODELS + t(" 条)") + "</label>" +
5503
5514
  '<div class="orch-row">' + chips +
5504
5515
  '<button class="ghost small" onclick="bindToggle(\'' + esc(c.id) + '\')">' +