codebee 0.1.28 → 0.1.30

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
@@ -1,6 +1,9 @@
1
1
  /* CodeBee 前端(无依赖;状态走 SSE 实时推送,断线自动降级轮询) */
2
2
  "use strict";
3
3
 
4
+ // 桌宠通过窗口标题里的端口识别并复用当前 CodeBee 实例。
5
+ document.title = "CodeBee [" + (location.port || (location.protocol === "https:" ? "443" : "80")) + "]";
6
+
4
7
  const $ = (id) => document.getElementById(id);
5
8
  const S = { state: null, catalog: null, catSig: "", providers: null, bindings: null, modelsSig: "", bindSig: "", tab: "tasks", detailRunId: null, pollTimer: null, showArchived: false, /* 会话内开关:每次加载默认隐藏已归档、图标不选中(不持久化,见 btn-side-arch) */ selProvs: {}, selModels: {}, selRuns: {}, bindSel: {}, catalogChecking: false, updateCheckAt: 0, control: null, sseLive: false, es: null, flows: null, orch: null, skills: null, orchSig: "", settings: null, sessionAgents: new Set(), atts: [], gitInfo: null, gitWb: null, gitWbKey: "", gitWbAt: 0, gitWbBusy: false, creatingTask: false, inspKey: null, inspData: null, inspSig: "", inspAt: 0, inspTab: "git", inspAutoSig: "", rdTab: null, rdTabSig: "", rdTabPin: false, _rdCtx: {}, lastPrefs: null };
6
9
 
