codebee 0.1.14 → 0.1.15

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/main.py CHANGED
@@ -304,6 +304,17 @@ class Handler(BaseHTTPRequestHandler):
304
304
  return self._send(200, "(报告尚未生成)", "text/markdown; charset=utf-8")
305
305
  return self._send(200, p.read_text(encoding="utf-8", errors="replace"),
306
306
  "text/markdown; charset=utf-8")
307
+ m = re.match(r"^/api/runs/([^/]+)/share$", path)
308
+ if m:
309
+ from core import share_page
310
+ run = store.get_run(m.group(1))
311
+ if not run:
312
+ return self._json(404, {"error": "not found"})
313
+ task = store.get_task(run.get("task_id") or "") if run.get("task_id") else None
314
+ rp = paths.RUNS_DIR / m.group(1) / "report.md"
315
+ report = rp.read_text(encoding="utf-8", errors="replace") if rp.is_file() else ""
316
+ html = share_page.render_share_html(run, task, report)
317
+ return self._send(200, html, "text/html; charset=utf-8")
307
318
  m = re.match(r"^/api/runs/([^/]+)/files$", path)
308
319
  if m:
309
320
  run = store.get_run(m.group(1))
@@ -437,6 +448,9 @@ class Handler(BaseHTTPRequestHandler):
437
448
  if path == "/api/automation":
438
449
  return self._json(200, {"tasks": automation.list_tasks(),
439
450
  "templates": automation.templates()})
451
+ if path == "/api/zentao":
452
+ from core import zentao
453
+ return self._json(200, zentao.view())
440
454
  m = re.match(r"^/api/automation/([^/]+)$", path)
441
455
  if m:
442
456
  t = automation.get_task(m.group(1))
@@ -955,6 +969,25 @@ class Handler(BaseHTTPRequestHandler):
955
969
  return self._json(200, {"ok": True, "task": t, "run_id": run_id or ""})
956
970
  ok = automation.delete(tid)
957
971
  return self._json(200, {"ok": True}) if ok else self._json(404, {"error": "not found"})
972
+ if path == "/api/zentao/config":
973
+ from core import zentao
974
+ try:
975
+ cfg = zentao.save_config(self._body())
976
+ except ValueError as e:
977
+ return self._json(400, {"error": str(e)})
978
+ return self._json(200, {"ok": True, "config": cfg})
979
+ if path == "/api/zentao/test":
980
+ from core import zentao
981
+ body = self._body()
982
+ pw = str(body.get("password") or "").strip()
983
+ ok, msg = zentao.test_connection(
984
+ base_url=body.get("base_url"), account=body.get("account"),
985
+ password=pw or None) # 空 = 用已保存密码
986
+ return self._json(200, {"ok": ok, "message": msg})
987
+ if path == "/api/zentao/scan":
988
+ from core import zentao
989
+ res = zentao.scan_now() # 网络操作同步做(ThreadingHTTPServer 不堵别的请求)
990
+ return self._json(200, dict(res, ok=bool(res.get("ok"))))
958
991
  # 外部目录路由必须在通配的 market/<id>/(install|remove) 之前——
959
992
  # 否则 "remote" 会被当成包名吞掉
960
993
  if path == "/api/market/remote/refresh":
@@ -1949,6 +1982,13 @@ def main():
1949
1982
  def _step(msg):
1950
1983
  print("[CodeBee] %s (%.1fs)" % (msg, time.time() - _t0), flush=True)
1951
1984
 
1985
+ _ver = ""
1986
+ try:
1987
+ _pj = Path(__file__).resolve().parents[1] / "package.json"
1988
+ _ver = str(json.loads(_pj.read_text(encoding="utf-8")).get("version") or "")
1989
+ except Exception:
1990
+ pass
1991
+ print("[CodeBee] v%s" % (_ver or "?"), flush=True)
1952
1992
  _step("正在准备数据目录…")
1953
1993
  paths.ensure_dirs()
1954
1994
  from core import attachments
@@ -2040,6 +2080,13 @@ def main():
2040
2080
  n_auto = automation.start() # 自动化:加载定时任务并拉起调度线程(错过的一次性任务不补跑)
2041
2081
  if n_auto:
2042
2082
  print("[CodeBee] 自动化:%d 个定时任务已加载" % n_auto)
