codebee 0.1.101 → 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/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
- if (!payload.goal) {
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
- body.innerHTML = (data.files && data.files.warning ? '<p class="hint">' + esc(data.files.warning) + "</p>" : "") +
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 (const g of groups) {
6224
- if (groups.length > 1 || g.v)
6225
- chips += '<div class="pb-vol">' + esc(g.v || t("未分卷")) + "</div>";
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 = "3d"; // 当前视图模式(orch.hiveView)
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(is3d) {
8641
+ function setHiveSceneButtons(mode) {
8588
8642
  document.querySelectorAll("[data-hive-view]").forEach((button) => {
8589
- const active = button.dataset.hiveView === (is3d ? "3d" : "2d");
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 is3d = mode !== "2d";
8661
- if (is3d) {
8714
+ let selected = ["live3d", "2d"].includes(mode) ? mode : "reference";
8715
+ if (selected !== "2d") {
8662
8716
  const sc = ensureHiveScene();
8663
8717
  if (!sc) {
8664
- is3d = false;
8718
+ selected = "2d";
8665
8719
  if (!(opts && opts.silent)) toast(t("当前环境不支持 WebGL,已切换 2D 列表"), true);
8666
8720
  }
8667
8721
  }
8668
- hiveMode = is3d ? "3d" : "2d";
8669
- if (hiveScene) hiveScene.setActive(is3d);
8670
- viewport.classList.toggle("hive-mode-3d", is3d);
8671
- viewport.classList.toggle("hive-mode-2d", !is3d);
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", !is3d);
8674
- setHiveSceneButtons(is3d);
8675
- try { localStorage.setItem("orch.hiveView", hiveMode); } catch (e) { /* 隐私模式等 */ }
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 !== "3d" || !hiveScene) return;
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 = "3d";
8700
- try { saved = localStorage.getItem("orch.hiveView") || "3d"; } catch (e) { /* ignore */ }
8701
- setHiveSceneMode(saved === "2d" ? "2d" : "3d", { silent: true });
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.setActive(hiveMode === "3d");
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>" +
@@ -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
  * 等显式入口各自直接写字段值,不跨刷新持久化。清掉旧版残留键,避免误解为默认选中。 */