codebee 0.1.20 → 0.1.21
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 +6 -0
- package/README.md +8 -6
- package/app/core/automation.py +6 -6
- package/app/core/capability.py +16 -2
- package/app/core/catalog.py +1 -1
- package/app/core/covergen.py +40 -4
- package/app/core/dispatch.py +141 -0
- package/app/core/flows.py +5 -0
- package/app/core/jobs.py +281 -166
- package/app/core/manager.py +30 -23
- package/app/core/market_remote.py +66 -22
- package/app/core/modelhub.py +145 -11
- package/app/core/pipeline.py +111 -30
- package/app/core/router.py +18 -7
- package/app/core/runner.py +2 -1
- package/app/core/selfupdate.py +24 -14
- package/app/core/settings.py +3 -3
- package/app/core/skills.py +43 -0
- package/app/core/store.py +20 -14
- package/app/core/zentao.py +26 -6
- package/app/main.py +20 -12
- package/app/pet.py +2 -2
- package/app/ui/app.js +62 -58
- package/app/ui/i18n.js +21 -3
- package/app/ui/index.html +11 -11
- package/app/ui/style.css +2 -0
- package/package.json +1 -1
package/app/core/zentao.py
CHANGED
|
@@ -1077,7 +1077,7 @@ def _goal_text(bug, side=None):
|
|
|
1077
1077
|
|
|
1078
1078
|
|
|
1079
1079
|
def _launch_fix(bug, profile, side, cfg):
|
|
1080
|
-
"""为一个 bug
|
|
1080
|
+
"""为一个 bug 的某一端建修复任务并立即启动。"""
|
|
1081
1081
|
bid = bug.get("id")
|
|
1082
1082
|
repo = _repo_of(profile, side)
|
|
1083
1083
|
wd = str(repo.get("workdir") or "").strip() or settings.default_workdir()
|
|
@@ -1088,11 +1088,31 @@ def _launch_fix(bug, profile, side, cfg):
|
|
|
1088
1088
|
payload["git_rev"] = str(repo["git_rev"]).strip()
|
|
1089
1089
|
if str(repo.get("verify_command") or "").strip():
|
|
1090
1090
|
payload["verify_command"] = str(repo["verify_command"]).strip()
|
|
1091
|
-
task =
|
|
1092
|
-
run =
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1091
|
+
task = None
|
|
1092
|
+
run = None
|
|
1093
|
+
try:
|
|
1094
|
+
task = store.create_task(payload)
|
|
1095
|
+
run = store.create_run("orchestration", task["title"], task_id=task["id"])
|
|
1096
|
+
store.update_task_status(task["id"], "queued")
|
|
1097
|
+
jobs.enqueue({"kind": "orchestration", "run_id": run["id"],
|
|
1098
|
+
"task_id": task["id"]})
|
|
1099
|
+
return task, run
|
|
1100
|
+
except Exception:
|
|
1101
|
+
log.exception("zentao: 修复任务启动失败 bug=%s side=%s task=%s run=%s",
|
|
1102
|
+
bid, side, (task or {}).get("id"), (run or {}).get("id"))
|
|
1103
|
+
if run:
|
|
1104
|
+
try:
|
|
1105
|
+
store.update_run(run["id"], status="failed",
|
|
1106
|
+
error="禅道修复任务启动失败,本次未排队,请稍后重试",
|
|
1107
|
+
ended_at=time.strftime("%Y-%m-%d %H:%M:%S"))
|
|
1108
|
+
except Exception:
|
|
1109
|
+
log.exception("zentao: 修复运行失败收口失败 run=%s", run.get("id"))
|
|
1110
|
+
if task:
|
|
1111
|
+
try:
|
|
1112
|
+
store.update_task_status(task["id"], "failed")
|
|
1113
|
+
except Exception:
|
|
1114
|
+
log.exception("zentao: 修复任务失败收口失败 task=%s", task.get("id"))
|
|
1115
|
+
raise
|
|
1096
1116
|
|
|
1097
1117
|
|
|
1098
1118
|
# ---------------------------------------------------------------- 回写文本
|
package/app/main.py
CHANGED
|
@@ -972,7 +972,7 @@ class Handler(BaseHTTPRequestHandler):
|
|
|
972
972
|
m = re.match(r"^/api/catalog/([^/]+)/launch$", path)
|
|
973
973
|
if m:
|
|
974
974
|
# 一键打开(web 类起服务+开浏览器 / console 类新终端窗口)。
|
|
975
|
-
#
|
|
975
|
+
# 即时返回不走任务执行器;body 可传 {"open": false} 供测试免开浏览器
|
|
976
976
|
entry = catalog.by_id(m.group(1))
|
|
977
977
|
if not entry:
|
|
978
978
|
return self._json(404, {"error": "catalog 中无此条目"})
|
|
@@ -1713,9 +1713,13 @@ class Handler(BaseHTTPRequestHandler):
|
|
|
1713
1713
|
ok, err, new_run = store.retry_task(run["task_id"])
|
|
1714
1714
|
if ok:
|
|
1715
1715
|
store.update_run(new_run["id"], op="qa", qa_text=text)
|
|
1716
|
-
self._enqueue_run(
|
|
1717
|
-
|
|
1718
|
-
|
|
1716
|
+
started, start_err = self._enqueue_run(
|
|
1717
|
+
new_run["id"], run["task_id"],
|
|
1718
|
+
{"kind": "orchestration", "run_id": new_run["id"],
|
|
1719
|
+
"task_id": run["task_id"]})
|
|
1720
|
+
if not started:
|
|
1721
|
+
return self._json(503, {"error": start_err,
|
|
1722
|
+
"run_id": new_run["id"]})
|
|
1719
1723
|
return self._json(200, {"ok": True, "message": msg,
|
|
1720
1724
|
"qa_run": new_run["id"]})
|
|
1721
1725
|
return self._json(200, {"ok": True, "message": msg})
|
|
@@ -2100,15 +2104,17 @@ class Handler(BaseHTTPRequestHandler):
|
|
|
2100
2104
|
return self._json(status, resp)
|
|
2101
2105
|
|
|
2102
2106
|
def _enqueue_run(self, run_id, task_id, job):
|
|
2103
|
-
"""
|
|
2107
|
+
"""立即启动失败时收口到 failed;系统默认没有等待队列。"""
|
|
2104
2108
|
try:
|
|
2105
2109
|
jobs.enqueue(job)
|
|
2106
2110
|
return True, ""
|
|
2107
|
-
except Exception:
|
|
2111
|
+
except Exception as exc:
|
|
2108
2112
|
# 不把异常文本(本机路径、命令行参数、供应商响应)返回给客户端;
|
|
2109
2113
|
# 详细堆栈只进服务端日志,run 记录也保留稳定的用户可读文案。
|
|
2110
2114
|
log.exception("任务入队失败 run=%s task=%s", run_id, task_id)
|
|
2111
|
-
|
|
2115
|
+
busy = isinstance(exc, jobs.JobsBusyError)
|
|
2116
|
+
err = ("当前运行任务已达并发保护上限,本次未排队,请稍后重试"
|
|
2117
|
+
if busy else "任务启动失败,请稍后重试")
|
|
2112
2118
|
try:
|
|
2113
2119
|
closed = store.update_run(run_id, status="failed", error=err,
|
|
2114
2120
|
ended_at=time.strftime("%Y-%m-%d %H:%M:%S"))
|
|
@@ -2161,8 +2167,7 @@ def _state_payload(client_id="", ver=None):
|
|
|
2161
2167
|
"control": remote.control_view(client_id),
|
|
2162
2168
|
# 供应商健康/告警(顶栏横幅数据源;有告警时 bump_state 会推给所有端)
|
|
2163
2169
|
"health": health.snapshot(),
|
|
2164
|
-
#
|
|
2165
|
-
# 是「并发满载在等」还是「job 蒸发没人管」(后者由看门狗 2 分钟自愈)
|
|
2170
|
+
# 直接执行观测:并发保护上限、运行数和可用位;queued 恒为 0。
|
|
2166
2171
|
"jobs": jobs.workers_info(),
|
|
2167
2172
|
}
|
|
2168
2173
|
|
|
@@ -2355,14 +2360,17 @@ def main():
|
|
|
2355
2360
|
telemetry.start_background() # 匿名错误回传+版本 ping(默认开可关;未配端点自动休眠,延迟 45s 不挡启动)
|
|
2356
2361
|
except Exception:
|
|
2357
2362
|
pass
|
|
2358
|
-
_step("
|
|
2363
|
+
_step("正在启动任务执行器…")
|
|
2359
2364
|
jobs.start_worker()
|
|
2365
|
+
n_wait = jobs.restore_deferred_resumes()
|
|
2366
|
+
if n_wait:
|
|
2367
|
+
print("[CodeBee] 已恢复 %d 个定时退避中的自动续跑任务" % n_wait)
|
|
2360
2368
|
n_resume = jobs.resume_interrupted() # 启动恢复:服务被杀中断的连载任务自动续跑
|
|
2361
2369
|
if n_resume:
|
|
2362
2370
|
print("[CodeBee] 已自动恢复 %d 个中断的连载任务(断点续跑)" % n_resume)
|
|
2363
|
-
n_rq = jobs.requeue_pending() #
|
|
2371
|
+
n_rq = jobs.requeue_pending() # 兼容旧版本遗留的无退避 queued 记录
|
|
2364
2372
|
if n_rq:
|
|
2365
|
-
print("[CodeBee]
|
|
2373
|
+
print("[CodeBee] 已直接启动 %d 个遗留运行" % n_rq)
|
|
2366
2374
|
_step("正在启动自动化调度…")
|
|
2367
2375
|
n_auto = automation.start() # 自动化:加载定时任务并拉起调度线程(错过的一次性任务不补跑)
|
|
2368
2376
|
if n_auto:
|
package/app/pet.py
CHANGED
|
@@ -117,7 +117,7 @@ def derive_state(prev_active_ids, tasks):
|
|
|
117
117
|
|
|
118
118
|
|
|
119
119
|
def tooltip_lines(snap, lang="zh"):
|
|
120
|
-
"""悬停清单:未读摘要 + 进行中 +
|
|
120
|
+
"""悬停清单:未读摘要 + 进行中 + 待启动/自动续跑,最多 6 行。
|
|
121
121
|
|
|
122
122
|
只列「还在跑 / 待跑」的任务——历史失败项会刷满清单(截图实测十几条
|
|
123
123
|
✘ 把面板撑成一堵墙),失败已由警示气泡点名,这里不再重复。
|
|
@@ -167,7 +167,7 @@ LANG = {
|
|
|
167
167
|
"all_clear": "蜂群闲着,都在打盹…",
|
|
168
168
|
"title": "蜂群动态",
|
|
169
169
|
"n_running": "%d 个进行中",
|
|
170
|
-
"queued": "
|
|
170
|
+
"queued": "正在启动",
|
|
171
171
|
"cheer": "🎉 %d 个任务完工!",
|
|
172
172
|
"alert": "⚠️ 「%s」出岔子了,点我看看",
|
|
173
173
|
"bye": "蜜蜂回巢啦",
|
package/app/ui/app.js
CHANGED
|
@@ -426,13 +426,13 @@ function jsq(s) {
|
|
|
426
426
|
}
|
|
427
427
|
|
|
428
428
|
function statusChip(st) {
|
|
429
|
-
const zh = { queued: t("
|
|
429
|
+
const zh = { queued: t("正在启动"), running: t("运行中"), done: t("完成"), failed: t("失败"), cancelled: t("已取消"), timeout: t("超时") };
|
|
430
430
|
return '<span class="chip ' + esc(st) + '">' + (zh[st] || esc(st)) + "</span>";
|
|
431
431
|
}
|
|
432
432
|
|
|
433
433
|
/* 运行状态文案(传 run 对象):退避窗口内的续跑副本写明「将于 HH:MM 自动
|
|
434
434
|
* 续跑」,别让 5 分钟等待看起来像卡死/资源排队(2026-09-18 重写任务误判案)。
|
|
435
|
-
*
|
|
435
|
+
* 到点后翻回「正在启动」——页面轮询重渲染时 Date.now() 已过预定时刻。 */
|
|
436
436
|
function runStatusText(run) {
|
|
437
437
|
const st = String((run && run.status) || "");
|
|
438
438
|
if (st === "queued" && run && run.resume_enqueue_at) {
|
|
@@ -440,7 +440,7 @@ function runStatusText(run) {
|
|
|
440
440
|
if (!isNaN(at) && Date.now() < at)
|
|
441
441
|
return t("将于 {0} 自动续跑", String(run.resume_enqueue_at).slice(11, 16));
|
|
442
442
|
}
|
|
443
|
-
return { queued: t("
|
|
443
|
+
return { queued: t("正在启动"), running: t("运行中"), done: t("完成"),
|
|
444
444
|
failed: t("失败"), cancelled: t("已取消"), timeout: t("超时") }[st] || st;
|
|
445
445
|
}
|
|
446
446
|
|
|
@@ -632,7 +632,7 @@ async function healthOp(op) {
|
|
|
632
632
|
async function healthDisableModel(pid, model) {
|
|
633
633
|
if (!pid || !model) { closeModal(); return; }
|
|
634
634
|
const yes = await uiConfirm(
|
|
635
|
-
t("确认禁用模型 {0}
|
|
635
|
+
t("确认禁用模型 {0}?链降级将自动跳过它,其余模型不受影响;可在模型调度页重新启用。").replace("{0}", pid + " · " + model),
|
|
636
636
|
{ title: t("禁用模型"), danger: true, ok: t("禁用") });
|
|
637
637
|
if (!yes) return;
|
|
638
638
|
try {
|
|
@@ -657,7 +657,7 @@ async function healthDisableModel(pid, model) {
|
|
|
657
657
|
async function healthDisableProvider(pid) {
|
|
658
658
|
if (!pid) { closeModal(); return; }
|
|
659
659
|
const yes = await uiConfirm(
|
|
660
|
-
t("
|
|
660
|
+
t("确认禁用该厂商?禁用后链降级自动跳过它,恢复后可在模型调度页重新启用。"),
|
|
661
661
|
{ title: t("禁用厂商"), danger: true, ok: t("禁用") });
|
|
662
662
|
if (!yes) return;
|
|
663
663
|
try {
|
|
@@ -855,7 +855,7 @@ function renderBindings() {
|
|
|
855
855
|
}
|
|
856
856
|
bbox.innerHTML = targets.map((c) => {
|
|
857
857
|
const b = (S.bindings || {})[c.id] || {};
|
|
858
|
-
const opts = '<option value="">' + t("
|
|
858
|
+
const opts = '<option value="">' + t("自动推荐(推荐)") + '</option>' + bindable.map((p) => {
|
|
859
859
|
const adaptedOnly = p.protocol !== "anthropic" && p.protocol !== "openai";
|
|
860
860
|
return '<option value="' + esc(p.id) + '"' + (b.provider_id === p.id ? " selected" : "") + ">" +
|
|
861
861
|
esc(p.name) + t("(") + esc(protoLabel(p)) + (adaptedOnly ? t(" · 已适配") : "") +
|
|
@@ -879,13 +879,13 @@ function renderBindings() {
|
|
|
879
879
|
return '<div class="card"><div class="head"><span class="name">' + esc(c.name) + "</span>" +
|
|
880
880
|
'<span class="tag">' + esc(c.orch_kind) + "</span></div>" +
|
|
881
881
|
'<div class="field"><label>' + t("供应商") + '</label><select id="bindprov-' + esc(c.id) + '">' + opts + "</select></div>" +
|
|
882
|
-
'<p class="hint">' + t("
|
|
882
|
+
'<p class="hint">' + t("默认不需要绑定:系统会按任务类型、难度、成本与健康状态自动推荐可用模型;只有需要固定厂商、模型或降级顺序时才在这里绑定。绑定后编排调用会注入该供应商的 API key 与地址;完全未指定时保持自动调度,没有兼容可用供应商才使用 CLI 自身配置。") + '</p>' +
|
|
883
883
|
bindModelBox(c, b.provider_id) + offWarn + protoWarn + chainWarn +
|
|
884
884
|
'<div class="ops"><label class="toggle"><input type="checkbox" id="binddiff-' + esc(c.id) + '"' +
|
|
885
885
|
(b.difficulty_routing ? " checked" : "") + '>' + t(" 按难度自动选模型(简单/困难)") + '</label>' +
|
|
886
886
|
'<button class="ghost small" onclick="saveBinding(\'' + esc(c.id) + '\')">' + t("保存") + '</button></div></div>';
|
|
887
887
|
}).join("") +
|
|
888
|
-
(!targets.length ? '<p class="hint">' + t("还没有已安装且可编排的 CLI
|
|
888
|
+
(!targets.length ? '<p class="hint">' + t("还没有已安装且可编排的 CLI——先到「本机智能体」页安装并启用。") + '</p>' : "") +
|
|
889
889
|
(nonBindable ? '<p class="hint">' + t("另有 ") + nonBindable +
|
|
890
890
|
t(" 个供应商(google 等协议)仅登记,不支持注入 CLI,未出现在上面的下拉中。") + '</p>' : "");
|
|
891
891
|
}
|
|
@@ -974,7 +974,7 @@ async function batchProvOp(op) {
|
|
|
974
974
|
const tips = {
|
|
975
975
|
enable: t("启用所选 ") + ids.length + t(" 个供应商?"),
|
|
976
976
|
disable: t("停用所选 ") + ids.length + t(" 个供应商?\n停用后其绑定会回落为 CLI 默认;配置与模型列表都保留,可随时再启用。"),
|
|
977
|
-
delete: t("删除所选 ") + ids.length + t(" 个供应商?\n
|
|
977
|
+
delete: t("删除所选 ") + ids.length + t(" 个供应商?\n相关显式模型调度会自动解除,此操作不可撤销。"),
|
|
978
978
|
};
|
|
979
979
|
if (!await uiConfirm(tips[op] || (t("执行「") + op + t("」?")))) return;
|
|
980
980
|
try {
|
|
@@ -1780,11 +1780,15 @@ async function doImport() {
|
|
|
1780
1780
|
|
|
1781
1781
|
async function saveBinding(id) {
|
|
1782
1782
|
const st = bindSelById(id);
|
|
1783
|
+
const provSel = $("bindprov-" + id);
|
|
1783
1784
|
// 主供应商跟链首走:模型链是唯一真源,链首非空时供应商下拉必须与之一致
|
|
1784
1785
|
// (下拉是旧状态时把旧值发上去,后端「显式指定」就会盖回停用的旧供应商)。
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1786
|
+
// 空链时保留下拉选择:这是“只锁定供应商,模型按该供应商默认/难度选”的
|
|
1787
|
+
// 合法显式覆盖,不能重置成自动模式。
|
|
1788
|
+
const headP = st.chain.length ? st.chain[0].p : (provSel ? provSel.value : "");
|
|
1789
|
+
if (st.chain.length && provSel && provSel.value !== (headP || "")) {
|
|
1790
|
+
provSel.value = headP || "";
|
|
1791
|
+
}
|
|
1788
1792
|
try {
|
|
1789
1793
|
await api("/api/models/binding", { method: "POST", body: JSON.stringify({
|
|
1790
1794
|
agent_id: id, provider_id: provSel ? provSel.value : "",
|
|
@@ -1926,7 +1930,8 @@ async function createTask() {
|
|
|
1926
1930
|
msg.textContent = t("目标有点简短,先问几个问题…");
|
|
1927
1931
|
try {
|
|
1928
1932
|
const cq = await api("/api/tasks/clarify", {
|
|
1929
|
-
method: "POST",
|
|
1933
|
+
method: "POST", timeout: 75000,
|
|
1934
|
+
body: JSON.stringify({ goal: payload.goal, type: payload.type }) });
|
|
1930
1935
|
const qs = (cq && cq.questions) || [];
|
|
1931
1936
|
if (qs.length) {
|
|
1932
1937
|
S.clarifyDone = true; // 本轮已采访;再点发送直接创建
|
|
@@ -1984,7 +1989,8 @@ async function createTask() {
|
|
|
1984
1989
|
if (critics.length) payload.critics = critics;
|
|
1985
1990
|
}
|
|
1986
1991
|
try {
|
|
1987
|
-
const r = await api("/api/tasks", { method: "POST",
|
|
1992
|
+
const r = await api("/api/tasks", { method: "POST", timeout: 30000,
|
|
1993
|
+
body: JSON.stringify(payload) });
|
|
1988
1994
|
msg.textContent = t("已创建,跳转运行页…");
|
|
1989
1995
|
// 先把新任务刷进 state 再跳:chatEngineIsDirect 靠 S.state.tasks 判引擎,
|
|
1990
1996
|
// 不刷的话对话页签不会就绪,自动选卡落不到「对话」
|
|
@@ -3260,7 +3266,7 @@ function drawTaskDetail(key, runs) {
|
|
|
3260
3266
|
// 取消收尾把僵尸步骤落成「已取消」时要立即重画,不等条数变化;
|
|
3261
3267
|
// 作品信息状态入签名:后台一键生成 running→done 要立刻反映到成果区面板
|
|
3262
3268
|
const bmTask = ((S.state || {}).tasks || []).find((x) => x.id === key);
|
|
3263
|
-
//
|
|
3269
|
+
// 自动续跑退避相位入签名:预定启动时刻过了之后 chip 翻为「正在启动」
|
|
3264
3270
|
const lr0 = runs[0] || {};
|
|
3265
3271
|
const resumePending = (lr0.resume_enqueue_at &&
|
|
3266
3272
|
Date.now() < Date.parse(String(lr0.resume_enqueue_at).replace(" ", "T"))) ? 1 : 0;
|
|
@@ -3274,8 +3280,8 @@ function drawTaskDetail(key, runs) {
|
|
|
3274
3280
|
const ordered = runs.slice(); // 详情按最近运行优先,方便排查
|
|
3275
3281
|
const totalSteps = runs.reduce((a, r) => a + (r.steps || []).length, 0);
|
|
3276
3282
|
const active = runs.some((r) => r.status === "running" || r.status === "queued");
|
|
3277
|
-
//
|
|
3278
|
-
//
|
|
3283
|
+
// 活跃态细分真实状态:queued 仅用于自动续跑退避或创建到起跑的瞬时状态;
|
|
3284
|
+
// 前者在 chip 上写明下一轮何时起跑,避免退避窗口看起来像卡死。
|
|
3279
3285
|
const activeRun0 = runs.find((r) => r.status === "running" || r.status === "queued");
|
|
3280
3286
|
const st = activeRun0 ? activeRun0.status : latest.status;
|
|
3281
3287
|
const resumeIn = (st === "queued" && resumePending)
|
|
@@ -3285,7 +3291,7 @@ function drawTaskDetail(key, runs) {
|
|
|
3285
3291
|
chip.className = "chip " + st;
|
|
3286
3292
|
chip.textContent = resumeIn
|
|
3287
3293
|
? t("将于 ") + resumeIn + t(" 自动续跑(第 ") + (Number(latest.auto_resumes) || 0) + t(" 次)")
|
|
3288
|
-
: ({ queued: t("
|
|
3294
|
+
: ({ queued: t("正在启动"), running: t("运行中"), done: t("完成"), failed: t("失败"), cancelled: t("已取消") }[st] || st);
|
|
3289
3295
|
const bpt = $("btn-pause");
|
|
3290
3296
|
if (bpt) bpt.classList.add("hidden");
|
|
3291
3297
|
$("btn-delete").classList.add("hidden");
|
|
@@ -6057,12 +6063,17 @@ function chatResultHTML(run, res) {
|
|
|
6057
6063
|
if (res.turns) meta.push(res.turns + " " + t("轮对话"));
|
|
6058
6064
|
if (res.duration_s != null) meta.push(chatDurTxt(res.duration_s));
|
|
6059
6065
|
const files = res.files || [];
|
|
6060
|
-
const chips = files.map((f) =>
|
|
6061
|
-
|
|
6062
|
-
|
|
6063
|
-
|
|
6064
|
-
|
|
6065
|
-
|
|
6066
|
+
const chips = files.map((f) => {
|
|
6067
|
+
const fUrl = urlAuth("/api/runs/" + encodeURIComponent(run.id) + "/file?name=" + encodeURIComponent(f.name));
|
|
6068
|
+
return '<div class="file-chip has-actions">' +
|
|
6069
|
+
'<i class="fx">' + esc(_fpExt(f.name).slice(0, 4) || "file") + "</i>" +
|
|
6070
|
+
'<span class="p" title="' + esc(f.name + " · " + fmtSize(f.size)) + '">' + esc(f.name) + "</span>" +
|
|
6071
|
+
'<span class="fsz">' + fmtSize(f.size) + "</span>" +
|
|
6072
|
+
'<span class="fbtns">' +
|
|
6073
|
+
'<button type="button" class="chip-btn" title="' + esc(t("预览")) + '" onclick="artPopup(\'' + esc(run.id) + "', '" + esc(f.name) + "', " + (Number(f.size) || 0) + ')"><svg class="ico" aria-hidden="true"><use href="#i-file-text"/></svg></button>' +
|
|
6074
|
+
'<a class="chip-btn" href="' + fUrl + '" download="' + esc(f.name) + '" title="' + esc(t("下载")) + '"><svg class="ico" aria-hidden="true"><use href="#i-arrow-left"/></svg></a>' +
|
|
6075
|
+
"</span></div>";
|
|
6076
|
+
}).join("");
|
|
6066
6077
|
return '<div class="chat-row">' +
|
|
6067
6078
|
'<span class="chat-avatar" aria-hidden="true"><svg class="ico"><use href="' + icon + '"></use></svg></span>' +
|
|
6068
6079
|
'<div class="chat-result' + (ok ? "" : bad ? " bad" : " off") + '">' +
|
|
@@ -6877,12 +6888,11 @@ function chainDeadReasons(c, chain) {
|
|
|
6877
6888
|
return dead;
|
|
6878
6889
|
}
|
|
6879
6890
|
|
|
6880
|
-
/*
|
|
6881
|
-
*
|
|
6882
|
-
|
|
6883
|
-
function bindRepairAction(c, st) {
|
|
6891
|
+
/* 单条显式绑定链的推荐修复动作:需要改返回新链,不动返回 null。
|
|
6892
|
+
* 空链代表默认自动调度,不能悄悄写成持久绑定;已有链失效时才推荐替代链。 */
|
|
6893
|
+
function bindRepairAction(c, st, includeEmpty) {
|
|
6884
6894
|
const rec = recommendFor(c);
|
|
6885
|
-
if (!st.chain.length) return rec ? [rec] : null;
|
|
6895
|
+
if (!st.chain.length) return includeEmpty && rec ? [rec] : null;
|
|
6886
6896
|
const dead = chainDeadReasons(c, st.chain);
|
|
6887
6897
|
const allDead = dead.every((d) => d);
|
|
6888
6898
|
const headDead = dead[0] !== ""; // 空 p = CLI 默认凭据,是有意配置不算死
|
|
@@ -6897,7 +6907,7 @@ function autoBindAll() {
|
|
|
6897
6907
|
let filled = 0, skipped = 0, noProv = 0, refilled = 0;
|
|
6898
6908
|
for (const c of targets) {
|
|
6899
6909
|
const st = bindSelById(c.id);
|
|
6900
|
-
const act = bindRepairAction(c, st);
|
|
6910
|
+
const act = bindRepairAction(c, st, true);
|
|
6901
6911
|
if (!act) {
|
|
6902
6912
|
// 没动它:分清「健康链无需推荐」和「没有可推荐的」两种落空
|
|
6903
6913
|
if (!st.chain.length) { noProv++; continue; }
|
|
@@ -6926,9 +6936,8 @@ function autoBindAll() {
|
|
|
6926
6936
|
}
|
|
6927
6937
|
}
|
|
6928
6938
|
|
|
6929
|
-
/*
|
|
6930
|
-
*
|
|
6931
|
-
* 但直接落盘(自动场景没有人工确认环节);目录页——空默认模型直填。
|
|
6939
|
+
/* 厂商/模型停用·启用·删除后自动修复显式绑定:
|
|
6940
|
+
* 空链保持自动调度,不写入绑定;已有链失效时才直接落盘替代链。
|
|
6932
6941
|
* 没有合适的推荐就保持原样,什么都不绑。绑定页上用户手改中的草稿(dirty)
|
|
6933
6942
|
* 不碰;一处都没改成静默返回,不打扰停用/启用的操作反馈。 */
|
|
6934
6943
|
let _autoRebindRunning = false, _autoRebindAgain = false;
|
|
@@ -6941,7 +6950,7 @@ async function autoRebindSoon() {
|
|
|
6941
6950
|
for (const c of (S.catalog || []).filter((x) => x.installed && x.orch_kind)) {
|
|
6942
6951
|
const st = bindSelById(c.id);
|
|
6943
6952
|
if (st.dirty) continue; // 用户手改中,不覆盖草稿
|
|
6944
|
-
const act = bindRepairAction(c, st);
|
|
6953
|
+
const act = bindRepairAction(c, st, false);
|
|
6945
6954
|
if (!act) continue;
|
|
6946
6955
|
const b = (S.bindings || {})[c.id] || {};
|
|
6947
6956
|
try {
|
|
@@ -6953,17 +6962,6 @@ async function autoRebindSoon() {
|
|
|
6953
6962
|
fixed++;
|
|
6954
6963
|
} catch (e) { /* 单条失败不打断,等下次变更再补 */ }
|
|
6955
6964
|
}
|
|
6956
|
-
for (const c of (S.catalog || []).filter((x) =>
|
|
6957
|
-
x.installed && x.config_writable && !fmtModel(x.model))) {
|
|
6958
|
-
const rec = recommendFor(c);
|
|
6959
|
-
if (!rec) continue;
|
|
6960
|
-
try {
|
|
6961
|
-
const r = await api("/api/catalog/" + encodeURIComponent(c.id) + "/model",
|
|
6962
|
-
{ method: "POST", body: JSON.stringify({ model: rec.m }) });
|
|
6963
|
-
c.model = r.model || rec.m;
|
|
6964
|
-
fixed++;
|
|
6965
|
-
} catch (e) { /* 同上 */ }
|
|
6966
|
-
}
|
|
6967
6965
|
if (fixed) {
|
|
6968
6966
|
S.catSig = null; S.bindSig = null;
|
|
6969
6967
|
render();
|
|
@@ -7024,7 +7022,7 @@ function bindModelBox(c, provId) {
|
|
|
7024
7022
|
}).join("")
|
|
7025
7023
|
: '<span class="hint">' + t("未设置") + (provId
|
|
7026
7024
|
? t("(按供应商/难度自动解析——供应商协议不匹配或被停用时解析为空,相关步骤将判失败)")
|
|
7027
|
-
: t("
|
|
7025
|
+
: t("(自动推荐厂商与模型;没有兼容可用供应商时才使用 CLI 自身配置)")) + "</span>";
|
|
7028
7026
|
return '<div class="field"><label>' + t("运行时模型链(跨厂商,最多 ") + MAX_ORCH_MODELS + t(" 条)") + "</label>" +
|
|
7029
7027
|
'<div class="orch-row">' + chips +
|
|
7030
7028
|
'<button class="ghost small" onclick="bindToggle(\'' + esc(c.id) + '\')">' +
|
|
@@ -7827,11 +7825,11 @@ function autoTplPace(tp) {
|
|
|
7827
7825
|
});
|
|
7828
7826
|
}
|
|
7829
7827
|
|
|
7830
|
-
/* last_status → 徽章:
|
|
7828
|
+
/* last_status → 徽章:started=正常灰、error=红「拉起失败」、missed=黄「已错过」 */
|
|
7831
7829
|
function autoStatusTag(tsk) {
|
|
7832
7830
|
if (tsk.last_status === "error") return '<span class="tag auto-tag-err">' + t("拉起失败") + "</span>";
|
|
7833
7831
|
if (tsk.last_status === "missed") return '<span class="tag auto-tag-miss">' + t("已错过") + "</span>";
|
|
7834
|
-
if (tsk.last_status === "queued") return '<span class="tag">' + t("正常") + "</span>";
|
|
7832
|
+
if (tsk.last_status === "started" || tsk.last_status === "queued") return '<span class="tag">' + t("正常") + "</span>";
|
|
7835
7833
|
return "";
|
|
7836
7834
|
}
|
|
7837
7835
|
|
|
@@ -8490,7 +8488,11 @@ function mkrDebounce() {
|
|
|
8490
8488
|
|
|
8491
8489
|
/* 拉一页:reset=清空已累积列表(首进/搜索/换来源/刷新后)。 */
|
|
8492
8490
|
async function mkrLoadPage(reset) {
|
|
8493
|
-
|
|
8491
|
+
// 翻页保持单飞;搜索/换来源/刷新属于重置请求,可以抢占旧请求。
|
|
8492
|
+
// 旧响应回来时由 requestKey 丢弃,不能覆盖用户刚选的新条件。
|
|
8493
|
+
if (S.mkrLoading && !reset) return;
|
|
8494
|
+
const requestKey = (S.mkrRequestKey || 0) + 1;
|
|
8495
|
+
S.mkrRequestKey = requestKey;
|
|
8494
8496
|
if (reset) { S.mkrAll = []; S.mkrOffset = 0; }
|
|
8495
8497
|
const q = (($("mkr-search") || {}).value || "").trim();
|
|
8496
8498
|
const srcSel = $("mkr-source");
|
|
@@ -8500,6 +8502,7 @@ async function mkrLoadPage(reset) {
|
|
|
8500
8502
|
S.mkrLoading = true;
|
|
8501
8503
|
let data = null;
|
|
8502
8504
|
try { data = await api(url); } catch (e) { data = null; }
|
|
8505
|
+
if (requestKey !== S.mkrRequestKey) return;
|
|
8503
8506
|
S.mkrLoading = false;
|
|
8504
8507
|
if (!data) { S.marketRemote = null; S.mkrAll = null; renderMarketRemote(); return; }
|
|
8505
8508
|
S.marketRemote = data;
|
|
@@ -8587,7 +8590,8 @@ async function mkrRefresh() {
|
|
|
8587
8590
|
const old = btn.textContent;
|
|
8588
8591
|
btn.textContent = t("拉取中…");
|
|
8589
8592
|
try {
|
|
8590
|
-
const r = await api("/api/market/remote/refresh", {
|
|
8593
|
+
const r = await api("/api/market/remote/refresh", {
|
|
8594
|
+
method: "POST", timeout: 120000, body: "{}" });
|
|
8591
8595
|
const bad = (r.refresh || []).filter((x) => !x.ok);
|
|
8592
8596
|
if (bad.length) toast(t("部分来源拉取失败:") + bad.map((x) => x.error || x.id).join(t(";")), true);
|
|
8593
8597
|
else toast(t("拉取成功"));
|
|
@@ -8920,13 +8924,13 @@ function helpChapterBody(id) {
|
|
|
8920
8924
|
if (id === "quickstart") {
|
|
8921
8925
|
return '<div class="welcome-steps">' +
|
|
8922
8926
|
'<div class="wstep"><b>1</b><span>' + t("添加模型供应商:设置 → 模型接入,填入 API Key") + '</span></div>' +
|
|
8923
|
-
'<div class="wstep"><b>2</b><span>' + t("
|
|
8927
|
+
'<div class="wstep"><b>2</b><span>' + t("启用本机智能体:运行时默认自动推荐模型") + '</span></div>' +
|
|
8924
8928
|
'<div class="wstep"><b>3</b><span>' + t("新建任务:选工作目录、写目标,蜂群开工") + '</span></div>' +
|
|
8925
8929
|
'</div>' +
|
|
8926
8930
|
'<p class="help-note">' + t("任务跑起来后,详情页能看到步骤、蜂巢、成果文件与 Git 版本;「直接执行」类任务还能像聊天一样边跑边追加消息。") + '</p>' +
|
|
8927
8931
|
'<div class="welcome-acts">' +
|
|
8928
8932
|
'<button class="wl-btn primary" onclick="welcomeGo(\'models\')"><span>' + t("开始配置模型") + '</span><svg class="ico" aria-hidden="true"><use href="#i-chevron-r"></use></svg></button>' +
|
|
8929
|
-
'<button class="wl-btn" onclick="welcomeGo(\'bindings\')"><span>' + t("
|
|
8933
|
+
'<button class="wl-btn" onclick="welcomeGo(\'bindings\')"><span>' + t("模型调度(可选)") + '</span><svg class="ico" aria-hidden="true"><use href="#i-chevron-r"></use></svg></button>' +
|
|
8930
8934
|
'<button class="wl-btn ghost" onclick="welcomeClose()"><span>' + t("先跳过,直接体验") + '</span></button>' +
|
|
8931
8935
|
'</div>';
|
|
8932
8936
|
}
|
|
@@ -8935,8 +8939,8 @@ function helpChapterBody(id) {
|
|
|
8935
8939
|
'<p class="help-p">' + t("「设置 → 模型接入 → 添加供应商」:填名称、API Key、接口地址(一般用默认)。保存后点「获取模型列表」自动拉取该厂商的模型;网关不提供列表接口时直接手工填模型名即可。同一厂商可配多把 Key,按顺序轮换,某把欠费自动切下一把。") + '</p>' +
|
|
8936
8940
|
'<h3 class="help-h3">' + t("第二步:认识协议徽章") + '</h3>' +
|
|
8937
8941
|
'<p class="help-p">' + t("新供应商默认自动识别协议,保存后在后台实测支持哪些调用形态,结果以徽章标在卡片上。带「· chat」表示该模型只适合站内直连对话;codex 这类只讲 responses 协议的 CLI 用不了它,绑定页会自动剔除,不用自己排查。") + '</p>' +
|
|
8938
|
-
'<h3 class="help-h3">' + t("
|
|
8939
|
-
'<p class="help-p">' + t("
|
|
8942
|
+
'<h3 class="help-h3">' + t("第三步:启用本机智能体") + '</h3>' +
|
|
8943
|
+
'<p class="help-p">' + t("无需预先绑定模型。CodeBee 默认按任务类型、难度、成本和健康状态自动推荐;需要固定厂商、模型或降级顺序时,再到「设置 → 模型调度(可选)」指定。") + '</p>' +
|
|
8940
8944
|
'<h3 class="help-h3">' + t("报错速查") + '</h3>' +
|
|
8941
8945
|
'<ul class="help-list">' +
|
|
8942
8946
|
'<li><b>401</b><span>' + t("Key 无效或过期——检查或更换 Key。") + '</span></li>' +
|
|
@@ -9001,7 +9005,7 @@ function helpChapterBody(id) {
|
|
|
9001
9005
|
return '<div class="help-qa"><b>' + t("点「获取模型列表」报 404?") + '</b><p>' + t("部分网关不提供模型列表接口,属正常现象;只要能正常对话就不用管,模型名手工填即可。") + '</p></div>' +
|
|
9002
9006
|
'<div class="help-qa"><b>' + t("报 503 / 无可用渠道?") + '</b><p>' + t("该模型当前没有可用渠道,最常见是余额耗尽。查一下余额或换个模型;同一厂商配了多把 Key 会自动轮换。") + '</p></div>' +
|
|
9003
9007
|
'<div class="help-qa"><b>' + t("「冷却中」是什么意思?") + '</b><p>' + t("一把 Key 连续失败会被暂停 30 分钟,防止反复撞墙烧钱,到期自动恢复;也可以在密钥旁手动重置。") + '</p></div>' +
|
|
9004
|
-
'<div class="help-qa"><b>' + t("任务一直显示「排队」?") + '</b><p>' + t("
|
|
9008
|
+
'<div class="help-qa"><b>' + t("任务一直显示「排队」?") + '</b><p>' + t("新任务默认立即运行,不会排队;达到并发保护上限时本次会直接失败并提示稍后重试。只有自动续跑退避会显示预定时间;历史版本遗留的排队记录会由恢复机制立即接管或收口。") + '</p></div>' +
|
|
9005
9009
|
'<div class="help-qa"><b>' + t("状态里写着「将于 HH:MM 自动续跑」?") + '</b><p>' + t("这一步失败了,正在退避等待自动重试,到点会接着跑,不需要手动干预;等不及也可以在详情页手动重试。") + '</p></div>' +
|
|
9006
9010
|
'<div class="help-qa"><b>' + t("生成的文件在哪?") + '</b><p>' + t("写作类任务的章节、封面、报告都落在任务的运行目录,详情页「成果」页签可浏览和预览。代码类任务则在仓库的任务分支上改代码,详情页「版本」里审阅后再合并。") + '</p></div>' +
|
|
9007
9011
|
'<div class="help-qa"><b>' + t("忘了令牌 / 手机打不开页面?") + '</b><p>' + t("服务启动日志里有带令牌的完整访问地址;远程设备必须用带令牌的 URL 打开(或在令牌门里输入一次),否则会一直要求授权。") + '</p></div>' +
|
|
@@ -9304,7 +9308,7 @@ async function suStartupCheck() {
|
|
|
9304
9308
|
}
|
|
9305
9309
|
|
|
9306
9310
|
/* ---------------------------------------------------------- 页签 & 初始化 */
|
|
9307
|
-
const TAB_TITLES = { tasks: "任务", runs: "运行记录", automation: "自动化", zentao: "禅道 Bug 自动修复", usage: "用量统计", agents: "
|
|
9311
|
+
const TAB_TITLES = { tasks: "任务", runs: "运行记录", automation: "自动化", zentao: "禅道 Bug 自动修复", usage: "用量统计", agents: "本机智能体", models: "模型接入", bindings: "模型调度(可选)", skills: "经验库", knowledge: "知识库", market: "插件市场", orch: "编排设置", data: "数据与备份", appearance: "皮肤", about: "关于与更新" };
|
|
9308
9312
|
const SET_TABS = new Set(Object.keys(TAB_TITLES)); // 设置导航里的子页(__phone 是弹框,不算)
|
|
9309
9313
|
|
|
9310
9314
|
function tabTitle(name) {
|
|
@@ -10062,7 +10066,7 @@ function renderBackupPreview(pv) {
|
|
|
10062
10066
|
rows.push('<span class="bad">' + esc(t("以下外部目录不在备份里,需自行拷贝:"))
|
|
10063
10067
|
+ esc(pv.external_workdirs.join("、")) + "</span>");
|
|
10064
10068
|
if ((pv.busy_runs || []).length)
|
|
10065
|
-
rows.push('<span class="bad">' + esc(t("
|
|
10069
|
+
rows.push('<span class="bad">' + esc(t("有任务正在运行或等待自动续跑,先等它们结束再导入")) + "</span>");
|
|
10066
10070
|
el.innerHTML = rows.map((s) => "<div>" + s + "</div>").join("");
|
|
10067
10071
|
if (!(pv.busy_runs || []).length) $("bi-apply").classList.remove("hidden");
|
|
10068
10072
|
}
|
|
@@ -10746,7 +10750,7 @@ function cmdkOps() {
|
|
|
10746
10750
|
{ icon: "i-blocks", label: t("插件市场"), run: () => switchTab("market") },
|
|
10747
10751
|
{ icon: "i-book", label: t("经验库"), run: () => switchTab("skills") },
|
|
10748
10752
|
{ icon: "i-sigma", label: t("知识库"), run: () => switchTab("knowledge") },
|
|
10749
|
-
{ icon: "i-cpu", label: t("
|
|
10753
|
+
{ icon: "i-cpu", label: t("本机智能体"), run: () => switchTab("agents") },
|
|
10750
10754
|
{ icon: "i-chart", label: t("用量统计"), run: () => switchTab("usage") },
|
|
10751
10755
|
{ icon: "i-gear", label: t("设置"), run: () => enterSettings() },
|
|
10752
10756
|
{ icon: "i-bee", label: t("帮助中心"), run: () => welcomeOpen() },
|
package/app/ui/i18n.js
CHANGED
|
@@ -428,8 +428,14 @@
|
|
|
428
428
|
"%)": "%)",
|
|
429
429
|
"(暂无数据)": "(No data yet)",
|
|
430
430
|
"智能体管理": "Agents",
|
|
431
|
+
"本机智能体": "Local agents",
|
|
431
432
|
"模型接入": "Models",
|
|
432
433
|
"CLI 绑定": "CLI bindings",
|
|
434
|
+
"模型调度(可选)": "Model routing (optional)",
|
|
435
|
+
"自动推荐(推荐)": "Automatic recommendation (recommended)",
|
|
436
|
+
"确认禁用模型 {0}?链降级将自动跳过它,其余模型不受影响;可在模型调度页重新启用。": "Disable model {0}? Fallback routing will skip it; other models are unaffected. You can re-enable it under Model routing.",
|
|
437
|
+
"确认禁用该厂商?禁用后链降级自动跳过它,恢复后可在模型调度页重新启用。": "Disable this provider? Fallback routing will skip it. You can re-enable it under Model routing.",
|
|
438
|
+
" 个供应商?\n相关显式模型调度会自动解除,此操作不可撤销。": " providers?\nRelated explicit model routing overrides will be removed. This cannot be undone.",
|
|
433
439
|
"编排设置": "Orchestrator",
|
|
434
440
|
"经验库": "Skill library",
|
|
435
441
|
"知识库": "Knowledge base",
|
|
@@ -503,9 +509,13 @@
|
|
|
503
509
|
"一群 AI 智能体自动分工:规划、实现、评审、打磨,直到交付成果。": "A crew of AI agents splits up the work — plan, build, review, polish — until the result is delivered.",
|
|
504
510
|
"添加模型供应商:设置 → 模型接入,填入 API Key": "Add a model provider: Settings → Models, paste your API key",
|
|
505
511
|
"绑定 CLI 智能体:点「一键推荐绑定」自动配好": "Bind CLI agents: one click on Recommended binding configures them",
|
|
512
|
+
"启用本机智能体:运行时默认自动推荐模型": "Enable local agents: runtime model routing is automatic by default",
|
|
506
513
|
"新建任务:选工作目录、写目标,蜂群开工": "Create a task: pick a work folder, describe the goal, and the hive gets going",
|
|
507
514
|
"开始配置模型": "Set up models",
|
|
508
515
|
"绑定 CLI 智能体": "Bind CLI agents",
|
|
516
|
+
"第三步:启用本机智能体": "Step 3: Enable local agents",
|
|
517
|
+
"无需预先绑定模型。CodeBee 默认按任务类型、难度、成本和健康状态自动推荐;需要固定厂商、模型或降级顺序时,再到「设置 → 模型调度(可选)」指定。": "You do not need to bind a model first. CodeBee recommends one from task type, difficulty, cost and health; use Settings → Model routing only when you need to pin a provider, model or fallback order.",
|
|
518
|
+
"还没有已安装且可编排的 CLI——先到「本机智能体」页安装并启用。": "No installed orchestration-ready CLI yet — install and enable one under Local agents.",
|
|
509
519
|
"先跳过,直接体验": "Skip and explore",
|
|
510
520
|
"可随时在「设置 → 关于与更新」重新打开本引导。": "You can reopen this guide anytime in Settings → About & updates.",
|
|
511
521
|
"可随时按 F1,或在「设置 → 关于与更新」重新打开本帮助中心。": "Press F1 anytime, or reopen this help center in Settings → About & updates.",
|
|
@@ -1006,6 +1016,7 @@
|
|
|
1006
1016
|
"最多选 3 条(1 个主模型 + 2 个降级备选)。": "Up to 3 entries (1 primary + 2 fallbacks).",
|
|
1007
1017
|
"有未保存改动": "Unsaved changes",
|
|
1008
1018
|
"一键推荐绑定": "Auto-fill recommendations",
|
|
1019
|
+
"生成推荐指定方案": "Generate an override plan",
|
|
1009
1020
|
"全部保存": "Save all",
|
|
1010
1021
|
"已为 %1 个 CLI 预填推荐模型,确认无误后点各卡片「保存」,或点右上「全部保存」。": "Prefilled recommended models for %1 CLI(s). Review each card and click \"Save\", or \"Save all\" above.",
|
|
1011
1022
|
"已为 %1 个空链 CLI 预填推荐,并修复 %2 条失效链——确认后保存。": "Prefilled %1 empty chain(s) and repaired %2 broken chain(s) — review and save.",
|
|
@@ -1017,6 +1028,11 @@
|
|
|
1017
1028
|
"没有可推荐的:先到「模型接入」页导入与 CLI 协议匹配的供应商。": "Nothing to recommend — import providers matching the CLI protocols on the \"Models\" page first.",
|
|
1018
1029
|
"所有 CLI 都已配置模型链,无需推荐。": "All CLIs already have model chains — no recommendation needed.",
|
|
1019
1030
|
"一键绑定推荐模型": "Auto-bind recommended models",
|
|
1031
|
+
"写入推荐默认模型(可选)": "Write recommended CLI default (optional)",
|
|
1032
|
+
"默认不需要绑定:系统会按任务类型、难度、成本与健康状态自动推荐可用模型;只有需要固定厂商、模型或降级顺序时才在这里绑定。绑定后编排调用会注入该供应商的 API key 与地址;完全未指定时保持自动调度,没有兼容可用供应商才使用 CLI 自身配置。": "Binding is optional by default: the system recommends models from task type, difficulty, cost and health. Bind only to pin a provider, model or fallback order. With no override, routing stays automatic and uses the CLI's own configuration only when no compatible provider is available.",
|
|
1033
|
+
"(自动推荐厂商与模型;没有兼容可用供应商时才使用 CLI 自身配置)": " (automatically recommends a provider and model; uses the CLI's own configuration only when no compatible provider is available)",
|
|
1034
|
+
"这里管理本机 CLI 的安装、版本、启停、冒烟测试和 CLI 自己的默认模型。CodeBee 运行任务时默认自动推荐厂商与模型,无需绑定;只有需要固定厂商、模型或降级顺序时,才到「模型调度(可选)」页指定。安装命令不明的条目可直接编辑": "Manage local CLI installation, versions, enablement, smoke tests and each CLI's own default model here. CodeBee recommends providers and models automatically at runtime; use Model routing only when you need to pin a provider, model or fallback order. For entries without a known install command, edit",
|
|
1035
|
+
"默认无需绑定:CodeBee 会按任务类型、步骤角色、难度、成本、协议兼容性和健康状态动态推荐厂商与模型,并在无可用供应商时回落 CLI 自身配置。只有需要固定厂商、模型或跨厂商降级顺序时才在这里指定;显式指定会覆盖自动推荐,但不改写 CLI 全局配置。": "No binding is required by default. CodeBee dynamically recommends providers and models from task type, step role, difficulty, cost, protocol compatibility and health, then falls back to the CLI's own configuration when needed. Specify an override here only to pin a provider, model or cross-provider fallback order; overrides do not rewrite the CLI's global configuration.",
|
|
1020
1036
|
"没有支持写入默认模型的已安装智能体。": "No installed agents support writing a default model.",
|
|
1021
1037
|
"所有已安装智能体都已配置默认模型,无需绑定。": "All installed agents already have a default model — nothing to bind.",
|
|
1022
1038
|
"已为 %1 个智能体写入推荐模型,%2 个失败。": "Wrote recommended models to %1 agent(s), %2 failed.",
|
|
@@ -1098,8 +1114,10 @@
|
|
|
1098
1114
|
"先选一个供应商再测试连通": "Pick a provider first",
|
|
1099
1115
|
"当前配置不生效:请检查供应商密钥、启停状态与模型选择。": "Current config is not active: check the API key, enabled state, and model selection.",
|
|
1100
1116
|
"最大并发任务数(多任务同时跑、互不打扰)": "Max concurrent tasks (run in parallel, fully isolated)",
|
|
1117
|
+
"并发保护上限(默认立即运行)": "Concurrency safety limit (starts immediately by default)",
|
|
1101
1118
|
"1 · 串行": "1 · serial",
|
|
1102
|
-
"
|
|
1119
|
+
"任务默认立即运行,不进入等待队列;达到上限时会明确提示稍后重试。同一任务仍单飞,自动续跑的退避等待不占执行位。": "Tasks start immediately by default with no waiting queue. At the safety limit, CodeBee clearly asks you to retry later. Each task remains single-flight, and auto-resume backoff uses no execution slot.",
|
|
1120
|
+
"正在启动": "Starting",
|
|
1103
1121
|
"默认保存路径(新建任务未指定工作目录时使用;支持 ~)": "Default save path (used when a new task doesn't specify one; ~ supported)",
|
|
1104
1122
|
"保存时把「旧默认路径下」的现有任务目录迁移到新路径(运行中的跳过;手动指定的目录不受影响)": "When saving, migrate existing task directories under the old default path to the new one (skip running tasks; manually specified paths are untouched)",
|
|
1105
1123
|
"不沿用(全新开始)": "Don't resume (fresh start)",
|
|
@@ -2095,7 +2113,7 @@
|
|
|
2095
2113
|
"「冷却中」是什么意思?": "What does \"Cooling down\" mean?",
|
|
2096
2114
|
"一把 Key 连续失败会被暂停 30 分钟,防止反复撞墙烧钱,到期自动恢复;也可以在密钥旁手动重置。": "A key that keeps failing is paused for 30 minutes so it stops burning money against a wall; it recovers automatically when time is up, or reset it manually next to the key.",
|
|
2097
2115
|
"任务一直显示「排队」?": "Task stuck on \"Queued\"?",
|
|
2098
|
-
"
|
|
2116
|
+
"新任务默认立即运行,不会排队;达到并发保护上限时本次会直接失败并提示稍后重试。只有自动续跑退避会显示预定时间;历史版本遗留的排队记录会由恢复机制立即接管或收口。": "New tasks start immediately and never wait in a queue. At the concurrency safety limit, the attempt fails clearly and asks you to retry later. Only auto-resume backoff shows a scheduled time; legacy queued records are immediately resumed or closed by recovery.",
|
|
2099
2117
|
"状态里写着「将于 HH:MM 自动续跑」?": "Status says \"Auto-resumes at HH:MM\"?",
|
|
2100
2118
|
"这一步失败了,正在退避等待自动重试,到点会接着跑,不需要手动干预;等不及也可以在详情页手动重试。": "That step failed and is backing off before an automatic retry — it continues on its own at the shown time, no manual action needed; if you can't wait, retry manually from the detail page.",
|
|
2101
2119
|
"生成的文件在哪?": "Where are the generated files?",
|
|
@@ -2217,7 +2235,7 @@
|
|
|
2217
2235
|
"工作目录文件": "Workspace files",
|
|
2218
2236
|
"备份不含运行过程日志": "Backup has no run process logs",
|
|
2219
2237
|
"路径重映射": "Path remap",
|
|
2220
|
-
"
|
|
2238
|
+
"有任务正在运行或等待自动续跑,先等它们结束再导入": "Tasks are running or waiting for auto-resume — wait for them to finish before importing",
|
|
2221
2239
|
"替换导入会先清空本机数据目录,再整体落入备份内容(当前数据已自动留了反悔备份)。确定继续?": "Replace-import wipes the local data directory first, then restores the backup wholesale (a regret backup of current data is saved automatically). Continue?",
|
|
2222
2240
|
"确认导入该备份包?同名任务与配置将被覆盖。": "Import this backup? Tasks and settings with the same names will be overwritten.",
|
|
2223
2241
|
"正在导入…": "Importing…",
|