codebee 0.1.19 → 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 +13 -0
- package/README.md +21 -14
- 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 +197 -35
- 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 +87 -63
- package/app/ui/i18n.js +22 -3
- package/app/ui/index.html +11 -11
- package/app/ui/style.css +2 -0
- package/package.json +1 -1
package/app/ui/app.js
CHANGED
|
@@ -13,6 +13,12 @@ async function loadFlows() {
|
|
|
13
13
|
renderTypeOptions();
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
+
function invalidateFlowRubricDraft(flowId) {
|
|
17
|
+
if (S.flowRubricDrafts) delete S.flowRubricDrafts[flowId];
|
|
18
|
+
const rubric = $("f-rubric");
|
|
19
|
+
if (rubric && rubric.dataset.flowId === flowId) rubric.dataset.flowId = "";
|
|
20
|
+
}
|
|
21
|
+
|
|
16
22
|
function flowById(id) {
|
|
17
23
|
return (S.flows || []).find((f) => f.id === id) || null;
|
|
18
24
|
}
|
|
@@ -78,7 +84,7 @@ function renderTypeMenu() {
|
|
|
78
84
|
* 拉到手后就地更新菜单项——菜单开着也不重建 DOM,不打断滚动/悬停 */
|
|
79
85
|
async function fetchFlowEstimates() {
|
|
80
86
|
if (!S.flows) return;
|
|
81
|
-
const targets = S.flows
|
|
87
|
+
const targets = S.flows;
|
|
82
88
|
const results = await Promise.all(targets.map(async (f) => {
|
|
83
89
|
try {
|
|
84
90
|
const d = await api("/api/usage/estimate?type=" + encodeURIComponent(f.id) + "&days=90");
|
|
@@ -157,8 +163,18 @@ function onTypeChange() {
|
|
|
157
163
|
if (flow.rounds) $("f-rounds").value = flow.rounds;
|
|
158
164
|
if (flow.threshold) $("f-threshold").value = flow.threshold;
|
|
159
165
|
if (flow.best_of) $("f-bestof").value = flow.best_of;
|
|
160
|
-
const
|
|
161
|
-
|
|
166
|
+
const rubric = $("f-rubric");
|
|
167
|
+
const previousFlow = rubric.dataset.flowId || "";
|
|
168
|
+
if (!S.flowRubricDrafts) S.flowRubricDrafts = {};
|
|
169
|
+
if (previousFlow && previousFlow !== flow.id) {
|
|
170
|
+
S.flowRubricDrafts[previousFlow] = rubric.value;
|
|
171
|
+
}
|
|
172
|
+
if (previousFlow !== flow.id) {
|
|
173
|
+
rubric.value = Object.prototype.hasOwnProperty.call(S.flowRubricDrafts, flow.id)
|
|
174
|
+
? S.flowRubricDrafts[flow.id]
|
|
175
|
+
: (flow.rubric || []).join(", ");
|
|
176
|
+
rubric.dataset.flowId = flow.id;
|
|
177
|
+
}
|
|
162
178
|
// 连载参数预填(用户可改/可清空 = 单稿件模式)
|
|
163
179
|
if (flow.serial) {
|
|
164
180
|
if (!$("f-chapters").value) $("f-chapters").value = flow.serial.chapters || "";
|
|
@@ -410,13 +426,13 @@ function jsq(s) {
|
|
|
410
426
|
}
|
|
411
427
|
|
|
412
428
|
function statusChip(st) {
|
|
413
|
-
const zh = { queued: t("
|
|
429
|
+
const zh = { queued: t("正在启动"), running: t("运行中"), done: t("完成"), failed: t("失败"), cancelled: t("已取消"), timeout: t("超时") };
|
|
414
430
|
return '<span class="chip ' + esc(st) + '">' + (zh[st] || esc(st)) + "</span>";
|
|
415
431
|
}
|
|
416
432
|
|
|
417
433
|
/* 运行状态文案(传 run 对象):退避窗口内的续跑副本写明「将于 HH:MM 自动
|
|
418
434
|
* 续跑」,别让 5 分钟等待看起来像卡死/资源排队(2026-09-18 重写任务误判案)。
|
|
419
|
-
*
|
|
435
|
+
* 到点后翻回「正在启动」——页面轮询重渲染时 Date.now() 已过预定时刻。 */
|
|
420
436
|
function runStatusText(run) {
|
|
421
437
|
const st = String((run && run.status) || "");
|
|
422
438
|
if (st === "queued" && run && run.resume_enqueue_at) {
|
|
@@ -424,7 +440,7 @@ function runStatusText(run) {
|
|
|
424
440
|
if (!isNaN(at) && Date.now() < at)
|
|
425
441
|
return t("将于 {0} 自动续跑", String(run.resume_enqueue_at).slice(11, 16));
|
|
426
442
|
}
|
|
427
|
-
return { queued: t("
|
|
443
|
+
return { queued: t("正在启动"), running: t("运行中"), done: t("完成"),
|
|
428
444
|
failed: t("失败"), cancelled: t("已取消"), timeout: t("超时") }[st] || st;
|
|
429
445
|
}
|
|
430
446
|
|
|
@@ -616,7 +632,7 @@ async function healthOp(op) {
|
|
|
616
632
|
async function healthDisableModel(pid, model) {
|
|
617
633
|
if (!pid || !model) { closeModal(); return; }
|
|
618
634
|
const yes = await uiConfirm(
|
|
619
|
-
t("确认禁用模型 {0}
|
|
635
|
+
t("确认禁用模型 {0}?链降级将自动跳过它,其余模型不受影响;可在模型调度页重新启用。").replace("{0}", pid + " · " + model),
|
|
620
636
|
{ title: t("禁用模型"), danger: true, ok: t("禁用") });
|
|
621
637
|
if (!yes) return;
|
|
622
638
|
try {
|
|
@@ -641,7 +657,7 @@ async function healthDisableModel(pid, model) {
|
|
|
641
657
|
async function healthDisableProvider(pid) {
|
|
642
658
|
if (!pid) { closeModal(); return; }
|
|
643
659
|
const yes = await uiConfirm(
|
|
644
|
-
t("
|
|
660
|
+
t("确认禁用该厂商?禁用后链降级自动跳过它,恢复后可在模型调度页重新启用。"),
|
|
645
661
|
{ title: t("禁用厂商"), danger: true, ok: t("禁用") });
|
|
646
662
|
if (!yes) return;
|
|
647
663
|
try {
|
|
@@ -839,7 +855,7 @@ function renderBindings() {
|
|
|
839
855
|
}
|
|
840
856
|
bbox.innerHTML = targets.map((c) => {
|
|
841
857
|
const b = (S.bindings || {})[c.id] || {};
|
|
842
|
-
const opts = '<option value="">' + t("
|
|
858
|
+
const opts = '<option value="">' + t("自动推荐(推荐)") + '</option>' + bindable.map((p) => {
|
|
843
859
|
const adaptedOnly = p.protocol !== "anthropic" && p.protocol !== "openai";
|
|
844
860
|
return '<option value="' + esc(p.id) + '"' + (b.provider_id === p.id ? " selected" : "") + ">" +
|
|
845
861
|
esc(p.name) + t("(") + esc(protoLabel(p)) + (adaptedOnly ? t(" · 已适配") : "") +
|
|
@@ -863,13 +879,13 @@ function renderBindings() {
|
|
|
863
879
|
return '<div class="card"><div class="head"><span class="name">' + esc(c.name) + "</span>" +
|
|
864
880
|
'<span class="tag">' + esc(c.orch_kind) + "</span></div>" +
|
|
865
881
|
'<div class="field"><label>' + t("供应商") + '</label><select id="bindprov-' + esc(c.id) + '">' + opts + "</select></div>" +
|
|
866
|
-
'<p class="hint">' + t("
|
|
882
|
+
'<p class="hint">' + t("默认不需要绑定:系统会按任务类型、难度、成本与健康状态自动推荐可用模型;只有需要固定厂商、模型或降级顺序时才在这里绑定。绑定后编排调用会注入该供应商的 API key 与地址;完全未指定时保持自动调度,没有兼容可用供应商才使用 CLI 自身配置。") + '</p>' +
|
|
867
883
|
bindModelBox(c, b.provider_id) + offWarn + protoWarn + chainWarn +
|
|
868
884
|
'<div class="ops"><label class="toggle"><input type="checkbox" id="binddiff-' + esc(c.id) + '"' +
|
|
869
885
|
(b.difficulty_routing ? " checked" : "") + '>' + t(" 按难度自动选模型(简单/困难)") + '</label>' +
|
|
870
886
|
'<button class="ghost small" onclick="saveBinding(\'' + esc(c.id) + '\')">' + t("保存") + '</button></div></div>';
|
|
871
887
|
}).join("") +
|
|
872
|
-
(!targets.length ? '<p class="hint">' + t("还没有已安装且可编排的 CLI
|
|
888
|
+
(!targets.length ? '<p class="hint">' + t("还没有已安装且可编排的 CLI——先到「本机智能体」页安装并启用。") + '</p>' : "") +
|
|
873
889
|
(nonBindable ? '<p class="hint">' + t("另有 ") + nonBindable +
|
|
874
890
|
t(" 个供应商(google 等协议)仅登记,不支持注入 CLI,未出现在上面的下拉中。") + '</p>' : "");
|
|
875
891
|
}
|
|
@@ -958,7 +974,7 @@ async function batchProvOp(op) {
|
|
|
958
974
|
const tips = {
|
|
959
975
|
enable: t("启用所选 ") + ids.length + t(" 个供应商?"),
|
|
960
976
|
disable: t("停用所选 ") + ids.length + t(" 个供应商?\n停用后其绑定会回落为 CLI 默认;配置与模型列表都保留,可随时再启用。"),
|
|
961
|
-
delete: t("删除所选 ") + ids.length + t(" 个供应商?\n
|
|
977
|
+
delete: t("删除所选 ") + ids.length + t(" 个供应商?\n相关显式模型调度会自动解除,此操作不可撤销。"),
|
|
962
978
|
};
|
|
963
979
|
if (!await uiConfirm(tips[op] || (t("执行「") + op + t("」?")))) return;
|
|
964
980
|
try {
|
|
@@ -1764,11 +1780,15 @@ async function doImport() {
|
|
|
1764
1780
|
|
|
1765
1781
|
async function saveBinding(id) {
|
|
1766
1782
|
const st = bindSelById(id);
|
|
1783
|
+
const provSel = $("bindprov-" + id);
|
|
1767
1784
|
// 主供应商跟链首走:模型链是唯一真源,链首非空时供应商下拉必须与之一致
|
|
1768
1785
|
// (下拉是旧状态时把旧值发上去,后端「显式指定」就会盖回停用的旧供应商)。
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
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
|
+
}
|
|
1772
1792
|
try {
|
|
1773
1793
|
await api("/api/models/binding", { method: "POST", body: JSON.stringify({
|
|
1774
1794
|
agent_id: id, provider_id: provSel ? provSel.value : "",
|
|
@@ -1910,7 +1930,8 @@ async function createTask() {
|
|
|
1910
1930
|
msg.textContent = t("目标有点简短,先问几个问题…");
|
|
1911
1931
|
try {
|
|
1912
1932
|
const cq = await api("/api/tasks/clarify", {
|
|
1913
|
-
method: "POST",
|
|
1933
|
+
method: "POST", timeout: 75000,
|
|
1934
|
+
body: JSON.stringify({ goal: payload.goal, type: payload.type }) });
|
|
1914
1935
|
const qs = (cq && cq.questions) || [];
|
|
1915
1936
|
if (qs.length) {
|
|
1916
1937
|
S.clarifyDone = true; // 本轮已采访;再点发送直接创建
|
|
@@ -1968,7 +1989,8 @@ async function createTask() {
|
|
|
1968
1989
|
if (critics.length) payload.critics = critics;
|
|
1969
1990
|
}
|
|
1970
1991
|
try {
|
|
1971
|
-
const r = await api("/api/tasks", { method: "POST",
|
|
1992
|
+
const r = await api("/api/tasks", { method: "POST", timeout: 30000,
|
|
1993
|
+
body: JSON.stringify(payload) });
|
|
1972
1994
|
msg.textContent = t("已创建,跳转运行页…");
|
|
1973
1995
|
// 先把新任务刷进 state 再跳:chatEngineIsDirect 靠 S.state.tasks 判引擎,
|
|
1974
1996
|
// 不刷的话对话页签不会就绪,自动选卡落不到「对话」
|
|
@@ -3244,7 +3266,7 @@ function drawTaskDetail(key, runs) {
|
|
|
3244
3266
|
// 取消收尾把僵尸步骤落成「已取消」时要立即重画,不等条数变化;
|
|
3245
3267
|
// 作品信息状态入签名:后台一键生成 running→done 要立刻反映到成果区面板
|
|
3246
3268
|
const bmTask = ((S.state || {}).tasks || []).find((x) => x.id === key);
|
|
3247
|
-
//
|
|
3269
|
+
// 自动续跑退避相位入签名:预定启动时刻过了之后 chip 翻为「正在启动」
|
|
3248
3270
|
const lr0 = runs[0] || {};
|
|
3249
3271
|
const resumePending = (lr0.resume_enqueue_at &&
|
|
3250
3272
|
Date.now() < Date.parse(String(lr0.resume_enqueue_at).replace(" ", "T"))) ? 1 : 0;
|
|
@@ -3258,8 +3280,8 @@ function drawTaskDetail(key, runs) {
|
|
|
3258
3280
|
const ordered = runs.slice(); // 详情按最近运行优先,方便排查
|
|
3259
3281
|
const totalSteps = runs.reduce((a, r) => a + (r.steps || []).length, 0);
|
|
3260
3282
|
const active = runs.some((r) => r.status === "running" || r.status === "queued");
|
|
3261
|
-
//
|
|
3262
|
-
//
|
|
3283
|
+
// 活跃态细分真实状态:queued 仅用于自动续跑退避或创建到起跑的瞬时状态;
|
|
3284
|
+
// 前者在 chip 上写明下一轮何时起跑,避免退避窗口看起来像卡死。
|
|
3263
3285
|
const activeRun0 = runs.find((r) => r.status === "running" || r.status === "queued");
|
|
3264
3286
|
const st = activeRun0 ? activeRun0.status : latest.status;
|
|
3265
3287
|
const resumeIn = (st === "queued" && resumePending)
|
|
@@ -3269,7 +3291,7 @@ function drawTaskDetail(key, runs) {
|
|
|
3269
3291
|
chip.className = "chip " + st;
|
|
3270
3292
|
chip.textContent = resumeIn
|
|
3271
3293
|
? t("将于 ") + resumeIn + t(" 自动续跑(第 ") + (Number(latest.auto_resumes) || 0) + t(" 次)")
|
|
3272
|
-
: ({ queued: t("
|
|
3294
|
+
: ({ queued: t("正在启动"), running: t("运行中"), done: t("完成"), failed: t("失败"), cancelled: t("已取消") }[st] || st);
|
|
3273
3295
|
const bpt = $("btn-pause");
|
|
3274
3296
|
if (bpt) bpt.classList.add("hidden");
|
|
3275
3297
|
$("btn-delete").classList.add("hidden");
|
|
@@ -6041,12 +6063,17 @@ function chatResultHTML(run, res) {
|
|
|
6041
6063
|
if (res.turns) meta.push(res.turns + " " + t("轮对话"));
|
|
6042
6064
|
if (res.duration_s != null) meta.push(chatDurTxt(res.duration_s));
|
|
6043
6065
|
const files = res.files || [];
|
|
6044
|
-
const chips = files.map((f) =>
|
|
6045
|
-
|
|
6046
|
-
|
|
6047
|
-
|
|
6048
|
-
|
|
6049
|
-
|
|
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("");
|
|
6050
6077
|
return '<div class="chat-row">' +
|
|
6051
6078
|
'<span class="chat-avatar" aria-hidden="true"><svg class="ico"><use href="' + icon + '"></use></svg></span>' +
|
|
6052
6079
|
'<div class="chat-result' + (ok ? "" : bad ? " bad" : " off") + '">' +
|
|
@@ -6861,12 +6888,11 @@ function chainDeadReasons(c, chain) {
|
|
|
6861
6888
|
return dead;
|
|
6862
6889
|
}
|
|
6863
6890
|
|
|
6864
|
-
/*
|
|
6865
|
-
*
|
|
6866
|
-
|
|
6867
|
-
function bindRepairAction(c, st) {
|
|
6891
|
+
/* 单条显式绑定链的推荐修复动作:需要改返回新链,不动返回 null。
|
|
6892
|
+
* 空链代表默认自动调度,不能悄悄写成持久绑定;已有链失效时才推荐替代链。 */
|
|
6893
|
+
function bindRepairAction(c, st, includeEmpty) {
|
|
6868
6894
|
const rec = recommendFor(c);
|
|
6869
|
-
if (!st.chain.length) return rec ? [rec] : null;
|
|
6895
|
+
if (!st.chain.length) return includeEmpty && rec ? [rec] : null;
|
|
6870
6896
|
const dead = chainDeadReasons(c, st.chain);
|
|
6871
6897
|
const allDead = dead.every((d) => d);
|
|
6872
6898
|
const headDead = dead[0] !== ""; // 空 p = CLI 默认凭据,是有意配置不算死
|
|
@@ -6881,7 +6907,7 @@ function autoBindAll() {
|
|
|
6881
6907
|
let filled = 0, skipped = 0, noProv = 0, refilled = 0;
|
|
6882
6908
|
for (const c of targets) {
|
|
6883
6909
|
const st = bindSelById(c.id);
|
|
6884
|
-
const act = bindRepairAction(c, st);
|
|
6910
|
+
const act = bindRepairAction(c, st, true);
|
|
6885
6911
|
if (!act) {
|
|
6886
6912
|
// 没动它:分清「健康链无需推荐」和「没有可推荐的」两种落空
|
|
6887
6913
|
if (!st.chain.length) { noProv++; continue; }
|
|
@@ -6910,9 +6936,8 @@ function autoBindAll() {
|
|
|
6910
6936
|
}
|
|
6911
6937
|
}
|
|
6912
6938
|
|
|
6913
|
-
/*
|
|
6914
|
-
*
|
|
6915
|
-
* 但直接落盘(自动场景没有人工确认环节);目录页——空默认模型直填。
|
|
6939
|
+
/* 厂商/模型停用·启用·删除后自动修复显式绑定:
|
|
6940
|
+
* 空链保持自动调度,不写入绑定;已有链失效时才直接落盘替代链。
|
|
6916
6941
|
* 没有合适的推荐就保持原样,什么都不绑。绑定页上用户手改中的草稿(dirty)
|
|
6917
6942
|
* 不碰;一处都没改成静默返回,不打扰停用/启用的操作反馈。 */
|
|
6918
6943
|
let _autoRebindRunning = false, _autoRebindAgain = false;
|
|
@@ -6925,7 +6950,7 @@ async function autoRebindSoon() {
|
|
|
6925
6950
|
for (const c of (S.catalog || []).filter((x) => x.installed && x.orch_kind)) {
|
|
6926
6951
|
const st = bindSelById(c.id);
|
|
6927
6952
|
if (st.dirty) continue; // 用户手改中,不覆盖草稿
|
|
6928
|
-
const act = bindRepairAction(c, st);
|
|
6953
|
+
const act = bindRepairAction(c, st, false);
|
|
6929
6954
|
if (!act) continue;
|
|
6930
6955
|
const b = (S.bindings || {})[c.id] || {};
|
|
6931
6956
|
try {
|
|
@@ -6937,17 +6962,6 @@ async function autoRebindSoon() {
|
|
|
6937
6962
|
fixed++;
|
|
6938
6963
|
} catch (e) { /* 单条失败不打断,等下次变更再补 */ }
|
|
6939
6964
|
}
|
|
6940
|
-
for (const c of (S.catalog || []).filter((x) =>
|
|
6941
|
-
x.installed && x.config_writable && !fmtModel(x.model))) {
|
|
6942
|
-
const rec = recommendFor(c);
|
|
6943
|
-
if (!rec) continue;
|
|
6944
|
-
try {
|
|
6945
|
-
const r = await api("/api/catalog/" + encodeURIComponent(c.id) + "/model",
|
|
6946
|
-
{ method: "POST", body: JSON.stringify({ model: rec.m }) });
|
|
6947
|
-
c.model = r.model || rec.m;
|
|
6948
|
-
fixed++;
|
|
6949
|
-
} catch (e) { /* 同上 */ }
|
|
6950
|
-
}
|
|
6951
6965
|
if (fixed) {
|
|
6952
6966
|
S.catSig = null; S.bindSig = null;
|
|
6953
6967
|
render();
|
|
@@ -7008,7 +7022,7 @@ function bindModelBox(c, provId) {
|
|
|
7008
7022
|
}).join("")
|
|
7009
7023
|
: '<span class="hint">' + t("未设置") + (provId
|
|
7010
7024
|
? t("(按供应商/难度自动解析——供应商协议不匹配或被停用时解析为空,相关步骤将判失败)")
|
|
7011
|
-
: t("
|
|
7025
|
+
: t("(自动推荐厂商与模型;没有兼容可用供应商时才使用 CLI 自身配置)")) + "</span>";
|
|
7012
7026
|
return '<div class="field"><label>' + t("运行时模型链(跨厂商,最多 ") + MAX_ORCH_MODELS + t(" 条)") + "</label>" +
|
|
7013
7027
|
'<div class="orch-row">' + chips +
|
|
7014
7028
|
'<button class="ghost small" onclick="bindToggle(\'' + esc(c.id) + '\')">' +
|
|
@@ -7417,7 +7431,8 @@ function openFlowsManager() {
|
|
|
7417
7431
|
const rows = flows.map((f) =>
|
|
7418
7432
|
'<div class="item"><div class="t"><span class="name">' + flowIconHtml(f) + " " + esc(t(f.name)) +
|
|
7419
7433
|
'</span><span class="tag">' + esc(f.id) + "</span>" +
|
|
7420
|
-
'<span class="tag">' + (f.engine === "code" ? t("代码引擎")
|
|
7434
|
+
'<span class="tag">' + (f.engine === "code" ? t("代码引擎")
|
|
7435
|
+
: f.engine === "direct" ? t("直连引擎") : t("评审引擎")) + "</span>" +
|
|
7421
7436
|
(f.serial ? '<span class="tag">' + t("连载 ") + f.serial.chapters + t(" 章") + "</span>" : "") +
|
|
7422
7437
|
(f.builtin ? '<span class="tag ok">' + t("预置") + '</span>' : '<span class="tag">' + t("自定义") + '</span>') +
|
|
7423
7438
|
(f.edited ? '<span class="tag">' + t("已改") + '</span>' : "") +
|
|
@@ -7440,6 +7455,7 @@ async function flowReset(fid) {
|
|
|
7440
7455
|
if (!await uiConfirm(t("把「") + fid + t("」恢复为内置默认配置?"), { ok: t("恢复") })) return;
|
|
7441
7456
|
try { await api("/api/flows/" + encodeURIComponent(fid) + "/reset", { method: "POST" }); }
|
|
7442
7457
|
catch (e) { toast(t("恢复失败:") + e.message, true); return; }
|
|
7458
|
+
invalidateFlowRubricDraft(fid);
|
|
7443
7459
|
await loadFlows();
|
|
7444
7460
|
openFlowsManager();
|
|
7445
7461
|
toast(t("已恢复默认"));
|
|
@@ -7456,7 +7472,8 @@ function flowForm(fid) {
|
|
|
7456
7472
|
'<div class="grid-2">' +
|
|
7457
7473
|
'<div class="field"><label>' + t("名称 ") + '<span class="req">*</span></label><input id="fl-name" value="' +
|
|
7458
7474
|
esc(f ? f.name : "") + '" placeholder="' + t("例:播客脚本") + '"></div>' +
|
|
7459
|
-
'<div class="field"><label>' + t("引擎 ") + '<span class="req">*</span></label><select id="fl-engine"' +
|
|
7475
|
+
'<div class="field"><label>' + t("引擎 ") + '<span class="req">*</span></label><select id="fl-engine" data-fid="' +
|
|
7476
|
+
esc(f ? f.id : "") + '"' +
|
|
7460
7477
|
(engLocked ? " disabled" : "") + '>' +
|
|
7461
7478
|
'<option value="review"' + (engine === "review" ? " selected" : "") + '>' + t("评审引擎(起草 → 多维评审 → 修订 → 门禁)") + '</option>' +
|
|
7462
7479
|
'<option value="code"' + (engine === "code" ? " selected" : "") + '>' + t("代码引擎(实现 → 验证 → 评审 → 修复)") + '</option>' +
|
|
@@ -7530,6 +7547,7 @@ async function saveFlow() {
|
|
|
7530
7547
|
await api("/api/flows", { method: "POST", body: JSON.stringify(payload) });
|
|
7531
7548
|
} catch (e) { toast(t("保存失败:") + e.message, true); return; }
|
|
7532
7549
|
closeModal();
|
|
7550
|
+
invalidateFlowRubricDraft(id);
|
|
7533
7551
|
await loadFlows();
|
|
7534
7552
|
openFlowsManager();
|
|
7535
7553
|
toast(t("流程已保存"));
|
|
@@ -7807,11 +7825,11 @@ function autoTplPace(tp) {
|
|
|
7807
7825
|
});
|
|
7808
7826
|
}
|
|
7809
7827
|
|
|
7810
|
-
/* last_status → 徽章:
|
|
7828
|
+
/* last_status → 徽章:started=正常灰、error=红「拉起失败」、missed=黄「已错过」 */
|
|
7811
7829
|
function autoStatusTag(tsk) {
|
|
7812
7830
|
if (tsk.last_status === "error") return '<span class="tag auto-tag-err">' + t("拉起失败") + "</span>";
|
|
7813
7831
|
if (tsk.last_status === "missed") return '<span class="tag auto-tag-miss">' + t("已错过") + "</span>";
|
|
7814
|
-
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>";
|
|
7815
7833
|
return "";
|
|
7816
7834
|
}
|
|
7817
7835
|
|
|
@@ -8470,7 +8488,11 @@ function mkrDebounce() {
|
|
|
8470
8488
|
|
|
8471
8489
|
/* 拉一页:reset=清空已累积列表(首进/搜索/换来源/刷新后)。 */
|
|
8472
8490
|
async function mkrLoadPage(reset) {
|
|
8473
|
-
|
|
8491
|
+
// 翻页保持单飞;搜索/换来源/刷新属于重置请求,可以抢占旧请求。
|
|
8492
|
+
// 旧响应回来时由 requestKey 丢弃,不能覆盖用户刚选的新条件。
|
|
8493
|
+
if (S.mkrLoading && !reset) return;
|
|
8494
|
+
const requestKey = (S.mkrRequestKey || 0) + 1;
|
|
8495
|
+
S.mkrRequestKey = requestKey;
|
|
8474
8496
|
if (reset) { S.mkrAll = []; S.mkrOffset = 0; }
|
|
8475
8497
|
const q = (($("mkr-search") || {}).value || "").trim();
|
|
8476
8498
|
const srcSel = $("mkr-source");
|
|
@@ -8480,6 +8502,7 @@ async function mkrLoadPage(reset) {
|
|
|
8480
8502
|
S.mkrLoading = true;
|
|
8481
8503
|
let data = null;
|
|
8482
8504
|
try { data = await api(url); } catch (e) { data = null; }
|
|
8505
|
+
if (requestKey !== S.mkrRequestKey) return;
|
|
8483
8506
|
S.mkrLoading = false;
|
|
8484
8507
|
if (!data) { S.marketRemote = null; S.mkrAll = null; renderMarketRemote(); return; }
|
|
8485
8508
|
S.marketRemote = data;
|
|
@@ -8567,7 +8590,8 @@ async function mkrRefresh() {
|
|
|
8567
8590
|
const old = btn.textContent;
|
|
8568
8591
|
btn.textContent = t("拉取中…");
|
|
8569
8592
|
try {
|
|
8570
|
-
const r = await api("/api/market/remote/refresh", {
|
|
8593
|
+
const r = await api("/api/market/remote/refresh", {
|
|
8594
|
+
method: "POST", timeout: 120000, body: "{}" });
|
|
8571
8595
|
const bad = (r.refresh || []).filter((x) => !x.ok);
|
|
8572
8596
|
if (bad.length) toast(t("部分来源拉取失败:") + bad.map((x) => x.error || x.id).join(t(";")), true);
|
|
8573
8597
|
else toast(t("拉取成功"));
|
|
@@ -8900,13 +8924,13 @@ function helpChapterBody(id) {
|
|
|
8900
8924
|
if (id === "quickstart") {
|
|
8901
8925
|
return '<div class="welcome-steps">' +
|
|
8902
8926
|
'<div class="wstep"><b>1</b><span>' + t("添加模型供应商:设置 → 模型接入,填入 API Key") + '</span></div>' +
|
|
8903
|
-
'<div class="wstep"><b>2</b><span>' + t("
|
|
8927
|
+
'<div class="wstep"><b>2</b><span>' + t("启用本机智能体:运行时默认自动推荐模型") + '</span></div>' +
|
|
8904
8928
|
'<div class="wstep"><b>3</b><span>' + t("新建任务:选工作目录、写目标,蜂群开工") + '</span></div>' +
|
|
8905
8929
|
'</div>' +
|
|
8906
8930
|
'<p class="help-note">' + t("任务跑起来后,详情页能看到步骤、蜂巢、成果文件与 Git 版本;「直接执行」类任务还能像聊天一样边跑边追加消息。") + '</p>' +
|
|
8907
8931
|
'<div class="welcome-acts">' +
|
|
8908
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>' +
|
|
8909
|
-
'<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>' +
|
|
8910
8934
|
'<button class="wl-btn ghost" onclick="welcomeClose()"><span>' + t("先跳过,直接体验") + '</span></button>' +
|
|
8911
8935
|
'</div>';
|
|
8912
8936
|
}
|
|
@@ -8915,8 +8939,8 @@ function helpChapterBody(id) {
|
|
|
8915
8939
|
'<p class="help-p">' + t("「设置 → 模型接入 → 添加供应商」:填名称、API Key、接口地址(一般用默认)。保存后点「获取模型列表」自动拉取该厂商的模型;网关不提供列表接口时直接手工填模型名即可。同一厂商可配多把 Key,按顺序轮换,某把欠费自动切下一把。") + '</p>' +
|
|
8916
8940
|
'<h3 class="help-h3">' + t("第二步:认识协议徽章") + '</h3>' +
|
|
8917
8941
|
'<p class="help-p">' + t("新供应商默认自动识别协议,保存后在后台实测支持哪些调用形态,结果以徽章标在卡片上。带「· chat」表示该模型只适合站内直连对话;codex 这类只讲 responses 协议的 CLI 用不了它,绑定页会自动剔除,不用自己排查。") + '</p>' +
|
|
8918
|
-
'<h3 class="help-h3">' + t("
|
|
8919
|
-
'<p class="help-p">' + t("
|
|
8942
|
+
'<h3 class="help-h3">' + t("第三步:启用本机智能体") + '</h3>' +
|
|
8943
|
+
'<p class="help-p">' + t("无需预先绑定模型。CodeBee 默认按任务类型、难度、成本和健康状态自动推荐;需要固定厂商、模型或降级顺序时,再到「设置 → 模型调度(可选)」指定。") + '</p>' +
|
|
8920
8944
|
'<h3 class="help-h3">' + t("报错速查") + '</h3>' +
|
|
8921
8945
|
'<ul class="help-list">' +
|
|
8922
8946
|
'<li><b>401</b><span>' + t("Key 无效或过期——检查或更换 Key。") + '</span></li>' +
|
|
@@ -8981,7 +9005,7 @@ function helpChapterBody(id) {
|
|
|
8981
9005
|
return '<div class="help-qa"><b>' + t("点「获取模型列表」报 404?") + '</b><p>' + t("部分网关不提供模型列表接口,属正常现象;只要能正常对话就不用管,模型名手工填即可。") + '</p></div>' +
|
|
8982
9006
|
'<div class="help-qa"><b>' + t("报 503 / 无可用渠道?") + '</b><p>' + t("该模型当前没有可用渠道,最常见是余额耗尽。查一下余额或换个模型;同一厂商配了多把 Key 会自动轮换。") + '</p></div>' +
|
|
8983
9007
|
'<div class="help-qa"><b>' + t("「冷却中」是什么意思?") + '</b><p>' + t("一把 Key 连续失败会被暂停 30 分钟,防止反复撞墙烧钱,到期自动恢复;也可以在密钥旁手动重置。") + '</p></div>' +
|
|
8984
|
-
'<div class="help-qa"><b>' + t("任务一直显示「排队」?") + '</b><p>' + t("
|
|
9008
|
+
'<div class="help-qa"><b>' + t("任务一直显示「排队」?") + '</b><p>' + t("新任务默认立即运行,不会排队;达到并发保护上限时本次会直接失败并提示稍后重试。只有自动续跑退避会显示预定时间;历史版本遗留的排队记录会由恢复机制立即接管或收口。") + '</p></div>' +
|
|
8985
9009
|
'<div class="help-qa"><b>' + t("状态里写着「将于 HH:MM 自动续跑」?") + '</b><p>' + t("这一步失败了,正在退避等待自动重试,到点会接着跑,不需要手动干预;等不及也可以在详情页手动重试。") + '</p></div>' +
|
|
8986
9010
|
'<div class="help-qa"><b>' + t("生成的文件在哪?") + '</b><p>' + t("写作类任务的章节、封面、报告都落在任务的运行目录,详情页「成果」页签可浏览和预览。代码类任务则在仓库的任务分支上改代码,详情页「版本」里审阅后再合并。") + '</p></div>' +
|
|
8987
9011
|
'<div class="help-qa"><b>' + t("忘了令牌 / 手机打不开页面?") + '</b><p>' + t("服务启动日志里有带令牌的完整访问地址;远程设备必须用带令牌的 URL 打开(或在令牌门里输入一次),否则会一直要求授权。") + '</p></div>' +
|
|
@@ -9284,7 +9308,7 @@ async function suStartupCheck() {
|
|
|
9284
9308
|
}
|
|
9285
9309
|
|
|
9286
9310
|
/* ---------------------------------------------------------- 页签 & 初始化 */
|
|
9287
|
-
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: "关于与更新" };
|
|
9288
9312
|
const SET_TABS = new Set(Object.keys(TAB_TITLES)); // 设置导航里的子页(__phone 是弹框,不算)
|
|
9289
9313
|
|
|
9290
9314
|
function tabTitle(name) {
|
|
@@ -10042,7 +10066,7 @@ function renderBackupPreview(pv) {
|
|
|
10042
10066
|
rows.push('<span class="bad">' + esc(t("以下外部目录不在备份里,需自行拷贝:"))
|
|
10043
10067
|
+ esc(pv.external_workdirs.join("、")) + "</span>");
|
|
10044
10068
|
if ((pv.busy_runs || []).length)
|
|
10045
|
-
rows.push('<span class="bad">' + esc(t("
|
|
10069
|
+
rows.push('<span class="bad">' + esc(t("有任务正在运行或等待自动续跑,先等它们结束再导入")) + "</span>");
|
|
10046
10070
|
el.innerHTML = rows.map((s) => "<div>" + s + "</div>").join("");
|
|
10047
10071
|
if (!(pv.busy_runs || []).length) $("bi-apply").classList.remove("hidden");
|
|
10048
10072
|
}
|
|
@@ -10726,7 +10750,7 @@ function cmdkOps() {
|
|
|
10726
10750
|
{ icon: "i-blocks", label: t("插件市场"), run: () => switchTab("market") },
|
|
10727
10751
|
{ icon: "i-book", label: t("经验库"), run: () => switchTab("skills") },
|
|
10728
10752
|
{ icon: "i-sigma", label: t("知识库"), run: () => switchTab("knowledge") },
|
|
10729
|
-
{ icon: "i-cpu", label: t("
|
|
10753
|
+
{ icon: "i-cpu", label: t("本机智能体"), run: () => switchTab("agents") },
|
|
10730
10754
|
{ icon: "i-chart", label: t("用量统计"), run: () => switchTab("usage") },
|
|
10731
10755
|
{ icon: "i-gear", label: t("设置"), run: () => enterSettings() },
|
|
10732
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.",
|
|
@@ -761,6 +771,7 @@
|
|
|
761
771
|
"手动:自行指定实现者与评审组": "Manual: pick implementer and reviewers yourself",
|
|
762
772
|
"代码引擎": "Code engine",
|
|
763
773
|
"评审引擎": "Review engine",
|
|
774
|
+
"直连引擎": "Direct engine",
|
|
764
775
|
"实现 → 验证 → 评审": "Implement → verify → review",
|
|
765
776
|
"起草 → 多维评审 → 门禁": "Draft → multi-dim review → gate",
|
|
766
777
|
"大纲 → 逐章起草评审 → 合并(可续跑)": "Outline → per-chapter draft & review → merge (resumable)",
|
|
@@ -1005,6 +1016,7 @@
|
|
|
1005
1016
|
"最多选 3 条(1 个主模型 + 2 个降级备选)。": "Up to 3 entries (1 primary + 2 fallbacks).",
|
|
1006
1017
|
"有未保存改动": "Unsaved changes",
|
|
1007
1018
|
"一键推荐绑定": "Auto-fill recommendations",
|
|
1019
|
+
"生成推荐指定方案": "Generate an override plan",
|
|
1008
1020
|
"全部保存": "Save all",
|
|
1009
1021
|
"已为 %1 个 CLI 预填推荐模型,确认无误后点各卡片「保存」,或点右上「全部保存」。": "Prefilled recommended models for %1 CLI(s). Review each card and click \"Save\", or \"Save all\" above.",
|
|
1010
1022
|
"已为 %1 个空链 CLI 预填推荐,并修复 %2 条失效链——确认后保存。": "Prefilled %1 empty chain(s) and repaired %2 broken chain(s) — review and save.",
|
|
@@ -1016,6 +1028,11 @@
|
|
|
1016
1028
|
"没有可推荐的:先到「模型接入」页导入与 CLI 协议匹配的供应商。": "Nothing to recommend — import providers matching the CLI protocols on the \"Models\" page first.",
|
|
1017
1029
|
"所有 CLI 都已配置模型链,无需推荐。": "All CLIs already have model chains — no recommendation needed.",
|
|
1018
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.",
|
|
1019
1036
|
"没有支持写入默认模型的已安装智能体。": "No installed agents support writing a default model.",
|
|
1020
1037
|
"所有已安装智能体都已配置默认模型,无需绑定。": "All installed agents already have a default model — nothing to bind.",
|
|
1021
1038
|
"已为 %1 个智能体写入推荐模型,%2 个失败。": "Wrote recommended models to %1 agent(s), %2 failed.",
|
|
@@ -1097,8 +1114,10 @@
|
|
|
1097
1114
|
"先选一个供应商再测试连通": "Pick a provider first",
|
|
1098
1115
|
"当前配置不生效:请检查供应商密钥、启停状态与模型选择。": "Current config is not active: check the API key, enabled state, and model selection.",
|
|
1099
1116
|
"最大并发任务数(多任务同时跑、互不打扰)": "Max concurrent tasks (run in parallel, fully isolated)",
|
|
1117
|
+
"并发保护上限(默认立即运行)": "Concurrency safety limit (starts immediately by default)",
|
|
1100
1118
|
"1 · 串行": "1 · serial",
|
|
1101
|
-
"
|
|
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",
|
|
1102
1121
|
"默认保存路径(新建任务未指定工作目录时使用;支持 ~)": "Default save path (used when a new task doesn't specify one; ~ supported)",
|
|
1103
1122
|
"保存时把「旧默认路径下」的现有任务目录迁移到新路径(运行中的跳过;手动指定的目录不受影响)": "When saving, migrate existing task directories under the old default path to the new one (skip running tasks; manually specified paths are untouched)",
|
|
1104
1123
|
"不沿用(全新开始)": "Don't resume (fresh start)",
|
|
@@ -2094,7 +2113,7 @@
|
|
|
2094
2113
|
"「冷却中」是什么意思?": "What does \"Cooling down\" mean?",
|
|
2095
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.",
|
|
2096
2115
|
"任务一直显示「排队」?": "Task stuck on \"Queued\"?",
|
|
2097
|
-
"
|
|
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.",
|
|
2098
2117
|
"状态里写着「将于 HH:MM 自动续跑」?": "Status says \"Auto-resumes at HH:MM\"?",
|
|
2099
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.",
|
|
2100
2119
|
"生成的文件在哪?": "Where are the generated files?",
|
|
@@ -2216,7 +2235,7 @@
|
|
|
2216
2235
|
"工作目录文件": "Workspace files",
|
|
2217
2236
|
"备份不含运行过程日志": "Backup has no run process logs",
|
|
2218
2237
|
"路径重映射": "Path remap",
|
|
2219
|
-
"
|
|
2238
|
+
"有任务正在运行或等待自动续跑,先等它们结束再导入": "Tasks are running or waiting for auto-resume — wait for them to finish before importing",
|
|
2220
2239
|
"替换导入会先清空本机数据目录,再整体落入备份内容(当前数据已自动留了反悔备份)。确定继续?": "Replace-import wipes the local data directory first, then restores the backup wholesale (a regret backup of current data is saved automatically). Continue?",
|
|
2221
2240
|
"确认导入该备份包?同名任务与配置将被覆盖。": "Import this backup? Tasks and settings with the same names will be overwritten.",
|
|
2222
2241
|
"正在导入…": "Importing…",
|