2083
+ try:
2084
+ from core import zentao
2085
+ n_zt = zentao.start() # 禅道集成:加载扫描配置与认领台账(调度挂在 automation tick)
2086
+ if n_zt:
2087
+ print("[CodeBee] 禅道集成:%d 条认领记录已加载" % n_zt)
2088
+ except Exception:
2089
+ pass
2043
2090
  tok = remote.token()
2044
2091
  import atexit
2045
2092
  import os as _os
@@ -2056,7 +2103,8 @@ def main():
2056
2103
  except OSError as e:
2057
2104
  # Windows 的 SO_REUSEADDR 允许两个进程同时 LISTEN 同一端口(请求随机
2058
2105
  # 分发,表现为"时好时坏");加独占锁后双起在这里干净失败并指路。
2059
- import os
2106
+ # (此处不能局部 import os:会让 os 变 main() 的局部名,后面 2127 行
2107
+ # 的 os.name 直接 UnboundLocalError——顶部已有全局导入,直接用)
2060
2108
  hint = ""
2061
2109
  if os.name == "nt":
2062
2110
  hint = ("(Windows 排查:netstat -ano | findstr :%d 找到 PID,"
@@ -2084,14 +2132,17 @@ def main():
2084
2132
  % (remote.PUBLIC_URL, tok))
2085
2133
  elif not args.no_public_tunnel and args.host != "127.0.0.1":
2086
2134
  if remote.has_local_creds():
2135
+ extra = ",或 start-public.bat" if os.name == "nt" else ""
2087
2136
  print("[CodeBee] (检测到已有 Cloudflare 隧道凭据:临时隧道在此类机器上不可用,"
2088
- "请用 --public-url 配固定域名,或 start-public.bat)")
2137
+ "请用 --public-url 配固定域名%s)" % extra)
2089
2138
  elif remote.start_quick_tunnel(args.port, _announce_public):
2090
2139
  print("[CodeBee] 正在建立 Cloudflare 快速隧道(公网地址几秒后打印;"
2091
2140
  "若打不开说明当前网络不支持,可改用固定域名或 Tailscale)…")
2092
2141
  else:
2142
+ cf = ("winget install Cloudflare.cloudflared" if os.name == "nt"
2143
+ else "brew install cloudflared")
2093
2144
  print("[CodeBee] (未检测到 cloudflared,跳过公网隧道;"
2094
- "winget install Cloudflare.cloudflared 后重启即可获得公网地址)")
2145
+ "%s 后重启即可获得公网地址)" % cf)
2095
2146
  atexit.register(remote.stop_quick_tunnel)
2096
2147
  if args.host != "127.0.0.1":
2097
2148
  lan = remote.lan_ip()
@@ -2105,9 +2156,21 @@ def main():
2105
2156
  print("[CodeBee] 远程访问受令牌保护;手机打开一次带 token 的地址后会记住。")
2106
2157
  if not args.no_browser:
2107
2158
  threading.Timer(1.0, lambda: webbrowser.open("http://127.0.0.1:%d" % args.port)).start()
2159
+ if os.name != "nt":
2160
+ # POSIX 的 SIGTERM(kill <pid>)/SIGHUP(关终端窗口)默认动作是立即
2161
+ # 终止进程:atexit 不跑,快速隧道的 cloudflared 子进程会变孤儿。转成
2162
+ # SystemExit 走正常退出路径;Ctrl+C 的 KeyboardInterrupt 本就触发
2163
+ # atexit,无需登记。Windows 语义不同,不碰。
2164
+ import signal as _signal
2165
+
2166
+ def _graceful_exit(signum, frame):
2167
+ raise SystemExit(0)
2168
+
2169
+ _signal.signal(_signal.SIGTERM, _graceful_exit)
2170
+ _signal.signal(_signal.SIGHUP, _graceful_exit)
2108
2171
  try:
2109
2172
  httpd.serve_forever()
2110
- except KeyboardInterrupt:
2173
+ except (KeyboardInterrupt, SystemExit):
2111
2174
  print("\n[CodeBee] 已退出")
2112
2175
 
2113
2176
 