@@ -155,6 +158,8 @@ function onTypeChange() {
155
158
  // 直连:验证命令与评审参数都不适用,两块一起收起(目标+附件即全部输入)
156
159
  if (codeOnly) codeOnly.classList.toggle("hidden", isReview || isDirect);
157
160
  if (reviewOnly) reviewOnly.classList.toggle("hidden", !isReview);
161
+ if ($("f-direct-model")) $("f-direct-model").classList.toggle("hidden", !isDirect);
162
+ if (isDirect) renderDirectModelPicker();
158
163
  const bibleCreate = $("bible-create-field");
159
164
  const isSerialReview = isReview && !!(flow && flow.serial);
160
165
  if (bibleCreate) bibleCreate.classList.toggle("hidden", !isSerialReview);
@@ -196,6 +201,46 @@ function onTypeChange() {
196
201
  refreshEstimate();
197
202
  }
198
203
 
204
+ function directProviders() {
205
+ return (S.providers || []).filter((p) => p.enabled !== false && p.api_key);
206
+ }
207
+
208
+ function renderDirectModelPicker() {
209
+ const ps = $("f-direct-provider"), ms = $("f-direct-model-name");
210
+ if (!ps || !ms) return;
211
+ const prev = ps.value;
212
+ const provs = directProviders();
213
+ ps.innerHTML = '<option value="">' + t("自动推荐") + '</option>' + provs.map((p) =>
214
+ '<option value="' + esc(p.id) + '">' + esc(p.name || p.id) + "</option>").join("");
215
+ if (prev && provs.some((p) => p.id === prev)) ps.value = prev;
216
+ const p = provs.find((x) => x.id === ps.value);
217
+ const oldModel = ms.value;
218
+ const names = p ? (p.models || []).filter((m) => m.enabled !== false && !m.hidden)
219
+ .map((m) => m.name).filter(Boolean) : [];
220
+ ms.innerHTML = '<option value="">' + t("随厂商推荐") + '</option>' + names.map((name) =>
221
+ '<option value="' + esc(name) + '">' + esc(name) + "</option>").join("");
222
+ if (oldModel && names.includes(oldModel)) ms.value = oldModel;
223
+ }
224
+
225
+ function recommendTaskType(goal) {
226
+ const s = String(goal || "").toLowerCase();
227
+ const rules = [
228
+ ["code", /(代码|bug|报错|修复|重构|接口|数据库|前端|后端|python|javascript|typescript|java|go\b|git)/i],
229
+ ["translation", /(翻译|译成|translate|translation|中译英|英译中)/i],
230
+ ["email", /(邮件|email|e-mail|回信|邀约信|商务函)/i],
231
+ ["weekly_report", /(周报|月报|述职|工作汇报|工作总结)/i],
232
+ ["video_script", /(短视频|口播|分镜|抖音|视频脚本)/i],
233
+ ["speech", /(演讲稿|发言稿|致辞|演讲)/i],
234
+ ["tech_proposal", /(技术方案|架构方案|选型方案|实施方案)/i],
235
+ ["research", /(调研报告|竞品调研|市场调研|深度研究)/i],
236
+ ["serial_novel", /(连载|续写.*章|网文|长篇小说)/i],
237
+ ["novel", /(小说|故事|短篇|章节)/i],
238
+ ["article", /(公众号|自媒体|文章|博客|小红书)/i],
239
+ ];
240
+ const hit = rules.find((x) => x[1].test(s));
241
+ return hit && flowById(hit[0]) ? hit[0] : "";
242
+ }
243
+
199
244
  /* 快捷类型 chips(Composer 框下方):内置流程一键选定,选中态跟 #f-type 走 */
200
245
  function renderQuickChips() {
201
246
  const box = $("cmp-quick");
@@ -919,6 +964,9 @@ async function poll() {
919
964
  S.providers = models.providers; S.bindings = models.bindings;
920
965
  S.modelCatalog = models.catalog || [];
921
966
  S.sourceNames = models.source_names || S.sourceNames || {};
967
+ if ($("f-direct-model") && !$('f-direct-model').classList.contains("hidden")) {
968
+ renderDirectModelPicker();
969
+ }
922
970
  // 供应商集合/启停/密钥变了才重拉编排者:否则「生效中」状态点会在停用厂商后失真
923
971
  const provSig = JSON.stringify((S.providers || []).map((p) => [p.id, p.enabled, !!p.api_key]));
924
972
  if (provSig !== S.provSig) { S.provSig = provSig; loadOrchestrator(); }
@@ -2145,6 +2193,7 @@ async function createTask() {
2145
2193
  goal: $("f-goal").value.trim(),
2146
2194
  context: $("f-context").value.trim(),
2147
2195
  workdir: $("f-workdir").value.trim(),
2196
+ thinking: $("f-thinking").value,
2148
2197
  };
2149
2198
  if (!payload.goal) {
2150
2199
  msg.className = "msg err";
@@ -2153,6 +2202,22 @@ async function createTask() {
2153
2202
  resetSubmit();
2154
2203
  return;
2155
2204
  }
2205
+ if (payload.type === "direct" && !S.typeSuggestionDone) {
2206
+ const suggested = recommendTaskType(payload.goal);
2207
+ if (suggested) {
2208
+ const flow = flowById(suggested) || {};
2209
+ resetSubmit();
2210
+ const yes = await uiConfirm(
2211
+ t("这个需求更适合「{0}」流程,能使用对应的规划与质量门禁。是否切换后执行?")
2212
+ .replace("{0}", t(flow.name || suggested)),
2213
+ { title: t("推荐任务类型"), ok: t("切换类型") });
2214
+ S.typeSuggestionDone = true;
2215
+ if (yes) pickType(suggested);
2216
+ $("btn-create").click();
2217
+ return;
2218
+ }
2219
+ }
2220
+ S.typeSuggestionDone = false;
2156
2221
  // 需求拷问闸(借鉴 grill-me):goal 很短且没写背景时先问 1-3 个澄清问题;
2157
2222
  // 采访失败/无问题照常创建——是增强不是闸门
2158
2223
  if (payload.goal.length < 12 && !payload.context && !S.clarifyDone) {
@@ -2198,6 +2263,8 @@ async function createTask() {
2198
2263
  payload.verify_command = $("f-verify").value.trim();
2199
2264
  } else if (_eng === "direct") {
2200
2265
  // 直连:目标+附件即全部输入,不带验证/评审参数
2266
+ payload.direct_provider_id = $("f-direct-provider").value;
2267
+ payload.direct_model = $("f-direct-model-name").value;
2201
2268
  } else {
2202
2269
  payload.manuscript = $("f-manuscript").value.trim() || "manuscript.md";
2203
2270
  payload.rounds = parseInt($("f-rounds").value, 10) || 2;
@@ -2863,11 +2930,19 @@ function newFromTask(id) {
2863
2930
  $("f-workdir").value = tk.workdir || "";
2864
2931
  S.atts = []; renderAttachChips(); // 附件清单不继承:同目录引用已随 context 保留
2865
2932
  queueGitProbe(); // 工作目录变了,重新探测代码版本
2866
- $("f-mode").value = tk.mode === "manual" ? "manual" : "auto";
2933
+ $("f-mode").value = ["fast", "expert", "manual"].includes(tk.mode) ? tk.mode : "auto";
2934
+ $("f-thinking").value = ["low", "standard", "high"].includes(tk.thinking)
2935
+ ? tk.thinking : "auto";
2936
+ $("f-manual-only").classList.toggle("hidden", $("f-mode").value !== "manual");
2867
2937
  renderImplSelects();
2868
2938
  if (tk.implementer) $("f-impl").value = tk.implementer;
2869
2939
  if (flow && flow.engine === "code") {
2870
2940
  $("f-verify").value = tk.verify_command || "";
2941
+ } else if (flow && flow.engine === "direct") {
2942
+ renderDirectModelPicker();
2943
+ $("f-direct-provider").value = tk.direct_provider_id || "";
2944
+ renderDirectModelPicker();
2945
+ $("f-direct-model-name").value = tk.direct_model || "";
2871
2946
  } else {
2872
2947
  $("f-manuscript").value = tk.manuscript || "manuscript.md";
2873
2948
  $("f-rounds").value = tk.rounds || 2;
@@ -8513,16 +8588,29 @@ function ztHarvestProfiles() {
8513
8588
  });
8514
8589
  }
8515
8590
 
8516
- function ztProfAdd() {
8591
+ async function ztProfAdd() {
8517
8592
  S.ztProfiles = ztHarvestProfiles();
8518
- S.ztProfiles.push({ product: 0, assigned_to: "", severity_cap: 0, our_sides: ["backend"],
8593
+ // 新档案必须来自禅道产品清单。先拉清单,再自动选第一个尚未配置的产品,
8594
+ // 避免生成 product=0 的空卡,用户随后保存才看到“缺产品 ID”。
8595
+ if (!(S.ztProducts || []).length) {
8596
+ if (!($("zt-base-url") && $("zt-base-url").value.trim())) {
8597
+ toast(t("请先测试并保存禅道连接,再添加产品"), true);
8598
+ return;
8599
+ }
8600
+ await ztFetchCatalog(false);
8601
+ S.ztProfiles = ztHarvestProfiles();
8602
+ }
8603
+ const used = new Set(S.ztProfiles.map((p) => String(p.product || "")));
8604
+ const next = (S.ztProducts || []).find((p) => p.id && !used.has(String(p.id)));
8605
+ if (!next) {
8606
+ toast(t((S.ztProducts || []).length ? "禅道产品均已添加" : "未拉到可用产品,请先测试连接"), true);
8607
+ return;
8608
+ }
8609
+ S.ztProfiles.push({ product: Number(next.id), assigned_to: "", severity_cap: 0, our_sides: ["backend"],
8519
8610
  repos: { backend: {}, frontend: {} }, repo_hints: { backend: "", frontend: "" },
8520
8611
  owners: { backend: "", frontend: "", not_ours: "" }, module_routes: [] });
8521
8612
  renderZentaoProfiles();
8522
- // 首次添加时自动拉清单,避免用户只能盲填产品 ID;拉不到仍保留手填回退。
8523
- if (!(S.ztProducts || []).length && $("zt-base-url") && $("zt-base-url").value.trim()) {
8524
- ztFetchCatalog(false);
8525
- }
8613
+ ztFetchMods(S.ztProfiles.length - 1, true);
8526
8614
  }
8527
8615
 
8528
8616
  function ztProfDel(i) {
@@ -8672,6 +8760,13 @@ function ztFormPayload() {
8672
8760
 
8673
8761
  async function saveZentao() {
8674
8762
  const payload = ztFormPayload();
8763
+ if ((payload.product_profiles || []).some((p) => !p.product)) {
8764
+ toast(t("请选择禅道产品后再保存"), true);
8765
+ const empty = Array.from(document.querySelectorAll("#zt-profiles .zt-p-product"))
8766
+ .find((el) => !parseInt(el.value || "", 10));
8767
+ if (empty) empty.focus();
8768
+ return;
8769
+ }
8675
8770
  try {
8676
8771
  await api("/api/zentao/config", { method: "POST", body: JSON.stringify(payload) });
8677
8772
  } catch (e) { toast(t("保存失败:") + e.message, true); return; }
@@ -10601,6 +10696,11 @@ function collapseDrawerIfMobile() {
10601
10696
  function switchTab(name) {
10602
10697
  if (name === "__phone") { openPhoneConnect(); return; } // 手机连接是弹框,不切页
10603
10698
  if (name === "__guide") { welcomeOpen(); return; } // 帮助中心是弹层,不切页(设置导航「软件」组)
10699
+ if (name === "__wxdigest") { // 群聊汇报沿用全局蜜蜂面板
10700
+ beeRefresh().finally(() => beeToggle(true));
10701
+ collapseDrawerIfMobile();
10702
+ return;
10703
+ }
10604
10704
  // 导航收进「设置」:进设置后左栏整体换成设置导航,内容铺满
10605
10705
  S.tab = name;
10606
10706
  if (SET_TABS.has(name)) localStorage.setItem("orch.setTab", name);
@@ -11087,6 +11187,7 @@ document.addEventListener("DOMContentLoaded", () => {
11087
11187
  $("f-mode").addEventListener("change", () => {
11088
11188
  $("f-manual-only").classList.toggle("hidden", $("f-mode").value !== "manual");
11089
11189
  });
11190
+ $("f-direct-provider").addEventListener("change", renderDirectModelPicker);
11090
11191
  $("f-type").dispatchEvent(new Event("change"));
11091
11192
  cmpGreeting(); // 问候语按时段刷新(切语言/回任务页也会重算)
11092
11193
  $("btn-reload-catalog").addEventListener("click", async () => {
@@ -11314,7 +11415,7 @@ function bindCmdK() {
11314
11415
 
11315
11416
  /* ------------------------------------------------ 群摘要蜜蜂坞(core/wxdigest.py)
11316
11417
  * 右下角悬浮蜜蜂:未读摘要徽章+气泡;面板看各群摘要、配置监控文件夹。
11317
- * 桌面蜜蜂 pet.py 的网页侧搭档;未启用且无数据时整坞隐藏(不占视线)。
11418
+ * 桌面蜜蜂 pet.py 的网页侧搭档;入口始终可见,未配置时也能直接打开设置。
11318
11419
  * 60s 轮询 /api/wxdigest;打开面板即 POST seen 清零未读。 */
11319
11420
  const Bee = { view: null, lastUnseen: 0, bubT: 0, selGroups: null };
11320
11421
 
@@ -11325,9 +11426,7 @@ async function beeRefresh() {
11325
11426
  let v;
11326
11427
  try { v = await api("/api/wxdigest"); } catch (e) { return; } // 静默:不打扰主流程
11327
11428
  Bee.view = v;
11328
- const on = !!(v.config && v.config.enabled) ||
11329
- (v.digests && v.digests.length) || v.unseen > 0;
11330
- beeShow($("bee-dock"), on);
11429
+ beeShow($("bee-dock"), true);
11331
11430
  beeShow($("bee-badge"), v.unseen > 0);
11332
11431
  if (v.unseen > 0) $("bee-badge").textContent = v.unseen > 99 ? "99+" : String(v.unseen);
11333
11432
  if (v.unseen > Bee.lastUnseen && !beePanelOpen()) { // 新摘要到:气泡 6s
@@ -11529,8 +11628,17 @@ async function beeScan() {
11529
11628
  });
11530
11629
  $("bee-groups-search").addEventListener("input", beeRenderGroups);
11531
11630
  $("bee-groups-refresh").addEventListener("click", beeRefreshGroups);
11631
+ // 64 位 Python 自动扫描 + 一键安装 wechatauto-replica(借鉴用户诉求:
11632
+ // 别手填解释器路径、别自己开 conda 装依赖)
11633
+ $("bee-py-scan").addEventListener("click", beePyScan);
11634
+ $("bee-py-install").addEventListener("click", beePyInstall);
11532
11635
  beeRefresh();
11533
11636
  setInterval(beeRefresh, 60000);
11637
+ // 桌宠右键「群摘要」直达:URL 带 #bee=1 时自动展开蜜蜂坞面板
11638
+ if (/bee=1/.test(location.hash)) {
11639
+ setTimeout(() => beeToggle(true), 800);
11640
+ history.replaceState(null, "", location.pathname + location.search);
11641
+ }
11534
11642
  };
11535
11643
  // 错开首屏:令牌门/状态首帧先走,群摘要轮询晚 2s 再起
11536
11644
  if (document.readyState === "loading") {
@@ -11539,3 +11647,93 @@ async function beeScan() {
11539
11647
  setTimeout(boot, 2000);
11540
11648
  }
11541
11649
  })();
11650
+
11651
+ /* ---- 64 位 Python 扫描/一键安装(蜜蜂坞微信直连配置)---- */
11652
+ let beePyTimer = null;
11653
+ async function beePyScan() {
11654
+ const btn = $("bee-py-scan"), box = $("bee-py-cands");
11655
+ if (!btn) return;
11656
+ btn.disabled = true;
11657
+ try { await api("/api/wxdigest/pythons/scan", { method: "POST", body: "{}" }); }
11658
+ catch (e) { toast(t("扫描失败:") + (e.message || e), true); btn.disabled = false; return; }
11659
+ box.classList.remove("hidden");
11660
+ box.innerHTML = '<span class="hint">' + esc(t("正在扫描本机 Python…")) + "</span>";
11661
+ beePyTimer = setInterval(async () => {
11662
+ try {
11663
+ const st = await api("/api/wxdigest/pythons");
11664
+ if (st.scanning) return;
11665
+ clearInterval(beePyTimer); beePyTimer = null;
11666
+ btn.disabled = false;
11667
+ beePyRender(st.results || []);
11668
+ } catch (e) { /* 轮询失败下一拍再试 */ }
11669
+ }, 1500);
11670
+ }
11671
+ function beePyRender(list) {
11672
+ const box = $("bee-py-cands"), st = $("bee-py-status"), ib = $("bee-py-install");
11673
+ if (!box) return;
11674
+ if (!list.length) { box.innerHTML = '<span class="hint">' + esc(t("没有找到可用的 Python,请手动填写路径")) + "</span>"; return; }
11675
+ const cur = ($("bee-reader-py").value || "").trim();
11676
+ box.innerHTML = list.map((p, idx) => {
11677
+ const tag = p.has_replica ? t("已装 wechatauto-replica") : t("未装依赖");
11678
+ const cls = "bee-py-item" + (p.path === cur ? " on" : "");
11679
+ return '<button type="button" class="' + cls + '" data-i="' + idx + '">' +
11680
+ '<span class="bee-py-path">' + esc(p.path) + "</span>" +
11681
+ '<span class="bee-py-meta">' + esc("Py" + p.version + " · " + p.bits + "bit · " + tag) + "</span></button>";
11682
+ }).join("");
11683
+ box.querySelectorAll(".bee-py-item").forEach((b) => {
11684
+ b.addEventListener("click", () => {
11685
+ const p = list[Number(b.dataset.i)];
11686
+ $("bee-reader-py").value = p.path;
11687
+ box.querySelectorAll(".bee-py-item").forEach((x) => x.classList.remove("on"));
11688
+ b.classList.add("on");
11689
+ beePyInstallVisibility(p);
11690
+ });
11691
+ });
11692
+ // 默认选中:当前输入框命中的;否则第一个已装依赖的 64 位
11693
+ const hit = list.find((p) => p.path === cur) ||
11694
+ list.find((p) => p.bits >= 64 && p.has_replica) ||
11695
+ list.find((p) => p.bits >= 64);
11696
+ if (hit) {
11697
+ $("bee-reader-py").value = hit.path;
11698
+ beePyInstallVisibility(hit);
11699
+ const btn = box.querySelector('.bee-py-item[data-i="' + list.indexOf(hit) + '"]');
11700
+ if (btn) btn.classList.add("on");
11701
+ }
11702
+ }
11703
+ function beePyInstallVisibility(p) {
11704
+ const ib = $("bee-py-install"), st = $("bee-py-status");
11705
+ if (!ib) return;
11706
+ const need = p && p.bits >= 64 && !p.has_replica;
11707
+ ib.classList.toggle("hidden", !need);
11708
+ if (st && !need) st.textContent = "";
11709
+ }
11710
+ async function beePyInstall() {
11711
+ const py = ($("bee-reader-py").value || "").trim();
11712
+ if (!py) { toast(t("请先选择或填写 Python 路径"), true); return; }
11713
+ const ib = $("bee-py-install"), st = $("bee-py-status");
11714
+ ib.disabled = true;
11715
+ if (st) st.textContent = t("正在安装 wechatauto-replica…(约 1-3 分钟)");
11716
+ try { await api("/api/wxdigest/replica/install", { method: "POST", body: JSON.stringify({ python: py }) }); }
11717
+ catch (e) {
11718
+ toast(t("安装失败:") + (e.message || e), true);
11719
+ ib.disabled = false;
11720
+ return;
11721
+ }
11722
+ const timer = setInterval(async () => {
11723
+ try {
11724
+ const s = await api("/api/wxdigest/replica/status");
11725
+ if (!s.running) {
11726
+ clearInterval(timer); timer = null;
11727
+ ib.disabled = false;
11728
+ if (s.ok) {
11729
+ if (st) st.textContent = t("安装完成,依赖已就绪");
11730
+ toast(t("wechatauto-replica 安装完成"));
11731
+ } else {
11732
+ if (st) st.textContent = (s.log || []).slice(-3).join(" ").slice(0, 200);
11733
+ toast(t("安装失败,详见状态行"), true);
11734
+ }
11735
+ beePyScan(); // 重扫刷新 has_replica 标记
11736
+ }
11737
+ } catch (e) { /* 轮询失败下一拍再试 */ }
11738
+ }, 3000);
11739
+ }
package/app/ui/i18n.js CHANGED
@@ -28,6 +28,24 @@
28
28
  // —— 表单字段/标签 ——
29
29
 
30
30
  // —— 智能/手动 模式 ——
31
+ "编排模式": "Orchestration mode",
32
+ "自动(推荐):按任务类型与风险动态决定步骤": "Auto (recommended): adapt steps to task type and risk",
33
+ "快速:少步骤优先,先做确定性验收": "Fast: fewer steps with deterministic checks first",
34
+ "专家:完整规划、多评审、失败修复与换将": "Expert: full planning, reviews, repairs, and fallback agents",
35
+ "手动:自行指定实现者与评审组": "Manual: choose implementer and reviewers",
36
+ "思考程度": "Reasoning effort",
37
+ "自动:随任务难度调整": "Auto: adapt to task difficulty",
38
+ "快速:少推理、优先响应": "Fast: less reasoning, faster response",
39
+ "标准:平衡质量与速度": "Standard: balance quality and speed",
40
+ "深度:更多推理与检查": "Deep: more reasoning and checks",
41
+ "对话模型(可选)": "Chat model (optional)",
42
+ "对话厂商": "Chat provider",
43
+ "对话模型": "Chat model",
44
+ "随厂商推荐": "Provider default",
45
+ "只对本次对话生效,不会修改全局绑定;指定渠道不可用时会明确报错。": "Applies only to this chat and does not change global bindings. An unavailable selected route returns an explicit error.",
46
+ "推荐任务类型": "Recommended task type",
47
+ "切换类型": "Switch type",
48
+ "这个需求更适合「{0}」流程,能使用对应的规划与质量门禁。是否切换后执行?": "This request fits the {0} workflow, which provides matching planning and quality gates. Switch before running?",
31
49
 
32
50
  // —— 引擎/角色 ——
33
51
 
@@ -1894,6 +1912,18 @@
1894
1912
  "反馈摘要生成失败:": "Failed to build issue summary: ",
1895
1913
  "端口占用": "Port Usage",
1896
1914
  "扫描本机端口": "Scan local ports",
1915
+ "扫描": "Scan",
1916
+ "一键安装 wechatauto-replica": "Install wechatauto-replica",
1917
+ "已装 wechatauto-replica": "replica installed",
1918
+ "未装依赖": "dependency missing",
1919
+ "正在扫描本机 Python…": "Scanning local Pythons…",
1920
+ "没有找到可用的 Python,请手动填写路径": "No usable Python found — please fill in the path manually",
1921
+ "请先选择或填写 Python 路径": "Pick or type a Python path first",
1922
+ "正在安装 wechatauto-replica…(约 1-3 分钟)": "Installing wechatauto-replica… (1-3 minutes)",
1923
+ "安装完成,依赖已就绪": "Installed — dependency ready",
1924
+ "安装失败,详见状态行": "Install failed — see status line",
1925
+ "扫描失败:": "Scan failed: ",
1926
+ "安装失败:": "Install failed: ",
1897
1927
  "谁占着端口、属于哪个项目一目了然;「仅本机」的端口外网不可达。关闭只发温和信号,系统进程与本服务自身拒绝关闭。": "See at a glance which process holds each port and which project it belongs to; \"local only\" ports are unreachable from outside. Close sends a gentle termination signal only — system processes and this service itself are refused.",
1898
1928
  "端口": "Port",
1899
1929
  "进程": "Process",
@@ -2005,6 +2035,10 @@
2005
2035
  "还没有认领过 Bug——配置好连接与产品档案后点「立即扫描」。": "No bugs claimed yet — set up connection and product profiles, then click \"Scan Now\".",
2006
2036
  "还没有产品档案——点「+ 添加产品」,填产品 ID 与两端仓库。": "No product profiles yet — click \"+ Add product\" and fill the product ID and repos.",
2007
2037
  "还没有产品档案——先测试连接,再从禅道选择产品,无需自己查 ID。": "No product profiles yet — test the connection, then select a product from ZenTao; no ID lookup needed.",
2038
+ "请先测试并保存禅道连接,再添加产品": "Test and save the ZenTao connection before adding a product.",
2039
+ "禅道产品均已添加": "All ZenTao products have already been added.",
2040
+ "未拉到可用产品,请先测试连接": "No available products were found. Test the connection first.",
2041
+ "请选择禅道产品后再保存": "Select a ZenTao product before saving.",
2008
2042
  "先填产品 ID 再拉模块清单": "Fill the product ID before fetching modules",
2009
2043
  "先选产品再拉模块清单": "Pick a product before fetching modules",
2010
2044
  "拉取失败": "Fetch failed",
@@ -2200,6 +2234,7 @@
2200
2234
 
2201
2235
  // —— 群摘要蜜蜂坞(wxdigest)——
2202
2236
  "群摘要": "Group Digests",
2237
+ "微信群聊汇报": "WeChat Group Reports",
2203
2238
  "启用定时扫描": "Enable scheduled scanning",
2204
2239
  "监控文件夹": "Watched folder",
2205
2240
  "间隔(分钟)": "Interval (min)",
package/app/ui/index.html CHANGED
@@ -178,6 +178,7 @@
178
178
  <button class="set-item active" data-sub="tasks" data-i18n="任务"><svg class="ico" aria-hidden="true"><use href="#i-tasks"></use></svg>任务</button>
179
179
  <button class="set-item" data-sub="runs" data-i18n="运行记录"><svg class="ico" aria-hidden="true"><use href="#i-history"></use></svg>运行记录</button>
180
180
  <button class="set-item" data-sub="automation" data-i18n="自动化"><svg class="ico" aria-hidden="true"><use href="#i-calendar-days"></use></svg>自动化</button>
181
+ <button class="set-item" data-sub="__wxdigest" data-i18n="微信群聊汇报"><svg class="ico" aria-hidden="true"><use href="#i-bee"></use></svg>微信群聊汇报</button>
181
182
  <button class="set-item" data-sub="zentao" data-i18n="禅道"><svg class="ico" aria-hidden="true"><use href="#i-search"></use></svg>禅道</button>
182
183
  <button class="set-item" data-sub="usage" data-i18n="用量统计"><svg class="ico" aria-hidden="true"><use href="#i-chart"></use></svg>用量统计</button>
183
184
  <div data-i18n="配置" class="set-group">配置</div>
@@ -328,10 +329,24 @@
328
329
  <div class="field">
329
330
  <label data-i18n="编排模式">编排模式</label>
330
331
  <select id="f-mode">
331
- <option data-i18n="智能(推荐):自动拆解 + 智能路由 + 失败自动修复/换将" value="auto">智能(推荐):自动拆解 + 智能路由 + 失败自动修复/换将</option>
332
+ <option data-i18n="自动(推荐):按任务类型与风险动态决定步骤" value="auto">自动(推荐):按任务类型与风险动态决定步骤</option>
333
+ <option data-i18n="快速:少步骤优先,先做确定性验收" value="fast">快速:少步骤优先,先做确定性验收</option>
334
+ <option data-i18n="专家:完整规划、多评审、失败修复与换将" value="expert">专家:完整规划、多评审、失败修复与换将</option>
332
335
  <option data-i18n="手动:自行指定实现者与评审组" value="manual">手动:自行指定实现者与评审组</option>
333
336
  </select>
334
337
  </div>
338
+ <div class="grid-2">
339
+ <div class="field"><label data-i18n="思考程度">思考程度</label><select id="f-thinking">
340
+ <option data-i18n="标准:平衡质量与速度" value="standard" selected>标准:平衡质量与速度</option>
341
+ <option data-i18n="自动:随任务难度调整" value="auto">自动:随任务难度调整</option>
342
+ <option data-i18n="快速:少推理、优先响应" value="low">快速:少推理、优先响应</option>
343
+ <option data-i18n="深度:更多推理与检查" value="high">深度:更多推理与检查</option>
344
+ </select></div>
345
+ <div id="f-direct-model" class="field hidden"><label data-i18n="对话模型(可选)">对话模型(可选)</label><div class="grid-2">
346
+ <select id="f-direct-provider" aria-label="对话厂商" data-i18n-aria="对话厂商"><option data-i18n="自动推荐">自动推荐</option></select>
347
+ <select id="f-direct-model-name" aria-label="对话模型" data-i18n-aria="对话模型"><option data-i18n="随厂商推荐">随厂商推荐</option></select>
348
+ </div><p class="hint" data-i18n="只对本次对话生效,不会修改全局绑定;指定渠道不可用时会明确报错。">只对本次对话生效,不会修改全局绑定;指定渠道不可用时会明确报错。</p></div>
349
+ </div>
335
350
 
336
351
  <div id="f-code-only">
337
352
  <div class="field"><label data-i18n="验证命令">验证命令</label><input id="f-verify" placeholder="例:python -m pytest -q(可空=只评审)" data-i18n-ph="例:python -m pytest -q(可空=只评审)"></div>
@@ -557,12 +572,12 @@
557
572
  <label data-i18n="密码">密码</label><input id="zt-password" type="password" placeholder="" autocomplete="new-password">
558
573
  </div>
559
574
  <h3 data-i18n="自动化" class="sec-title">自动化</h3>
560
- <div class="zt-grid zt-grid-toggles">
575
+ <div class="zt-grid zt-grid-toggles zt-auto-options">
561
576
  <label class="toggle" title="模块路由没命中时,用 AI 读 Bug 描述判端(前端/后端/双端/非我方);关掉则未命中模块的 Bug 留人工" data-i18n-title="模块路由没命中时,用 AI 读 Bug 描述判端(前端/后端/双端/非我方);关掉则未命中模块的 Bug 留人工"><input id="zt-triageai" type="checkbox"><span data-i18n="AI 排查兜底">AI 排查兜底</span></label>
562
577
  <label class="toggle" title="修复任务评审通过后,自动到禅道 resolve(fixed)并回评论" data-i18n-title="修复任务评审通过后,自动到禅道 resolve(fixed)并回评论"><input id="zt-autoresolve" type="checkbox"><span data-i18n="自动 resolve Bug">自动 resolve Bug</span></label>
563
578
  <label class="toggle" title="把任务分支自动合并回基线分支(需配置基线分支;合并失败不 resolve 不转派,留人工)" data-i18n-title="把任务分支自动合并回基线分支(需配置基线分支;合并失败不 resolve 不转派,留人工)"><input id="zt-automerge" type="checkbox"><span data-i18n="自动合并代码">自动合并代码</span></label>
564
579
  <label class="toggle" title="按下面的间隔定时扫描;关掉后仍可手动「立即扫描」" data-i18n-title="按下面的间隔定时扫描;关掉后仍可手动「立即扫描」"><input id="zt-poll" type="checkbox"><span data-i18n="定时扫描">定时扫描</span></label>
565
- <label data-i18n="扫描间隔(小时)">扫描间隔(小时)</label><input id="zt-interval" type="number" min="1" max="168" step="1">
580
+ <label class="zt-interval-field"><span data-i18n="扫描间隔(小时)">扫描间隔(小时)</span><input id="zt-interval" type="number" min="1" max="168" step="1"></label>
566
581
  </div>
567
582
  <div class="ops"><button id="btn-zentao-save" class="primary small"><span data-i18n="保存配置">保存配置</span></button></div>
568
583
  <h3 data-i18n="产品档案" class="sec-title">产品档案</h3>
@@ -1107,10 +1122,10 @@
1107
1122
 
1108
1123
  <!-- 群摘要蜜蜂坞(右下角悬浮蜜蜂):桌面蜜蜂 pet.py 的网页侧搭档。
1109
1124
  未读摘要时徽章+气泡提醒,点开面板看各群摘要/配置监控文件夹;
1110
- 未启用且无数据时整坞隐藏(不占视线)。后端 core/wxdigest.py。 -->
1111
- <div id="bee-dock" class="hidden">
1125
+ 未配置时也保持入口可见,避免功能存在却无处开启。后端 core/wxdigest.py。 -->
1126
+ <div id="bee-dock">
1112
1127
  <div id="bee-bubble" class="hidden"></div>
1113
- <button id="bee-fab" aria-label="群摘要" data-i18n-aria="群摘要" data-i18n-title="群摘要" title="群摘要">
1128
+ <button id="bee-fab" aria-label="微信群聊汇报" data-i18n-aria="微信群聊汇报" data-i18n-title="微信群聊汇报" title="微信群聊汇报">
1114
1129
  <svg class="ico ico-lg" aria-hidden="true"><use href="#i-bee"></use></svg>
1115
1130
  <span id="bee-badge" class="hidden"></span>
1116
1131
  </button>
@@ -1130,7 +1145,13 @@
1130
1145
  <label class="toggle"><input id="bee-reader" type="checkbox"> <span data-i18n="从本机微信自动拉取(微信直连)">从本机微信自动拉取(微信直连)</span></label>
1131
1146
  <div id="bee-reader-box" class="hidden">
1132
1147
  <span class="bee-lab" data-i18n="64 位 Python 路径(需装 wechatauto-replica)">64 位 Python 路径(需装 wechatauto-replica)</span>
1133
- <input id="bee-reader-py" type="text" spellcheck="false" data-i18n-ph="例如 C:\Users\HP\anaconda3\envs\wxprobe\python.exe" placeholder="例如 C:\Users\HP\anaconda3\envs\wxprobe\python.exe">
1148
+ <div class="bee-dir-row">
1149
+ <input id="bee-reader-py" type="text" spellcheck="false" style="flex:1" data-i18n-ph="例如 C:\Users\HP\anaconda3\envs\wxprobe\python.exe" placeholder="例如 C:\Users\HP\anaconda3\envs\wxprobe\python.exe">
1150
+ <button id="bee-py-scan" class="ghost small" data-i18n="扫描">扫描</button>
1151
+ </div>
1152
+ <div id="bee-py-cands" class="bee-py-cands hidden"></div>
1153
+ <button id="bee-py-install" class="ghost small hidden" data-i18n="一键安装 wechatauto-replica">一键安装 wechatauto-replica</button>
1154
+ <span id="bee-py-status" class="hint"></span>
1134
1155
  <div class="bee-dir-row">
1135
1156
  <button id="bee-groups-btn" class="ghost small" data-i18n="选择要监听的群(0)">选择要监听的群(0)</button>
1136
1157
  </div>
package/app/ui/style.css CHANGED
@@ -5521,3 +5521,65 @@ button.request-busy > * { opacity: .18; }
5521
5521
  line-height: 1.6;
5522
5522
  }
5523
5523
  #su-info.empty { text-align: center; }
5524
+
5525
+ /* ---------------- 禅道·添加产品与自动化布局(追加于文件尾) ---------------- */
5526
+ .zt-prof .head .name {
5527
+ display: flex; flex: 1 1 280px; min-width: 0; align-items: center; gap: 8px;
5528
+ overflow: visible; white-space: normal;
5529
+ }
5530
+ .zt-prof .head .zt-p-product {
5531
+ width: min(360px, 100%); min-width: 180px; flex: 1 1 240px;
5532
+ }
5533
+ .zt-auto-options {
5534
+ grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
5535
+ gap: 12px 18px; align-items: start;
5536
+ }
5537
+ @media (min-width: 901px) {
5538
+ .zt-auto-options { grid-template-columns: repeat(5, minmax(0, 1fr)); }
5539
+ }
5540
+ .zt-auto-options > .toggle {
5541
+ min-height: 38px; margin: 0; align-self: end;
5542
+ }
5543
+ .zt-auto-options .zt-interval-field {
5544
+ display: grid; grid-template-columns: 1fr; gap: 6px; min-width: 0;
5545
+ color: var(--muted); font-size: 12.5px;
5546
+ }
5547
+ .zt-auto-options .zt-interval-field input {
5548
+ width: 100%; max-width: none; box-sizing: border-box;
5549
+ }
5550
+ @media (max-width: 640px) {
5551
+ .zt-auto-options { grid-template-columns: 1fr 1fr; }
5552
+ .zt-auto-options .zt-interval-field { grid-column: 1 / -1; }
5553
+ .zt-prof .head .zt-p-product { min-width: 0; }
5554
+ }
5555
+
5556
+ /* 禅道自动化开关行:紧凑排成一行(四开关 + 扫描间隔同行,窄屏自动换行;
5557
+ 间隔输入改行内「标签+输入」,不再独占一格上下分层——用户反馈排成一行) */
5558
+ .zt-auto-options {
5559
+ display: flex; flex-wrap: wrap; align-items: center;
5560
+ gap: 8px 22px; grid-template-columns: none;
5561
+ }
5562
+ .zt-auto-options > .toggle { min-height: 0; margin: 0; align-self: auto; }
5563
+ .zt-auto-options .zt-interval-field {
5564
+ display: inline-flex; align-items: center; gap: 8px;
5565
+ grid-template-columns: none; min-width: 0;
5566
+ margin-left: auto; color: var(--muted); font-size: 12.5px;
5567
+ }
5568
+ .zt-auto-options .zt-interval-field input { width: 72px; }
5569
+ @media (max-width: 900px) {
5570
+ .zt-auto-options .zt-interval-field { margin-left: 0; }
5571
+ }
5572
+
5573
+ /* 蜜蜂坞微信直连:Python 扫描候选列表(样式对齐 bee-cfg 卡片内风格) */
5574
+ .bee-py-cands {
5575
+ display: flex; flex-direction: column; gap: 4px;
5576
+ max-height: 168px; overflow: auto; margin: 6px 0;
5577
+ }
5578
+ .bee-py-item {
5579
+ display: flex; flex-direction: column; gap: 2px; text-align: left;
5580
+ padding: 6px 8px; border: 1px solid var(--border, rgba(128,128,128,.25));
5581
+ border-radius: 6px; background: transparent; cursor: pointer; color: inherit;
5582
+ }
5583
+ .bee-py-item.on { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 10%, transparent); }
5584
+ .bee-py-path { font-size: 12px; word-break: break-all; }
5585
+ .bee-py-meta { font-size: 11px; opacity: .65; }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codebee",
3
- "version": "0.1.28",
3
+ "version": "0.1.30",
4
4
  "description": "CodeBee · 多智能体编排台 — 统一编排本机 AI 编码 CLI(Codex / Claude Code / QwenCode / OpenCode / Aider…):目标→拆解→路由→执行→验证→跨厂商评审→修复→报告。纯 Python 标准库实现,本地 Web UI。",
5
5
  "license": "MIT",
6
6
  "bin": {