codebee 0.1.45 → 0.1.46

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 CHANGED
@@ -6,6 +6,12 @@ README 元数据带回,供老版本在「发现新版本」时展示新版更
6
6
 
7
7
  ## 未发布
8
8
 
9
+ ## v0.1.46(2026-09-22)
10
+
11
+ ### ✨ 新功能
12
+
13
+ - 对话时间线支持复制与编辑重发:自己发的消息悬停出现「复制 / 编辑」,「编辑」把原文载入输入框,改完再发;智能体的回答悬停可「复制」(自动剥掉协议尾,复制到的就是看到的内容)。
14
+
9
15
  ## v0.1.45(2026-09-22)
10
16
 
11
17
  ### 🛠 问题修复
package/README.md CHANGED
@@ -21,6 +21,11 @@ Kimi Code、MiMo Code、Grok Build、Pi、DeepSeek Harness……),提供
21
21
  不会把你的任务内容交给任何第三方。
22
22
 
23
23
  <!-- relnotes:start -->
24
+ ### ✨ 新功能 · New
25
+
26
+ - 对话时间线支持复制与编辑重发:自己发的消息悬停出现「复制 / 编辑」,编辑载入原文改完再发;智能体回答悬停可复制(自动剥协议尾)。
27
+ - Chat timeline messages now support copy & edit-to-resend: hover your own message for Copy / Edit (edit loads the original into the input box); agent replies offer Copy with protocol tails stripped.
28
+
24
29
  ### 🛠 问题修复 · Fixes
25
30
 
26
31
  - 智能体读超大文件不再只给开头:保留开头 44KB+结尾 16KB、中段标注省略——文件末尾的报错与结论不再被截掉,结尾中文不乱码。