package/app/ui/app.js CHANGED
@@ -3574,6 +3574,7 @@ async function renderRunDetail() {
3574
3574
  $("btn-cancel").classList.toggle("hidden", !active);
3575
3575
  S.cancelTargetRunId = active ? run.id : null;
3576
3576
  $("btn-delete").classList.toggle("hidden", active);
3577
+ $("btn-share").classList.toggle("hidden", active); // 分享页:结束后可生成自包含 HTML
3577
3578
  $("btn-retry").classList.toggle("hidden", !(run.task_id && (run.status === "failed" || run.status === "cancelled")));
3578
3579
  $("btn-talk").classList.toggle("hidden", !(run.task_id && !chatEngineIsDirect(run)));
3579
3580
  const rcTask = ((S.state || {}).tasks || []).find((x) => x.id === run.task_id);
@@ -7681,6 +7682,136 @@ function autoTemplate(tplId) {
7681
7682
  if (tp) autoForm(null, tp);
7682
7683
  }
7683
7684
 
7685
+ /* ---------------------------------------------------------- 禅道 Bug 自动修复 */
7686
+ /* /api/zentao → {config(脱敏), claims, last_scan, next_scan, last_error}。
7687
+ * 进页拉一次回填表单;保存/测试/扫描各自 POST 后重拉。 */
7688
+
7689
+ async function loadZentao() {
7690
+ let v = null;
7691
+ try { v = await api("/api/zentao"); } catch (e) { v = null; }
7692
+ S.zentao = v;
7693
+ const cfg = (v && v.config) || {};
7694
+ const set = (id, val) => { const el = $(id); if (el) el.value = val == null ? "" : val; };
7695
+ set("zt-base-url", cfg.base_url);
7696
+ set("zt-account", cfg.account);
7697
+ const pw = $("zt-password");
7698
+ if (pw) { pw.value = ""; pw.placeholder = cfg.has_password ? t("已保存(不改就留空)") : ""; }
7699
+ set("zt-products", (cfg.products || []).join(","));
7700
+ set("zt-assigned", cfg.assigned_to);
7701
+ const sev = $("zt-sev"); if (sev) sev.value = String(cfg.severity_cap || 0);
7702
+ set("zt-workdir", cfg.workdir);
7703
+ set("zt-gitrev", cfg.git_rev);
7704
+ set("zt-verify", cfg.verify_command);
7705
+ const tg = (id, on) => { const el = $(id); if (el) el.checked = !!on; };
7706
+ tg("zt-autoresolve", cfg.auto_resolve);
7707
+ tg("zt-automerge", cfg.auto_merge);
7708
+ tg("zt-poll", cfg.poll_enabled);
7709
+ set("zt-interval", cfg.interval_hours || 2);
7710
+ renderZentaoStatus();
7711
+ renderZentaoClaims();
7712
+ }
7713
+
7714
+ function zentaoStateTag(st) {
7715
+ const MAP = {
7716
+ fixing: ["修复中", "zt-tag-fix"],
7717
+ resolved: ["已解决", "zt-tag-ok"],
7718
+ merge_failed: ["合并失败", "zt-tag-warn"],
7719
+ commented: ["已评论留人工", "zt-tag-warn"],
7720
+ resolve_failed: ["回写失败", "zt-tag-warn"],
7721
+ done_manual: ["待人工确认", "zt-tag-warn"],
7722
+ lost: ["记录丢失", "zt-tag-err"],
7723
+ };
7724
+ const m = MAP[st] || [st || "?", ""];
7725
+ return '<span class="tag ' + m[1] + '">' + t(m[0]) + "</span>";
7726
+ }
7727
+
7728
+ function renderZentaoStatus() {
7729
+ const box = $("zentao-status");
7730
+ if (!box) return;
7731
+ const v = S.zentao;
7732
+ if (!v) { box.innerHTML = t("加载失败:服务未连接"); return; }
7733
+ const cfg = v.config || {};
7734
+ const parts = [];
7735
+ parts.push(cfg.poll_enabled
7736
+ ? (t("定时扫描已开启,每 ") + (cfg.interval_hours || 2) + t(" 小时一次")
7737
+ + (v.next_scan ? t(",下次 ") + esc(v.next_scan) : ""))
7738
+ : t("定时扫描未开启(仍可手动「立即扫描」)"));
7739
+ if (v.last_scan) parts.push(t("上次扫描 ") + esc(v.last_scan));
7740
+ box.innerHTML = parts.map(esc).join(" · ") +
7741
+ (v.last_error ? '<span class="zt-err">' + t("最近错误:") + esc(v.last_error) + "</span>" : "");
7742
+ }
7743
+
7744
+ function renderZentaoClaims() {
7745
+ const box = $("zentao-claims");
7746
+ if (!box) return;
7747
+ const claims = (S.zentao && S.zentao.claims) || [];
7748
+ box.innerHTML = claims.map((c) =>
7749
+ '<div class="card"><div class="head"><span class="name">#' + esc(c.bug_id) + " " + esc(c.title || "") + "</span>" +
7750
+ zentaoStateTag(c.state) + "</div>" +
7751
+ '<div class="auto-meta">' +
7752
+ "<span>" + t("修复任务:") + '<a href="#" onclick="zentaoOpenTask(\'' + esc(c.task_id || "") + '\');return false;">' + esc(c.task_id || "?") + "</a></span>" +
7753
+ (c.claimed_at ? "<span>" + t("认领于 ") + esc(c.claimed_at) + "</span>" : "") +
7754
+ "</div>" +
7755
+ (c.note ? '<div class="note">' + esc(c.note) + "</div>" : "") +
7756
+ "</div>"
7757
+ ).join("") || '<div class="empty">' + t("还没有认领过 Bug——配置好连接与产品 ID 后点「立即扫描」。") + "</div>";
7758
+ }
7759
+
7760
+ function zentaoOpenTask(taskId) {
7761
+ const c = ((S.zentao && S.zentao.claims) || []).find((x) => x.task_id === taskId);
7762
+ if (c && c.run_id) { openRun(c.run_id); return; }
7763
+ switchTab("tasks");
7764
+ }
7765
+
7766
+ /* 表单 → 保存配置。password 只在用户输入了才提交(空 = 保持已存密码)。 */
7767
+ async function saveZentao() {
7768
+ const val = (id) => (($(id).value || "").trim());
7769
+ const products = val("zt-products").split(/[,,\s]+/).filter(Boolean).map(Number);
7770
+ const payload = {
7771
+ base_url: val("zt-base-url"),
7772
+ account: val("zt-account"),
7773
+ products,
7774
+ assigned_to: val("zt-assigned"),
7775
+ severity_cap: parseInt($("zt-sev").value, 10) || 0,
7776
+ workdir: val("zt-workdir"),
7777
+ git_rev: val("zt-gitrev"),
7778
+ verify_command: val("zt-verify"),
7779
+ auto_resolve: $("zt-autoresolve").checked,
7780
+ auto_merge: $("zt-automerge").checked,
7781
+ poll_enabled: $("zt-poll").checked,
7782
+ interval_hours: parseInt($("zt-interval").value, 10) || 2,
7783
+ };
7784
+ const pw = ($("zt-password").value || "").trim();
7785
+ if (pw) payload.password = pw;
7786
+ try {
7787
+ await api("/api/zentao/config", { method: "POST", body: JSON.stringify(payload) });
7788
+ } catch (e) { toast(t("保存失败:") + e.message, true); return; }
7789
+ toast(t("禅道配置已保存"));
7790
+ loadZentao();
7791
+ }
7792
+
7793
+ async function testZentao() {
7794
+ const val = (id) => (($(id).value || "").trim());
7795
+ const body = { base_url: val("zt-base-url"), account: val("zt-account") };
7796
+ const pw = ($("zt-password").value || "").trim();
7797
+ if (pw) body.password = pw;
7798
+ let r;
7799
+ try {
7800
+ r = await api("/api/zentao/test", { method: "POST", body: JSON.stringify(body) });
7801
+ } catch (e) { toast(e.message, true); return; }
7802
+ toast(r.message || (r.ok ? t("连接成功") : t("连接失败")), !r.ok);
7803
+ }
7804
+
7805
+ async function scanZentao() {
7806
+ let r;
7807
+ try {
7808
+ r = await api("/api/zentao/scan", { method: "POST", body: "{}" });
7809
+ } catch (e) { toast(e.message, true); return; }
7810
+ if (r.error) toast(r.error, true);
7811
+ else toast(t("扫描完成,新认领 ") + (r.claimed || 0) + t(" 个 Bug"));
7812
+ loadZentao();
7813
+ }
7814
+
7684
7815
  /* ---------------------------------------------------------- 插件市场 */
7685
7816
  /* /api/market → {catalog, categories(闭集)};搜索/分类/状态全在本地过滤。
7686
7817
  * 外部目录(/api/market/remote)只读缓存不联网;联网拉取仅在用户点「拉取更新」。 */
@@ -8423,7 +8554,7 @@ async function suStartupCheck() {
8423
8554
  }
8424
8555
 
8425
8556
  /* ---------------------------------------------------------- 页签 & 初始化 */
8426
- const TAB_TITLES = { tasks: "任务", runs: "运行记录", automation: "自动化", usage: "用量统计", agents: "智能体管理", models: "模型接入", bindings: "CLI 绑定", skills: "经验库", market: "插件市场", orch: "编排设置", appearance: "皮肤", about: "关于与更新" };
8557
+ const TAB_TITLES = { tasks: "任务", runs: "运行记录", automation: "自动化", zentao: "禅道 Bug 自动修复", usage: "用量统计", agents: "智能体管理", models: "模型接入", bindings: "CLI 绑定", skills: "经验库", market: "插件市场", orch: "编排设置", appearance: "皮肤", about: "关于与更新" };
8427
8558
  const SET_TABS = new Set(Object.keys(TAB_TITLES)); // 设置导航里的子页(__phone 是弹框,不算)
8428
8559
 
8429
8560
  function tabTitle(name) {
@@ -9082,6 +9213,7 @@ function switchTab(name) {
9082
9213
  if (name === "skills") loadSkills(); // 进经验库页拉取沉淀
9083
9214
  if (name === "automation") { loadAutomation(); startAutoPoll(); } // 进自动化页:拉取 + 页面可见时每 8s 轮询
9084
9215
  else stopAutoPoll(); // 离开自动化页(或切到别的子页)即停表
9216
+ if (name === "zentao") loadZentao(); // 进禅道页:拉配置与修复记录回填表单
9085
9217
  if (name === "market") loadMarket(); // 进插件市场页拉取目录
9086
9218
  if (name === "usage") { syncUsageRange(); loadUsage(); } // 进用量页:对齐范围选中态并拉取
9087
9219
  if (name === "appearance") renderAppearance(); // 进皮肤页:按当前皮肤/明暗重画卡片
@@ -9394,6 +9526,11 @@ document.addEventListener("DOMContentLoaded", () => {
9394
9526
  if (g) g.focus(); // 焦点直接落到目标框,改完即可提交
9395
9527
  });
9396
9528
  $("btn-delete").addEventListener("click", () => { if (S.detailRunId) deleteRun(S.detailRunId); });
9529
+ // 分享页:新窗口打开自包含 HTML(零依赖离线可看,可直接另存/转发)
9530
+ $("btn-share").addEventListener("click", () => {
9531
+ if (S.detailRunId) window.open("/api/runs/" + encodeURIComponent(S.detailRunId) + "/share",
9532
+ "_blank", "noopener");
9533
+ });
9397
9534
  $("btn-theme").addEventListener("click", toggleTheme);
9398
9535
  $("btn-notify-toggle").addEventListener("click", toggleNotifySound);
9399
9536
  // 命令面板:侧栏搜索行 / Ctrl+K 唤起(函数若尚未落地,跳过而不炸整个初始化)
@@ -9574,6 +9711,10 @@ document.addEventListener("DOMContentLoaded", () => {
9574
9711
  // 自动化页:刷新 / 新建 / 三段筛选(本地过滤)
9575
9712
  $("btn-auto-refresh").addEventListener("click", loadAutomation);
9576
9713
  $("btn-auto-new").addEventListener("click", () => autoForm());
9714
+ // 禅道页:测试连接 / 立即扫描 / 保存配置
9715
+ $("btn-zentao-test").addEventListener("click", testZentao);
9716
+ $("btn-zentao-scan").addEventListener("click", scanZentao);
9717
+ $("btn-zentao-save").addEventListener("click", saveZentao);
9577
9718
  $("auto-filter").addEventListener("click", (e) => {
9578
9719
  const b = e.target.closest("[data-f]");
9579
9720
  if (!b) return;
package/app/ui/i18n.js CHANGED
@@ -1572,6 +1572,8 @@
1572
1572
  "重新生成": "Regenerate",
1573
1573
  "重新生成会覆盖现有内容": "Regenerating overwrites the existing content",
1574
1574
  "生成失败": "Generation failed",
1575
+ "分享页": "Share page",
1576
+ "生成自包含分享页(单文件 HTML),可直接发给他人查看任务成果": "Generate a self-contained share page (single-file HTML) to send to others",
1575
1577
  "封面图": "Cover image",
1576
1578
  "生成封面": "Generate cover",
1577
1579
  "重试": "Retry",
@@ -1773,6 +1775,57 @@
1773
1775
  "一键反馈 Issue": "Report Issue",
1774
1776
  "已打开 GitHub 反馈页,内容已预填,可直接提交(也可附上诊断包 zip)": "GitHub issue page opened with the report pre-filled — submit it, and feel free to attach the diagnostics zip",
1775
1777
  "反馈摘要生成失败:": "Failed to build issue summary: ",
1778
+
1779
+ // —— 禅道 Bug 自动修复 ——
1780
+ "禅道": "ZenTao",
1781
+ "禅道 Bug 自动修复": "ZenTao Bug Auto-Fix",
1782
+ "禅道连接": "ZenTao Connection",
1783
+ "修复任务": "Fix Tasks",
1784
+ "修复记录": "Fix History",
1785
+ "测试连接": "Test Connection",
1786
+ "立即扫描": "Scan Now",
1787
+ "保存配置": "Save Settings",
1788
+ "用当前表单里的连接信息试连禅道": "Test ZenTao with the connection info in this form",
1789
+ "立刻扫一遍激活 Bug 并对账回写": "Scan active bugs now and write results back",
1790
+ "定时扫描禅道的激活 Bug,自动创建代码修复任务(实现→验证→评审→修复);跑完自动合并代码、resolve Bug 并把报告评论回禅道,群机器人同步通知。": "Periodically scans active ZenTao bugs and creates code fix tasks (implement → verify → review → fix); on success it merges the code, resolves the bug, posts a report comment back to ZenTao, and notifies the group bot.",
1791
+ "禅道地址": "ZenTao URL",
1792
+ "账号": "Account",
1793
+ "密码": "Password",
1794
+ "产品 ID": "Product IDs",
1795
+ "只认领指派给": "Only claim bugs assigned to",
1796
+ "严重度上限": "Severity cap",
1797
+ "不限": "Any",
1798
+ "工作目录": "Working directory",
1799
+ "基线分支": "Base branch",
1800
+ "验证命令": "Verify command",
1801
+ "自动 resolve Bug": "Auto-resolve bugs",
1802
+ "自动合并代码": "Auto-merge code",
1803
+ "定时扫描": "Scheduled scan",
1804
+ "扫描间隔(小时)": "Scan interval (hours)",
1805
+ "已保存(不改就留空)": "Saved (leave blank to keep)",
1806
+ "定时扫描已开启,每 ": "Scheduled scan on, every ",
1807
+ " 小时一次": " h",
1808
+ ",下次 ": ", next ",
1809
+ "定时扫描未开启(仍可手动「立即扫描」)": "Scheduled scan off (manual \"Scan Now\" still works)",
1810
+ "上次扫描 ": "Last scan ",
1811
+ "最近错误:": "Last error: ",
1812
+ "加载失败:服务未连接": "Load failed: service not connected",
1813
+ "修复中": "Fixing",
1814
+ "已解决": "Resolved",
1815
+ "合并失败": "Merge failed",
1816
+ "已评论留人工": "Commented, left to human",
1817
+ "回写失败": "Write-back failed",
1818
+ "待人工确认": "Awaiting human confirm",
1819
+ "记录丢失": "Record lost",
1820
+ "修复任务:": "Fix task: ",
1821
+ "认领于 ": "Claimed at ",
1822
+ "还没有认领过 Bug——配置好连接与产品 ID 后点「立即扫描」。": "No bugs claimed yet — set up the connection and product IDs, then click \"Scan Now\".",
1823
+ "禅道配置已保存": "ZenTao settings saved",
1824
+ "保存失败:": "Save failed: ",
1825
+ "连接成功": "Connected",
1826
+ "连接失败": "Connection failed",
1827
+ "扫描完成,新认领 ": "Scan finished, claimed ",
1828
+ " 个 Bug": " bug(s)",
1776
1829
  };
1777
1830
 
1778
1831
  // ---------- 工具 ----------
package/app/ui/index.html CHANGED
@@ -177,6 +177,7 @@
177
177
  <button class="set-item active" data-sub="tasks" data-i18n="任务"><svg class="ico" aria-hidden="true"><use href="#i-tasks"></use></svg>任务</button>
178
178
  <button class="set-item" data-sub="runs" data-i18n="运行记录"><svg class="ico" aria-hidden="true"><use href="#i-history"></use></svg>运行记录</button>
179
179
  <button class="set-item" data-sub="automation" data-i18n="自动化"><svg class="ico" aria-hidden="true"><use href="#i-calendar-days"></use></svg>自动化</button>
180
+ <button class="set-item" data-sub="zentao" data-i18n="禅道"><svg class="ico" aria-hidden="true"><use href="#i-search"></use></svg>禅道</button>
180
181
  <button class="set-item" data-sub="usage" data-i18n="用量统计"><svg class="ico" aria-hidden="true"><use href="#i-chart"></use></svg>用量统计</button>
181
182
  <div data-i18n="配置" class="set-group">配置</div>
182
183
  <button class="set-item" data-sub="agents" data-i18n="智能体管理"><svg class="ico" aria-hidden="true"><use href="#i-cpu"></use></svg>智能体管理</button>
@@ -383,6 +384,7 @@
383
384
  <button data-i18n="✎ 编辑重试" id="btn-editretry" class="ghost hidden" data-i18n-title="取回原任务的目标/目录/评审设置,修改后提交即重新开跑" title="取回原任务的目标/目录/评审设置,修改后提交即重新开跑">✎ 编辑重试</button>
384
385
  <button data-i18n="继续连载" id="btn-continue" class="ghost hidden">继续连载</button>
385
386
  <button data-i18n="基于此任务新建" id="btn-newfrom" class="ghost hidden">基于此任务新建</button>
387
+ <button data-i18n="分享页" id="btn-share" class="ghost hidden" data-i18n-title="生成自包含分享页(单文件 HTML),可直接发给他人查看任务成果" title="生成自包含分享页(单文件 HTML),可直接发给他人查看任务成果">分享页</button>
386
388
  <button data-i18n="删除记录" id="btn-delete" class="danger hidden">删除记录</button>
387
389
  <button data-i18n="✉ 继续对话" id="btn-talk" class="ghost hidden" data-i18n-title="人工检查有问题?告诉主智能体,由它判断怎么引导执行" title="人工检查有问题?告诉主智能体,由它判断怎么引导执行">✉ 继续对话</button>
388
390
  <button id="rd-more-toggle" class="rd-more-toggle" type="button" aria-label="更多任务信息" data-i18n-aria="更多任务信息" aria-expanded="false" aria-controls="rd-meta-popover" title="更多任务信息" data-i18n-title="更多任务信息">•••</button>
@@ -519,6 +521,53 @@
519
521
  </div>
520
522
  </div>
521
523
 
524
+ <!-- 禅道:Bug 自动扫描修复 -->
525
+ <div id="sub-zentao" class="subpage hidden">
526
+ <div class="panel wide">
527
+ <div class="panel-head">
528
+ <h2 data-i18n="禅道 Bug 自动修复">禅道 Bug 自动修复</h2>
529
+ <div class="actions">
530
+ <button id="btn-zentao-test" class="ghost small" title="用当前表单里的连接信息试连禅道" data-i18n-title="用当前表单里的连接信息试连禅道"><span data-i18n="测试连接">测试连接</span></button>
531
+ <button id="btn-zentao-scan" class="primary small" title="立刻扫一遍激活 Bug 并对账回写" data-i18n-title="立刻扫一遍激活 Bug 并对账回写"><span data-i18n="立即扫描">立即扫描</span></button>
532
+ </div>
533
+ </div>
534
+ <p data-i18n="定时扫描禅道的激活 Bug,自动创建代码修复任务(实现→验证→评审→修复);跑完自动合并代码、resolve Bug 并把报告评论回禅道,群机器人同步通知。" class="hint">定时扫描禅道的激活 Bug,自动创建代码修复任务(实现→验证→评审→修复);跑完自动合并代码、resolve Bug 并把报告评论回禅道,群机器人同步通知。</p>
535
+ <div id="zentao-status" class="hint zt-status"></div>
536
+ <h3 data-i18n="禅道连接" class="sec-title">禅道连接</h3>
537
+ <div class="zt-grid">
538
+ <label data-i18n="禅道地址">禅道地址</label><input id="zt-base-url" type="text" placeholder="https://zentao.example.com(子目录部署带子目录)">
539
+ <label data-i18n="账号">账号</label><input id="zt-account" type="text" placeholder="建议建专用账号(如 codebee)收 Bug">
540
+ <label data-i18n="密码">密码</label><input id="zt-password" type="password" placeholder="" autocomplete="new-password">
541
+ <label data-i18n="产品 ID">产品 ID</label><input id="zt-products" type="text" placeholder="逗号分隔,如 1,2(禅道产品页 URL 里的 product= 数字)">
542
+ <label data-i18n="只认领指派给">只认领指派给</label><input id="zt-assigned" type="text" placeholder="禅道账号名,空 = 不按指派过滤">
543
+ <label data-i18n="严重度上限">严重度上限</label>
544
+ <select id="zt-sev">
545
+ <option value="0" data-i18n="不限">不限</option>
546
+ <option value="1">1(最严重)</option>
547
+ <option value="2">2</option>
548
+ <option value="3">3</option>
549
+ <option value="4">4(最轻)</option>
550
+ </select>
551
+ </div>
552
+ <h3 data-i18n="修复任务" class="sec-title">修复任务</h3>
553
+ <div class="zt-grid">
554
+ <label data-i18n="工作目录">工作目录</label><input id="zt-workdir" type="text" placeholder="空 = 跟随默认保存路径">
555
+ <label data-i18n="基线分支">基线分支</label><input id="zt-gitrev" type="text" placeholder="如 main:每个修复检出任务分支,跑完可自动合并;空 = 直接改工作目录">
556
+ <label data-i18n="验证命令">验证命令</label><input id="zt-verify" type="text" placeholder="如 npm test:实现完成后先跑它把关;空 = 靠评审把关">
557
+ </div>
558
+ <h3 data-i18n="自动化" class="sec-title">自动化</h3>
559
+ <div class="zt-grid zt-grid-toggles">
560
+ <label class="toggle" title="修复任务评审通过后,自动到禅道 resolve(fixed)并回评论"><input id="zt-autoresolve" type="checkbox"><span data-i18n="自动 resolve Bug">自动 resolve Bug</span></label>
561
+ <label class="toggle" title="把任务分支自动合并回基线分支(需配置基线分支;合并失败不 resolve,留人工)"><input id="zt-automerge" type="checkbox"><span data-i18n="自动合并代码">自动合并代码</span></label>
562
+ <label class="toggle" title="按下面的间隔定时扫描;关掉后仍可手动「立即扫描」"><input id="zt-poll" type="checkbox"><span data-i18n="定时扫描">定时扫描</span></label>
563
+ <label data-i18n="扫描间隔(小时)">扫描间隔(小时)</label><input id="zt-interval" type="number" min="1" max="168" step="1">
564
+ </div>
565
+ <div class="ops"><button id="btn-zentao-save" class="primary small"><span data-i18n="保存配置">保存配置</span></button></div>
566
+ <h3 data-i18n="修复记录" class="sec-title">修复记录</h3>
567
+ <div id="zentao-claims"></div>
568
+ </div>
569
+ </div>
570
+
522
571
  <!-- 使用统计 -->
523
572
  <div id="sub-usage" class="subpage hidden">
524
573
  <div class="panel wide">
package/app/ui/style.css CHANGED
@@ -5123,3 +5123,20 @@ body.welcome-open { overflow: hidden; }
5123
5123
  .set-v2-row input[type="number"], .set-v2-row input[type="text"] { width: 140px; }
5124
5124
  .set-v2-lb { font-size: 12px; color: var(--muted); }
5125
5125
  .set-v2-msg { font-size: 12px; }
5126
+
5127
+ /* ---------------- 禅道 Bug 自动修复设置页(追加于文件尾防并行重排) ---------------- */
5128
+ .zt-status { margin: 2px 0 6px; }
5129
+ .zt-err { color: var(--danger, #dc2626); margin-left: 10px; }
5130
+ .zt-grid {
5131
+ display: grid; grid-template-columns: 150px minmax(240px, 560px);
5132
+ gap: 8px 14px; align-items: center; margin: 6px 0 4px;
5133
+ }
5134
+ .zt-grid label { font-size: 12.5px; color: var(--muted); }
5135
+ .zt-grid input[type="text"], .zt-grid input[type="password"], .zt-grid input[type="number"],
5136
+ .zt-grid select { width: 100%; max-width: 560px; }
5137
+ .zt-grid-toggles { grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); }
5138
+ .zt-grid-toggles label { display: flex; align-items: center; gap: 8px; color: inherit; font-size: 13px; }
5139
+ .zt-tag-ok { color: var(--ok); }
5140
+ .zt-tag-warn { color: var(--warn, #b45309); border-color: var(--warn, #b45309); }
5141
+ .zt-tag-err { color: var(--danger, #dc2626); border-color: var(--danger, #dc2626); }
5142
+ .zt-tag-fix { color: var(--accent); border-color: var(--accent); }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codebee",
3
- "version": "0.1.14",
3
+ "version": "0.1.15",
4
4
  "description": "CodeBee · 多智能体编排台 — 统一编排本机 AI 编码 CLI(Codex / Claude Code / QwenCode / OpenCode / Aider…):目标→拆解→路由→执行→验证→跨厂商评审→修复→报告。纯 Python 标准库实现,本地 Web UI。",
5
5
  "license": "MIT",
6
6
  "bin": {