codebee 0.1.100 → 0.1.102
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 +17 -0
- package/README.md +8 -5
- package/app/core/acp_client.py +34 -3
- package/app/core/board.py +21 -17
- package/app/core/builtin_agent.py +13 -4
- package/app/core/catalog.py +28 -6
- package/app/core/checkpoints.py +117 -31
- package/app/core/eval_matrix.py +10 -1
- package/app/core/mcp_client.py +7 -2
- package/app/core/openapi.py +8 -0
- package/app/core/pipeline.py +86 -5
- package/app/core/plugins.py +9 -2
- package/app/core/policy.py +9 -0
- package/app/core/publish/auto.py +106 -9
- package/app/core/publish/fanqie.py +16 -10
- package/app/core/publish/flow.py +29 -1
- package/app/core/publish/manager.py +8 -0
- package/app/core/sandbox_runtime.py +95 -0
- package/app/core/store.py +31 -4
- package/app/main.py +1 -0
- package/app/ui/app.js +164 -27
- package/app/ui/hive3d.js +491 -223
- package/app/ui/i18n.js +18 -0
- package/app/ui/index.html +24 -4
- package/app/ui/style.css +548 -18
- package/package.json +1 -1
package/app/core/store.py
CHANGED
|
@@ -145,8 +145,15 @@ def create_task(payload):
|
|
|
145
145
|
goal = _text(payload.get("goal"), "goal")
|
|
146
146
|
workdir = _text(payload.get("workdir"), "workdir")
|
|
147
147
|
if not goal:
|
|
148
|
-
|
|
149
|
-
|
|
148
|
+
# 扫榜选材的菜单承诺「方向可留空,默认分析总榜热门题材」——
|
|
149
|
+
# paihang.rank_scan_prompt 与 pipeline 两侧均已支持空 goal,这里放行;
|
|
150
|
+
# 其余类型目标必填不变。
|
|
151
|
+
if payload.get("type") == "rank_scan":
|
|
152
|
+
title = title or flow.get("name") or "扫榜选材"
|
|
153
|
+
else:
|
|
154
|
+
raise ValueError("目标描述不能为空")
|
|
155
|
+
else:
|
|
156
|
+
title = title or goal.splitlines()[0][:30] # 标题可省略,自动取目标首行
|
|
150
157
|
if not workdir:
|
|
151
158
|
# 未指定目录 → 用「默认保存路径」(设置里可改;内置回落 <数据目录>/workspace)。
|
|
152
159
|
# 默认路径允许自动创建;用户显式给的目录仍必须已存在。
|
|
@@ -1723,12 +1730,27 @@ def retry_task(task_id):
|
|
|
1723
1730
|
# 结构——这正是「重写未达标章」按钮(前端 done+未达标态放行
|
|
1724
1731
|
# retry)区别于断点续跑的语义。
|
|
1725
1732
|
redo = set()
|
|
1733
|
+
redo_issues = []
|
|
1726
1734
|
if (prev.get("verdict") or {}).get("publishable") is False:
|
|
1727
1735
|
redo = {int(c["chapter"]) for c in v_scores
|
|
1728
1736
|
if not c.get("passed") and c.get("chapter") is not None}
|
|
1737
|
+
# 未达标原因账(verdict.major_issues,章节级+全局级)随继承
|
|
1738
|
+
# 下发:流水线把本章点名项与全书级项渲染进重写章起草提示词,
|
|
1739
|
+
# 作者第一遍就知道往哪改,不再盲写等修订拉回。被 major 点名
|
|
1740
|
+
# 的过线章一并回炉——总判定卡在 major 上时只重写未过线章
|
|
1741
|
+
# 消不掉账,「重写未达标章」会空转(2026-10-10 实案)。
|
|
1742
|
+
for x in (prev.get("verdict") or {}).get("major_issues") or []:
|
|
1743
|
+
if not isinstance(x, dict):
|
|
1744
|
+
continue
|
|
1745
|
+
redo_issues.append(x)
|
|
1746
|
+
try:
|
|
1747
|
+
redo.add(int(x.get("chapter")))
|
|
1748
|
+
except (TypeError, ValueError):
|
|
1749
|
+
pass
|
|
1729
1750
|
if redo:
|
|
1730
1751
|
done = [n for n in done if n not in redo]
|
|
1731
|
-
v_scores = [c for c in v_scores if c.get("passed")
|
|
1752
|
+
v_scores = [c for c in v_scores if c.get("passed")
|
|
1753
|
+
and c.get("chapter") not in redo]
|
|
1732
1754
|
if done or redo:
|
|
1733
1755
|
if redo:
|
|
1734
1756
|
scores = v_scores # 未达标重写:只带已过线章的分数
|
|
@@ -1743,6 +1765,8 @@ def retry_task(task_id):
|
|
|
1743
1765
|
# 落账的 chapter_scores——否则多轮失败恢复会把全部
|
|
1744
1766
|
# 已过线章节重新评审(实测一晚白烧数百万 token)
|
|
1745
1767
|
"chapter_scores": scores,
|
|
1768
|
+
# 未达标重写的原因账(达标续跑时为空表)
|
|
1769
|
+
"redo_issues": redo_issues,
|
|
1746
1770
|
}
|
|
1747
1771
|
break
|
|
1748
1772
|
else:
|
|
@@ -2111,7 +2135,7 @@ def clear_stream_state(run_id=None, n=None):
|
|
|
2111
2135
|
|
|
2112
2136
|
def finish_step(run_id, n, status, summary="", exit_code=None,
|
|
2113
2137
|
cost_usd=0.0, tokens=0.0, duration_s=None, model=None, output=None,
|
|
2114
|
-
followups=None, thinking=None, partial=False):
|
|
2138
|
+
followups=None, thinking=None, partial=False, acp_events=None):
|
|
2115
2139
|
with LOCK:
|
|
2116
2140
|
run = _RUNS.get(run_id)
|
|
2117
2141
|
if not run:
|
|
@@ -2146,6 +2170,9 @@ def finish_step(run_id, n, status, summary="", exit_code=None,
|
|
|
2146
2170
|
s["partial"] = True
|
|
2147
2171
|
else:
|
|
2148
2172
|
s.pop("partial", None)
|
|
2173
|
+
if acp_events:
|
|
2174
|
+
s["acp_events"] = [dict(item) for item in acp_events[:200]
|
|
2175
|
+
if isinstance(item, dict)]
|
|
2149
2176
|
# 收尾即清运行中态:stream/activity/live 只是过程快照,留着会让
|
|
2150
2177
|
# 前端把「已结束」的步骤仍当实时流渲染(也白占 run.json 体积)
|
|
2151
2178
|
s.pop("stream", None)
|
package/app/main.py
CHANGED
|
@@ -2848,6 +2848,7 @@ class Handler(BaseHTTPRequestHandler):
|
|
|
2848
2848
|
"thinking": s.get("thinking") or "",
|
|
2849
2849
|
"stream": (s.get("stream") or "") if running else "",
|
|
2850
2850
|
"activity": (s.get("activity") or []) if running else [],
|
|
2851
|
+
"acp_events": s.get("acp_events") or [],
|
|
2851
2852
|
"live": s.get("live") or 0,
|
|
2852
2853
|
})
|
|
2853
2854
|
|
package/app/ui/app.js
CHANGED
|
@@ -3123,7 +3123,20 @@ async function createTask() {
|
|
|
3123
3123
|
workdir: $("f-workdir").value.trim(),
|
|
3124
3124
|
thinking: $("f-thinking").value,
|
|
3125
3125
|
};
|
|
3126
|
-
|
|
3126
|
+
const sandboxBackend = $("f-sandbox-backend").value;
|
|
3127
|
+
if (sandboxBackend === "docker") {
|
|
3128
|
+
const dockerImage = $("f-sandbox-image").value.trim();
|
|
3129
|
+
if (!dockerImage) {
|
|
3130
|
+
msg.className = "msg err";
|
|
3131
|
+
msg.textContent = t("选择 Docker 后请填写镜像名");
|
|
3132
|
+
$("f-sandbox-image").focus();
|
|
3133
|
+
resetSubmit();
|
|
3134
|
+
return;
|
|
3135
|
+
}
|
|
3136
|
+
payload.sandbox = { backend: "docker", docker_image: dockerImage };
|
|
3137
|
+
}
|
|
3138
|
+
// 扫榜选材允许目标留空(菜单承诺:默认分析总榜热门题材),其余类型必填
|
|
3139
|
+
if (!payload.goal && payload.type !== "rank_scan") {
|
|
3127
3140
|
msg.className = "msg err";
|
|
3128
3141
|
msg.textContent = t("请先填写目标");
|
|
3129
3142
|
$("f-goal").focus();
|
|
@@ -3252,6 +3265,12 @@ async function createTask() {
|
|
|
3252
3265
|
}
|
|
3253
3266
|
}
|
|
3254
3267
|
|
|
3268
|
+
function syncSandboxComposer() {
|
|
3269
|
+
const docker = $("f-sandbox-backend") && $("f-sandbox-backend").value === "docker";
|
|
3270
|
+
const row = $("row-sandbox-image");
|
|
3271
|
+
if (row) row.classList.toggle("hidden", !docker);
|
|
3272
|
+
}
|
|
3273
|
+
|
|
3255
3274
|
/* ---------------------------------------------------------- 附件(截图/文件) */
|
|
3256
3275
|
/* 待提交附件:选中/粘贴即上传到服务端 pending 区,创建任务时随 payload 落盘
|
|
3257
3276
|
* 到工作目录 _attachments/ 并注入上下文。图片另走 codex 原生 -i 直读;
|
|
@@ -3957,6 +3976,10 @@ async function newFromTaskAsync(id) {
|
|
|
3957
3976
|
// 不随预填带走:新任务要么重新上传,要么由 _attachments 路径引用
|
|
3958
3977
|
$("f-context").value = (tk.context || "").split("\n## 附件材料")[0].trimEnd();
|
|
3959
3978
|
$("f-workdir").value = tk.workdir || "";
|
|
3979
|
+
const taskSandbox = tk.sandbox || {};
|
|
3980
|
+
$("f-sandbox-backend").value = taskSandbox.backend === "docker" ? "docker" : "native";
|
|
3981
|
+
$("f-sandbox-image").value = taskSandbox.docker_image || "";
|
|
3982
|
+
syncSandboxComposer();
|
|
3960
3983
|
S.atts = []; renderAttachChips(); // 附件清单不继承:同目录引用已随 context 保留
|
|
3961
3984
|
queueGitProbe(); // 工作目录变了,重新探测代码版本
|
|
3962
3985
|
$("f-mode").value = ["fast", "expert", "manual"].includes(tk.mode) ? tk.mode : "auto";
|
|
@@ -4815,7 +4838,13 @@ window.loadRunCheckpoints = async function (requestedRunId) {
|
|
|
4815
4838
|
const run = (S.lastRun && S.lastRun.id === runId) ? S.lastRun : latest;
|
|
4816
4839
|
box.dataset.hasRestorable = files.some((file) => file.restorable) ? "true" : "false";
|
|
4817
4840
|
refreshCheckpointRestoreAvailability(run);
|
|
4818
|
-
|
|
4841
|
+
const checkpoint = data.checkpoint || {};
|
|
4842
|
+
const steps = Array.isArray(checkpoint.steps) ? checkpoint.steps : [];
|
|
4843
|
+
const stepSummary = steps.length ? '<div class="rd-checkpoint-steps"><strong>' + esc(t("步骤回放预览")) + '</strong>' +
|
|
4844
|
+
steps.map((step) => '<div class="rd-checkpoint-step"><span>#' + esc(String(step.step ?? "")) + ' · ' + esc(step.role || t("未命名步骤")) + '</span><span class="chip">' + esc(step.status || step.event || "unknown") + (step.attempt ? ' · ' + esc(t("第") + step.attempt + t("次")) : "") + '</span></div>').join("") +
|
|
4845
|
+
(checkpoint.truncated ? '<p class="hint">' + esc(t("仅显示最近 200 个步骤,共 ") + (checkpoint.total_steps || steps.length)) + '</p>' : '') +
|
|
4846
|
+
(checkpoint.replayable ? '<p class="hint">' + esc(t("所有步骤均已完成,可人工复核后继续")) + '</p>' : '<p class="hint">' + esc(t("无法安全自动回放:") + (checkpoint.blocked_reason || t("需要人工核对"))) + '</p>') + '</div>' : '';
|
|
4847
|
+
body.innerHTML = (data.files && data.files.warning ? '<p class="hint">' + esc(data.files.warning) + "</p>" : "") + stepSummary +
|
|
4819
4848
|
(files.length ? files.map((file) => '<article class="rd-checkpoint-row"><strong>' + esc(file.path) + '</strong><span class="chip">' + esc(file.status) + (file.restorable ? " · " + esc(t("可恢复")) : "") + '</span><pre>' + esc(file.diff || "") + "</pre></article>").join("") : '<span class="hint">' + esc(t("此运行没有文件快照")) + "</span>");
|
|
4820
4849
|
box.dataset.runId = runId;
|
|
4821
4850
|
box.classList.remove("needs-load");
|
|
@@ -6185,6 +6214,11 @@ function pbBlock(task, platform) {
|
|
|
6185
6214
|
S.pubAuto.running.platform === platform) ? S.pubAuto.running : null;
|
|
6186
6215
|
if (run && run.status === "running") {
|
|
6187
6216
|
btns += '<span class="pb-book">' + esc((run.as_draft ? t("存草稿中 ") : t("自动发布中 ")) + run.done + "/" + run.total) + "</span>";
|
|
6217
|
+
}
|
|
6218
|
+
if (run && Array.isArray(run.notes) && run.notes.length) {
|
|
6219
|
+
// 对账剔除/跳过/重试的过程记录(auto 层 notes),最近三条
|
|
6220
|
+
btns += '<div class="pb-hint">' +
|
|
6221
|
+
esc(run.notes.slice(-3).join(";")) + "</div>";
|
|
6188
6222
|
} else if (au.pending > 0) {
|
|
6189
6223
|
// 门禁不再禁用按钮:点了走 pbApiWithQualityOverride 的复审/强制对话,
|
|
6190
6224
|
// 死按钮只会让用户无路可走(2026-10-08 实案:guard false 全灰点不了)
|
|
@@ -6209,9 +6243,6 @@ function pbBlock(task, platform) {
|
|
|
6209
6243
|
const key = task.id + ":" + platform;
|
|
6210
6244
|
const sig = its.map((x) => x.n).join(",");
|
|
6211
6245
|
S.pbSel = S.pbSel || {};
|
|
6212
|
-
if (!S.pbSel[key] || S.pbSel[key].sig !== sig)
|
|
6213
|
-
S.pbSel[key] = { sig, sel: new Set(its.map((x) => x.n)) };
|
|
6214
|
-
const sel = S.pbSel[key].sel;
|
|
6215
6246
|
// 连续段按卷名分组;无卷计划的整块平铺不显示卷头
|
|
6216
6247
|
const groups = [];
|
|
6217
6248
|
for (const x of its) {
|
|
@@ -6219,10 +6250,22 @@ function pbBlock(task, platform) {
|
|
|
6219
6250
|
if (g && g.v === x.v) g.ns.push(x.n);
|
|
6220
6251
|
else groups.push({ v: x.v, ns: [x.n] });
|
|
6221
6252
|
}
|
|
6253
|
+
if (!S.pbSel[key] || S.pbSel[key].sig !== sig)
|
|
6254
|
+
S.pbSel[key] = { sig, sel: new Set(its.map((x) => x.n)), groups };
|
|
6255
|
+
const sel = S.pbSel[key].sel;
|
|
6222
6256
|
let chips = "";
|
|
6223
|
-
for (
|
|
6224
|
-
|
|
6225
|
-
|
|
6257
|
+
for (let gi = 0; gi < groups.length; gi++) {
|
|
6258
|
+
const g = groups[gi];
|
|
6259
|
+
if (groups.length > 1 || g.v) {
|
|
6260
|
+
// 卷头=整卷开关:全选打勾、部分选中显示 已选/总(2026-10-10 按卷选章)
|
|
6261
|
+
const cnt = g.ns.filter((n) => sel.has(n)).length;
|
|
6262
|
+
const allOn = cnt === g.ns.length;
|
|
6263
|
+
chips += '<div class="pb-vol"><label class="pb-vol-sel' + (allOn ? " on" : "") +
|
|
6264
|
+
'" title="' + esc(t("勾选卷头=整卷进所选,再点取消")) + '">' +
|
|
6265
|
+
'<input type="checkbox" ' + (allOn ? "checked" : "") +
|
|
6266
|
+
' onchange="pbSelVolume(\'' + esc(task.id) + "','" + platform + "'," + gi + ',this.checked)">' +
|
|
6267
|
+
esc(g.v || t("未分卷")) + t("(") + (allOn ? "" : cnt + "/") + g.ns.length + t(")") + "</label></div>";
|
|
6268
|
+
}
|
|
6226
6269
|
for (const n of g.ns)
|
|
6227
6270
|
chips += '<label class="pb-chip' + (sel.has(n) ? " on" : "") + '">' +
|
|
6228
6271
|
'<input type="checkbox" ' + (sel.has(n) ? "checked" : "") +
|
|
@@ -6580,6 +6623,17 @@ window.pbSelAll = function (taskId, platform, all) {
|
|
|
6580
6623
|
S._pbSig = ""; pbKick();
|
|
6581
6624
|
};
|
|
6582
6625
|
|
|
6626
|
+
window.pbSelVolume = function (taskId, platform, gi, on) {
|
|
6627
|
+
const key = taskId + ":" + platform;
|
|
6628
|
+
const ent = (S.pbSel || {})[key];
|
|
6629
|
+
const g = ent && ent.groups && ent.groups[gi];
|
|
6630
|
+
if (!g || !g.ns.length) return;
|
|
6631
|
+
if (on) for (const n of g.ns) ent.sel.add(n);
|
|
6632
|
+
else for (const n of g.ns) ent.sel.delete(n);
|
|
6633
|
+
pbSelCountSync(key);
|
|
6634
|
+
S._pbSig = ""; pbKick();
|
|
6635
|
+
};
|
|
6636
|
+
|
|
6583
6637
|
window.pbPublishSelected = async function (taskId, platform, autoSubmit) {
|
|
6584
6638
|
const key = taskId + ":" + platform;
|
|
6585
6639
|
const ent = (S.pbSel || {})[key];
|
|
@@ -8578,15 +8632,15 @@ function hiveThinkLine(lines) {
|
|
|
8578
8632
|
* localStorage 持久化/WebGL 不可用降级,以及把 renderHive 的泳道数据翻译给场景。 —— */
|
|
8579
8633
|
let hiveScene = null; // Hive3D 场景实例(懒创建)
|
|
8580
8634
|
let hiveSceneDead = false; // WebGL 不可用/上下文丢失——本次页面周期内不再尝试
|
|
8581
|
-
let hiveMode = "
|
|
8635
|
+
let hiveMode = "live3d"; // live3d is the real scene; reference and 2d remain opt-in views
|
|
8582
8636
|
let hiveRunId = ""; // 最近一次 renderHive 的 run.id(尾巴/思考缓存键前缀)
|
|
8583
8637
|
const hiveLiveMeta = {}; // rel -> { started_at }(3D 芯片秒表用)
|
|
8584
8638
|
let hiveDialogPoll = null;
|
|
8585
8639
|
let hiveDialogKey = "";
|
|
8586
8640
|
|
|
8587
|
-
function setHiveSceneButtons(
|
|
8641
|
+
function setHiveSceneButtons(mode) {
|
|
8588
8642
|
document.querySelectorAll("[data-hive-view]").forEach((button) => {
|
|
8589
|
-
const active = button.dataset.hiveView ===
|
|
8643
|
+
const active = button.dataset.hiveView === mode;
|
|
8590
8644
|
button.classList.toggle("active", active);
|
|
8591
8645
|
button.setAttribute("aria-pressed", active ? "true" : "false");
|
|
8592
8646
|
});
|
|
@@ -8657,28 +8711,80 @@ function closeHiveMonitorLog() {
|
|
|
8657
8711
|
function setHiveSceneMode(mode, opts) {
|
|
8658
8712
|
const viewport = $("rd-hive-viewport");
|
|
8659
8713
|
if (!viewport) return;
|
|
8660
|
-
let
|
|
8661
|
-
if (
|
|
8714
|
+
let selected = ["live3d", "2d"].includes(mode) ? mode : "reference";
|
|
8715
|
+
if (selected !== "2d") {
|
|
8662
8716
|
const sc = ensureHiveScene();
|
|
8663
8717
|
if (!sc) {
|
|
8664
|
-
|
|
8718
|
+
selected = "2d";
|
|
8665
8719
|
if (!(opts && opts.silent)) toast(t("当前环境不支持 WebGL,已切换 2D 列表"), true);
|
|
8666
8720
|
}
|
|
8667
8721
|
}
|
|
8668
|
-
hiveMode =
|
|
8669
|
-
if (hiveScene)
|
|
8670
|
-
|
|
8671
|
-
|
|
8722
|
+
hiveMode = selected;
|
|
8723
|
+
if (hiveScene) {
|
|
8724
|
+
hiveScene.setDisplayMode(selected);
|
|
8725
|
+
hiveScene.setActive(selected !== "2d");
|
|
8726
|
+
}
|
|
8727
|
+
viewport.classList.toggle("hive-mode-3d", selected === "reference");
|
|
8728
|
+
viewport.classList.toggle("hive-mode-live3d", selected === "live3d");
|
|
8729
|
+
viewport.classList.toggle("hive-mode-2d", selected === "2d");
|
|
8672
8730
|
const tools = document.querySelector(".hive-scene-tools");
|
|
8673
|
-
if (tools) tools.classList.toggle("hive-2d",
|
|
8674
|
-
setHiveSceneButtons(
|
|
8675
|
-
|
|
8731
|
+
if (tools) tools.classList.toggle("hive-2d", selected === "2d");
|
|
8732
|
+
setHiveSceneButtons(selected);
|
|
8733
|
+
const hint = document.querySelector(".hive-scene-hint");
|
|
8734
|
+
if (hint) {
|
|
8735
|
+
const hintKey = selected === "live3d"
|
|
8736
|
+
? "拖动旋转 · Shift+拖动平移 · 滚轮缩放 · 双击复位 · 点击屏幕看日志"
|
|
8737
|
+
: selected === "2d" ? "点击格子看日志"
|
|
8738
|
+
: "拖动平移 · 滚轮缩放 · 双击复位 · 点击屏幕看日志";
|
|
8739
|
+
hint.dataset.i18n = hintKey;
|
|
8740
|
+
hint.textContent = t(hintKey);
|
|
8741
|
+
}
|
|
8742
|
+
try { localStorage.setItem("orch.hiveView.v2", hiveMode); } catch (e) { /* 隐私模式等 */ }
|
|
8743
|
+
}
|
|
8744
|
+
|
|
8745
|
+
function syncHiveFullscreenButton() {
|
|
8746
|
+
const stage = $("hive-scene-stage");
|
|
8747
|
+
const button = $("hive-scene-fullscreen");
|
|
8748
|
+
const active = !!stage && document.fullscreenElement === stage;
|
|
8749
|
+
if (stage) stage.classList.toggle("is-fullscreen", active);
|
|
8750
|
+
if (!button) return;
|
|
8751
|
+
const textKey = active ? "退出全屏" : "全屏";
|
|
8752
|
+
const labelKey = active ? "退出全屏" : "进入全屏";
|
|
8753
|
+
button.dataset.i18n = textKey;
|
|
8754
|
+
button.dataset.i18nTitle = labelKey;
|
|
8755
|
+
button.dataset.i18nAria = labelKey;
|
|
8756
|
+
button.textContent = t(textKey);
|
|
8757
|
+
button.title = t(labelKey);
|
|
8758
|
+
button.setAttribute("aria-label", t(labelKey));
|
|
8759
|
+
button.setAttribute("aria-pressed", active ? "true" : "false");
|
|
8760
|
+
}
|
|
8761
|
+
|
|
8762
|
+
async function toggleHiveFullscreen() {
|
|
8763
|
+
const stage = $("hive-scene-stage");
|
|
8764
|
+
if (!stage) return;
|
|
8765
|
+
try {
|
|
8766
|
+
if (document.fullscreenElement === stage) {
|
|
8767
|
+
await document.exitFullscreen();
|
|
8768
|
+
return;
|
|
8769
|
+
}
|
|
8770
|
+
if (typeof stage.requestFullscreen !== "function" || document.fullscreenEnabled === false) {
|
|
8771
|
+
toast(t("当前浏览器不支持全屏"), true);
|
|
8772
|
+
return;
|
|
8773
|
+
}
|
|
8774
|
+
await stage.requestFullscreen();
|
|
8775
|
+
} catch (error) {
|
|
8776
|
+
toast(t("全屏失败:") + (error && error.message ? error.message : ""), true);
|
|
8777
|
+
}
|
|
8676
8778
|
}
|
|
8677
8779
|
|
|
8678
8780
|
function setupHiveSceneControls() {
|
|
8679
8781
|
const viewport = $("rd-hive-viewport");
|
|
8680
8782
|
if (!viewport || viewport.dataset.controlsReady) return;
|
|
8681
8783
|
viewport.dataset.controlsReady = "true";
|
|
8784
|
+
const fullscreenButton = $("hive-scene-fullscreen");
|
|
8785
|
+
if (fullscreenButton) fullscreenButton.addEventListener("click", toggleHiveFullscreen);
|
|
8786
|
+
document.addEventListener("fullscreenchange", syncHiveFullscreenButton);
|
|
8787
|
+
syncHiveFullscreenButton();
|
|
8682
8788
|
const hiveDialog = $("hive-log-dialog");
|
|
8683
8789
|
const hiveClose = $("hive-log-close");
|
|
8684
8790
|
if (hiveClose) hiveClose.addEventListener("click", closeHiveMonitorLog);
|
|
@@ -8691,14 +8797,23 @@ function setupHiveSceneControls() {
|
|
|
8691
8797
|
});
|
|
8692
8798
|
document.querySelectorAll("[data-hive-scene-action]").forEach((button) => {
|
|
8693
8799
|
button.addEventListener("click", () => {
|
|
8694
|
-
if (hiveMode
|
|
8800
|
+
if (hiveMode === "2d" || !hiveScene) return;
|
|
8695
8801
|
if (button.dataset.hiveSceneAction === "reset") hiveScene.resetView();
|
|
8696
8802
|
else hiveScene.zoomAt(button.dataset.hiveSceneAction === "zoom-in" ? 1 / 1.18 : 1.18);
|
|
8697
8803
|
});
|
|
8698
8804
|
});
|
|
8699
|
-
let saved = "
|
|
8700
|
-
try {
|
|
8701
|
-
|
|
8805
|
+
let saved = "live3d";
|
|
8806
|
+
try {
|
|
8807
|
+
saved = localStorage.getItem("orch.hiveView.v2") || "";
|
|
8808
|
+
if (!saved) {
|
|
8809
|
+
// Migrate an explicitly selected 2D/live3D preference; the old default "reference"
|
|
8810
|
+
// was auto-persisted for every new visitor, so it is not a reliable user preference.
|
|
8811
|
+
const legacy = localStorage.getItem("orch.hiveView");
|
|
8812
|
+
saved = legacy === "2d" || legacy === "live3d" ? legacy : "live3d";
|
|
8813
|
+
localStorage.setItem("orch.hiveView.v2", saved);
|
|
8814
|
+
}
|
|
8815
|
+
} catch (e) { /* ignore storage restrictions and keep the live 3D default */ }
|
|
8816
|
+
setHiveSceneMode(["reference", "live3d", "2d"].includes(saved) ? saved : "live3d", { silent: true });
|
|
8702
8817
|
}
|
|
8703
8818
|
|
|
8704
8819
|
/* renderHive → 场景数据翻译:泳道=蜂巢塔的一层,格子按状态映射高度/颜色。
|
|
@@ -8758,7 +8873,8 @@ function hiveSceneSync(run, lanes, byStage) {
|
|
|
8758
8873
|
const sc = ensureHiveScene();
|
|
8759
8874
|
if (!sc) return;
|
|
8760
8875
|
sc.sync(model); // 2D 模式也同步:切回 3D 时数据即 ready(画布未激活零 GPU 开销)
|
|
8761
|
-
sc.
|
|
8876
|
+
sc.setDisplayMode(hiveMode);
|
|
8877
|
+
sc.setActive(hiveMode !== "2d");
|
|
8762
8878
|
}
|
|
8763
8879
|
|
|
8764
8880
|
window.renderHive = function (run) {
|
|
@@ -9322,6 +9438,20 @@ function chatThinkHTML(it) {
|
|
|
9322
9438
|
'<div class="ct-body">' + esc(think) + "</div></details>";
|
|
9323
9439
|
}
|
|
9324
9440
|
|
|
9441
|
+
function chatAcpEventsHTML(it) {
|
|
9442
|
+
const events = Array.isArray(it.acp_events) ? it.acp_events.slice(-8) : [];
|
|
9443
|
+
if (!events.length) return "";
|
|
9444
|
+
return '<details class="chat-think chat-acp-events"><summary>' + esc(t("ACP 活动")) +
|
|
9445
|
+
'<span class="ct-len">' + events.length + ' ' + esc(t("条")) + '</span></summary>' +
|
|
9446
|
+
'<div class="ct-acts">' + events.map((event) => {
|
|
9447
|
+
const type = String(event.sessionUpdate || "event");
|
|
9448
|
+
const label = event.title || event.status || event.modeId || "";
|
|
9449
|
+
const entries = Array.isArray(event.entries) ? event.entries.map((entry) =>
|
|
9450
|
+
'<div class="rd-acp-plan-entry">' + esc(entry.status || "") + ' · ' + esc(entry.content || "") + '</div>').join("") : "";
|
|
9451
|
+
return '<div><code>' + esc(type) + '</code>' + (label ? ' · ' + esc(label) : "") + entries + '</div>';
|
|
9452
|
+
}).join("") + '</div></details>';
|
|
9453
|
+
}
|
|
9454
|
+
|
|
9325
9455
|
/* 运行中直连对话的快轮询:SSE/轮询最密 2s、闲时 8s,思考是逐句吐的——
|
|
9326
9456
|
* 1.2s 直拉时间线把增量打印出来。run 终态、切详情、分区隐藏即停。 */
|
|
9327
9457
|
function stopChatLive() {
|
|
@@ -9424,6 +9554,7 @@ function drawChatFlow(run, data, active) {
|
|
|
9424
9554
|
(it.status || "") + ":" + String(it.text || "").length + ":" +
|
|
9425
9555
|
String(it.thinking || "").length + ":" + String(it.stream || "").length + ":" +
|
|
9426
9556
|
(it.activity || []).length + ":" + (it.followups || []).length +
|
|
9557
|
+
":" + (it.acp_events || []).length +
|
|
9427
9558
|
":" + (it.consumed ? 1 : 0), "") + "|" + ((data && data.result) ? "r" : "");
|
|
9428
9559
|
if (liveSig === chatLiveSig && flow.childElementCount) return;
|
|
9429
9560
|
chatLiveSig = liveSig;
|
|
@@ -9497,6 +9628,7 @@ function drawChatFlow(run, data, active) {
|
|
|
9497
9628
|
'<span class="chat-avatar" aria-hidden="true"><svg class="ico"><use href="#i-bee"></use></svg></span>' +
|
|
9498
9629
|
'<div class="chat-bubble agent">' +
|
|
9499
9630
|
chatThinkHTML(it) +
|
|
9631
|
+
chatAcpEventsHTML(it) +
|
|
9500
9632
|
'<div class="chat-body">' + bodyHtml + "</div>" +
|
|
9501
9633
|
'<div class="chat-meta">' + esc(it.who || "") + " " + esc(chatTime(it.at)) +
|
|
9502
9634
|
(metaBad ? " · " + t(it.status === "failed" ? "失败" : "已取消") : "") + actsA + "</div>" +
|
|
@@ -10389,7 +10521,7 @@ function codeThemeFor(mode) {
|
|
|
10389
10521
|
return ok ? ok.id : (mode === "dark" ? "github-dark" : "github");
|
|
10390
10522
|
}
|
|
10391
10523
|
function codeLineNum() { return localStorage.getItem(CT_LINENUM_KEY) !== "0"; } // 缺省开
|
|
10392
|
-
function codeWrap() { return localStorage.getItem(CT_WRAP_KEY)
|
|
10524
|
+
function codeWrap() { return localStorage.getItem(CT_WRAP_KEY) !== "0"; } // 缺省开:长行自动换行,别撑出横向滚动条
|
|
10393
10525
|
function codeFontSize() {
|
|
10394
10526
|
const n = parseFloat(localStorage.getItem(CT_SIZE_KEY) || "12.5");
|
|
10395
10527
|
return isFinite(n) ? Math.min(22, Math.max(10, n)) : 12.5;
|
|
@@ -16070,6 +16202,9 @@ window.suRestart = suRestart;
|
|
|
16070
16202
|
window.updDismiss = updDismiss;
|
|
16071
16203
|
|
|
16072
16204
|
document.addEventListener("DOMContentLoaded", () => {
|
|
16205
|
+
// Initialize Hive3D controls independently of run data. A fresh workspace may have no
|
|
16206
|
+
// active run yet, but fullscreen/view controls must still be wired and usable.
|
|
16207
|
+
setupHiveSceneControls();
|
|
16073
16208
|
// 远程地址里带的 ?token= 存起来并从地址栏抹掉,之后所有请求走请求头
|
|
16074
16209
|
const urlTok = new URLSearchParams(location.search).get("token");
|
|
16075
16210
|
if (urlTok) {
|
|
@@ -16411,6 +16546,8 @@ document.addEventListener("DOMContentLoaded", () => {
|
|
|
16411
16546
|
localStorage.removeItem("orch.showArchived"); // 清掉旧版持久化残留,避免误解为默认选中
|
|
16412
16547
|
$("f-resume-agent").addEventListener("change", loadSessions);
|
|
16413
16548
|
$("f-resume-session").addEventListener("change", showResumeHint);
|
|
16549
|
+
$("f-sandbox-backend").addEventListener("change", syncSandboxComposer);
|
|
16550
|
+
syncSandboxComposer();
|
|
16414
16551
|
/* 工作目录不再用「上次用过」的旧记忆预填:那样改默认保存路径后,新建任务仍会被
|
|
16415
16552
|
* 旧目录盖过。首屏默认选中设置里的默认路径(loadSettings 落值),克隆/右键新建
|
|
16416
16553
|
* 等显式入口各自直接写字段值,不跨刷新持久化。清掉旧版残留键,避免误解为默认选中。 */
|