package/app/ui/app.js CHANGED
@@ -6530,6 +6530,17 @@ function bindDirector() {
6530
6530
  * 进程,运行中递的话只在下一步生效。 */
6531
6531
  let chatRunId = null;
6532
6532
  let chatAtts = [];
6533
+ let chatTimelineItems = []; // renderChat 每次重建时间线时刷新;复制/编辑重发按索引回查
6534
+
6535
+ /* 聊天正文协议清洗:剥 DIRECT_DONE/<followups> 尾巴与「本轮做了什么」复读头。
6536
+ * 渲染与「复制」共用同一口径——复制出来的就是看到的内容。 */
6537
+ function chatCleanText(raw) {
6538
+ return String(raw || "")
6539
+ .replace(/^\s*本轮做了什么\s*[::][^\n]*\n+(回复\s*[::]\s*\n?)?/, "")
6540
+ .replace(/\n*DIRECT_DONE:.*\s*$/s, "")
6541
+ .replace(/\n*<followups>[\s\S]*?<\/followups>\s*$/s, "")
6542
+ .trim();
6543
+ }
6533
6544
  let chatSig = "";
6534
6545
 
6535
6546
  function chatEngineIsDirect(run) {
@@ -6608,7 +6619,8 @@ async function renderChat(run, active) {
6608
6619
  const attName = (a) => { const p = typeof a === "string" ? a
6609
6620
  : ((a && (a.name || a.path)) || "");
6610
6621
  return String(p).split(/[\\/]/).pop() || ""; };
6611
- let html = items.map((it) => {
6622
+ chatTimelineItems = items; // 复制/编辑重发按索引回查(见 rd-chat-flow 委托)
6623
+ let html = items.map((it, idx) => {
6612
6624
  if (it.kind === "user") {
6613
6625
  // 用户消息:右侧浅灰圆角块,元信息在块内顶部。
6614
6626
  // 附件胶囊可点开预览:data-att 带 _attachments/ 相对路径(老数据裸文件名
@@ -6625,10 +6637,15 @@ async function renderChat(run, active) {
6625
6637
  ? "/api/runs/" + encodeURIComponent(chatRunId) + "/file?name=" + encodeURIComponent(rel) : "";
6626
6638
  return { nm, rel, img: src };
6627
6639
  }).filter(Boolean);
6640
+ // 悬停动作:复制原文 / 编辑重发(载入输入框改完再发,2026-09-22 用户诉求)
6641
+ const acts = '<span class="chat-acts2">' +
6642
+ '<button type="button" class="chat-act" data-cact="copy" data-ci="' + idx + '">' + esc(t("复制")) + "</button>" +
6643
+ '<button type="button" class="chat-act" data-cact="edit" data-ci="' + idx + '">' + esc(t("编辑")) + "</button>" +
6644
+ "</span>";
6628
6645
  return '<div class="chat-row me">' +
6629
6646
  '<div class="chat-bubble me">' +
6630
6647
  '<div class="chat-meta">' + esc(it.who || "") + " " + esc(chatTime(it.at)) +
6631
- (it.consumed ? "" : " · " + t("待送达")) + "</div>" +
6648
+ (it.consumed ? "" : " · " + t("待送达")) + acts + "</div>" +
6632
6649
  esc(it.text || t("(仅附件)")) +
6633
6650
  (atts.length
6634
6651
  ? '<div class="chat-atts">' + atts.map((a) =>
@@ -6644,11 +6661,7 @@ async function renderChat(run, active) {
6644
6661
  // <followups> 块后端已剥并转成结构化字段,这里再兜一次底防老数据/漏剥;
6645
6662
  // 开场「本轮做了什么:…」+「回复:」是模型复读协议措辞的元描述(提示词已禁,
6646
6663
  // 这里兜历史数据),只剥消息开头的第一行
6647
- const body = String(it.text || "")
6648
- .replace(/^\s*本轮做了什么\s*[::][^\n]*\n+(回复\s*[::]\s*\n?)?/, "")
6649
- .replace(/\n*DIRECT_DONE:.*\s*$/s, "")
6650
- .replace(/\n*<followups>[\s\S]*?<\/followups>\s*$/s, "")
6651
- .trim();
6664
+ const body = chatCleanText(it.text);
6652
6665
  // 运行中还没有正文:三点打字动画(终态无正文才落「无文本输出」占位)
6653
6666
  const bodyHtml = body ? chatBodyHTML(body)
6654
6667
  : (it.status === "running"
@@ -6656,13 +6669,17 @@ async function renderChat(run, active) {
6656
6669
  : esc(t("(本轮无文本输出)")));
6657
6670
  // 元信息只留 名字+时间(+失败标记):路由/供应商细节属于「步骤」页签,塞这里就是噪音
6658
6671
  const metaBad = it.status === "failed" || it.status === "cancelled";
6672
+ // 智能体回复:头像 + 整幅正文(不套气泡框),元信息小字落在正文下方;
6673
+ // 悬停可复制回答原文(协议清洗后)
6674
+ const actsA = body ? '<span class="chat-acts2">' +
6675
+ '<button type="button" class="chat-act" data-cact="copy" data-ci="' + idx + '">' + esc(t("复制")) + "</button></span>" : "";
6659
6676
  // 智能体回复:头像 + 整幅正文(不套气泡框),元信息小字落在正文下方
6660
6677
  return '<div class="chat-row">' +
6661
6678
  '<span class="chat-avatar" aria-hidden="true"><svg class="ico"><use href="#i-bee"></use></svg></span>' +
6662
6679
  '<div class="chat-bubble agent">' +
6663
6680
  '<div class="chat-body">' + bodyHtml + "</div>" +
6664
6681
  '<div class="chat-meta">' + esc(it.who || "") + " " + esc(chatTime(it.at)) +
6665
- (metaBad ? " · " + t(it.status === "failed" ? "失败" : "已取消") : "") + "</div>" +
6682
+ (metaBad ? " · " + t(it.status === "failed" ? "失败" : "已取消") : "") + actsA + "</div>" +
6666
6683
  (it.log && it.run ? '<div class="chat-actions"><button class="chat-log-btn" type="button" data-chat-log-run="' +
6667
6684
  esc(it.run) + '" data-chat-log-rel="' + esc(it.log) + '" title="' + esc(t("查看日志")) + '">' + esc(t("CLI 日志")) + "</button></div>" : "") +
6668
6685
  (Array.isArray(it.followups) && it.followups.length
@@ -7144,6 +7161,27 @@ function bindChat() {
7144
7161
  artPopup(file.dataset.fileRun, file.dataset.fileName || "", Number(file.dataset.fileSize) || 0);
7145
7162
  return;
7146
7163
  }
7164
+ // 复制 / 编辑重发(2026-09-22 用户诉求:已发送的对话可复制修改再次发送)。
7165
+ // 编辑=原文载入输入框,用户改完按发送即再次发出(不静默自动重发)
7166
+ const act = e.target.closest("[data-cact]");
7167
+ if (act) {
7168
+ const it = chatTimelineItems[Number(act.dataset.ci)];
7169
+ if (!it) return;
7170
+ const txt = it.kind === "user" ? String(it.text || "") : chatCleanText(it.text);
7171
+ if (act.dataset.cact === "copy") {
7172
+ copyText(txt);
7173
+ toast(t("已复制"));
7174
+ } else if (act.dataset.cact === "edit") {
7175
+ const ta = $("rd-chat-input");
7176
+ if (ta && txt) {
7177
+ ta.value = txt;
7178
+ ta.focus();
7179
+ ta.selectionStart = ta.selectionEnd = ta.value.length;
7180
+ toast(t("已载入原文,可修改后发送"));
7181
+ }
7182
+ }
7183
+ return;
7184
+ }
7147
7185
  const chip = e.target.closest(".att-open");
7148
7186
  if (chip) window.chatAttPopup(chip.dataset.att);
7149
7187
  });
package/app/ui/i18n.js CHANGED
@@ -242,6 +242,7 @@
242
242
  "双击弹窗查看该文件的变更": "Double-click to view this file's changes in a popup",
243
243
  "复制": "Copy",
244
244
  "已复制": "Copied",
245
+ "已载入原文,可修改后发送": "Original text loaded — edit and send",
245
246
  "选择文件夹": "Choose folder",
246
247
  "此电脑": "This PC",
247
248
  "上级": "Up",
package/app/ui/style.css CHANGED
@@ -5696,3 +5696,15 @@ button.request-busy > * { opacity: .18; }
5696
5696
  /* 气泡图片缩略图:限制在气泡内(曾压住输入框边框线——2026-09-21 用户反馈) */
5697
5697
  .chat-img img { box-sizing: border-box; }
5698
5698
  .chat-bubble { overflow-wrap: anywhere; }
5699
+
5700
+ /* 对话气泡悬停动作(复制/编辑重发,2026-09-22):默认隐没在元信息行,
5701
+ * 悬停该条消息才浮现,不与正文抢注意力 */
5702
+ .chat-meta { display: flex; align-items: center; gap: 6px; }
5703
+ .chat-acts2 { margin-left: auto; display: inline-flex; gap: 2px;
5704
+ opacity: 0; transition: opacity .15s; }
5705
+ .chat-row:hover .chat-acts2 { opacity: 1; }
5706
+ .chat-act { appearance: none; border: none; background: transparent;
5707
+ color: var(--muted); font: inherit; font-size: 11px; cursor: pointer;
5708
+ padding: 1px 7px; border-radius: 6px; }
5709
+ .chat-act:hover { background: color-mix(in srgb, var(--text) 8%, transparent);
5710
+ color: var(--text); }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codebee",
3
- "version": "0.1.45",
3
+ "version": "0.1.46",
4
4
  "description": "CodeBee · 多智能体编排台 — 统一编排本机 AI 编码 CLI(Codex / Claude Code / QwenCode / OpenCode / Aider…):目标→拆解→路由→执行→验证→跨厂商评审→修复→报告。纯 Python 标准库实现,本地 Web UI。",
5
5
  "license": "MIT",
6
6
  "bin": {