codebee 0.1.37 → 0.1.39

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,18 @@ README 元数据带回,供老版本在「发现新版本」时展示新版更
6
6
 
7
7
  ## 未发布
8
8
 
9
+ ## v0.1.39(2026-09-21)
10
+
11
+ ### 🛠 问题修复
12
+
13
+ - 修复长任务上下文撑爆后重试仍失败:压缩生成的会话摘要此前只写日志、没有真正替换发往模型的上下文;现在重试改用「压缩摘要 + 最近消息」重建请求并放弃膨胀的旧会话,超长连载/代码任务能真正续跑而不是再爆一次。
14
+
15
+ ## v0.1.38(2026-09-21)
16
+
17
+ ### ✨ 新功能
18
+
19
+ - 对话里发出的图片附件现在直接以 96×96 缩略图展示在气泡中(此前只有一串文件名),点击可放大查看;发图片时会提醒一次:若当前模型未开启「图」输入,模型将看不到这张图(可在模型接入页为模型打开图片能力)。
20
+
9
21
  ## v0.1.37(2026-09-21)
10
22
 
11
23
  ### ✨ 新功能
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
+ ### 🛠 问题修复 · Fixes
25
+
26
+ - 修复长任务上下文撑爆后重试仍失败:压缩摘要此前只写日志、没真正替换发往模型的上下文;现在重试用「压缩摘要+最近消息」重建请求并放弃膨胀的旧会话,超长任务能真正续跑。
27
+ - Fixed long-task overflow retries failing again: compaction summaries used to go only to logs. Retries now rebuild the request from the summary plus recent messages and drop the bloated old session.
28
+
24
29
  ### ✨ 新功能 · New
25
30
 
26
31
  - 新建任务输入框焕新:编排模式、思考程度、「厂商/模型」选择器收进输入框工具行右侧(左附件、右偏好+发送的主流布局);对话模型点开是「厂商 → 模型」两级菜单,选中打勾、底部「管理模型」直达,pill 显示「厂商/模型」组合名。
@@ -101,7 +101,33 @@ def execute_step(session, run_agent_fn, prompt, *, model: str = "",
101
101
  log.info("overflow but no compaction progress (gen %s->%s), no retry",
102
102
  gen0, gen1)
103
103
  return result, False
104
- # 压缩生效:surface 已折叠,重新派生消息后用同一 prompt 重试一次
105
- log.info("compaction advanced gen %s->%s, retrying step", gen0, gen1)
106
- result2 = run_agent_fn(prompt, **kwargs)
104
+ # 压缩生效:surface 已折叠。**真正替换 CLI 请求上下文**(2026-09-21
105
+ # 清最老待深挖项:此前重试仍发原 prompt + 续旧 resume 会话——摘要只进
106
+ # 审计日志,CLI 看到的上下文一点没小,重试必然再爆):
107
+ # ①重试 prompt 改用 session 派生转录(折叠摘要 + 近尾消息,体量骤降);
108
+ # ②弃 resume(旧会话本体仍是膨胀态,续上就前功尽弃)。
109
+ msgs = []
110
+ try:
111
+ msgs = session.derive_messages()
112
+ except Exception:
113
+ log.debug("derive_messages failed on retry", exc_info=True)
114
+ kwargs2 = dict(kwargs)
115
+ if msgs:
116
+ transcript = "\n\n".join("[%s] %s" % (m.get("role"), m.get("content"))
117
+ for m in msgs)
118
+ if len(transcript) > 24_000:
119
+ transcript = transcript[-24_000:]
120
+ retry_prompt = transcript
121
+ # 本步指令必须在场:被折叠/截断掉就显式补在尾部(缺失指令比重复严重)
122
+ tail = transcript[-2000:]
123
+ if prompt[:400] not in tail:
124
+ retry_prompt = transcript + "\n\n" + prompt
125
+ kwargs2.pop("resume", None)
126
+ log.info("compaction advanced gen %s->%s, retrying with derived "
127
+ "context (%d msgs, resume dropped)", gen0, gen1, len(msgs))
128
+ result2 = run_agent_fn(retry_prompt, **kwargs2)
129
+ else:
130
+ log.info("compaction advanced gen %s->%s but no surface derived, "
131
+ "retrying same prompt", gen0, gen1)
132
+ result2 = run_agent_fn(prompt, **kwargs)
107
133
  return result2, True
package/app/ui/app.js CHANGED
@@ -158,32 +158,34 @@ window.toggleModelMenu = toggleModelMenu;
158
158
  if (!menu || menu.dataset.cmpBound) return;
159
159
  menu.dataset.cmpBound = "1";
160
160
  menu.addEventListener("click", (e) => {
161
+ // 钻取/选定都在菜单内完成:阻止冒泡到 document 的「点外面收起」——
162
+ // innerHTML 重写后 e.target 是已脱离 DOM 的旧节点,wrap.contains 会误判
163
+ // false 而把刚钻取的菜单收掉(真机「点厂商没反应」根因)
164
+ e.stopPropagation();
161
165
  const b = e.target.closest("[data-p],[data-back],[data-manage]");
162
166
  if (!b) return;
163
167
  if (b.dataset.manage) { toggleModelMenu(false); switchTab("models"); return; }
164
168
  if (b.dataset.back) { cmpModelProv = ""; cmpModelMenuRender(); return; }
165
169
  if (b.dataset.m === undefined) {
166
- // 点厂商 = 厂商+推荐模型一起定(用户反馈「只选了厂商」):带出该厂商
167
- // 排序最前的模型并展开模型级菜单,可立即改选;已是当前厂商则只钻取。
168
- // 注意先 renderDirectModelPicker 重建模型 options,再赋值才生效。
169
- const pid = b.dataset.p;
170
- if ($("f-direct-provider").value !== pid) {
171
- $("f-direct-provider").value = pid;
172
- renderDirectModelPicker();
173
- const p = directProviders().find((x) => x.id === pid);
174
- const names = p ? (p.models || []).filter((m) => m.enabled !== false && !m.hidden)
175
- .map((m) => m.name).filter(Boolean) : [];
176
- const ms = $("f-direct-model-name");
177
- if (names.length && names.includes(names[0])) ms.value = names[0];
178
- cmpDirectBtnSync();
179
- }
180
- cmpModelProv = pid;
170
+ // 点厂商 = 只钻取到模型级(用户拍板 2026-09-21:模型必须亲手点,
171
+ // 不要自动带推荐)。真源不动——用户点了具体模型才写 provider/model。
172
+ cmpModelProv = b.dataset.p;
181
173
  cmpModelMenuRender();
182
174
  return;
183
175
  }
184
- // 选定:写回隐藏 select 真源(提交流/回归测试全走原路径)
176
+ // 选定:写回隐藏 select 真源(提交流/回归测试全走原路径)。顺序关键:
177
+ // 先让 provider 的 option 就位再赋值,否则 select.value 静默失效(真机
178
+ // 「没法选模型」根因:options 由 poll 重建,可能仍是残缺态)
179
+ renderDirectModelPicker();
185
180
  $("f-direct-provider").value = b.dataset.p;
181
+ renderDirectModelPicker();
186
182
  $("f-direct-model-name").value = b.dataset.m;
183
+ if ($("f-direct-model-name").value !== b.dataset.m) {
184
+ const opt = document.createElement("option");
185
+ opt.value = b.dataset.m;
186
+ $("f-direct-model-name").appendChild(opt);
187
+ $("f-direct-model-name").value = b.dataset.m;
188
+ }
187
189
  cmpDirectBtnSync();
188
190
  toggleModelMenu(false);
189
191
  });
@@ -338,6 +340,7 @@ function onTypeChange() {
338
340
  }
339
341
 
340
342
  function directProviders() {
343
+ /* 停用厂商不列出(用户拍板 2026-09-21「停用的不弄出来」);只列启用且有 key 的 */
341
344
  return (S.providers || []).filter((p) => p.enabled !== false && p.api_key);
342
345
  }
343
346
 
@@ -351,7 +354,7 @@ function renderDirectModelPicker() {
351
354
  if (prev && provs.some((p) => p.id === prev)) ps.value = prev;
352
355
  const p = provs.find((x) => x.id === ps.value);
353
356
  const oldModel = ms.value;
354
- const names = p ? (p.models || []).filter((m) => m.enabled !== false && !m.hidden)
357
+ const names = p ? (p.models || []).filter((m) => !m.hidden)
355
358
  .map((m) => m.name).filter(Boolean) : [];
356
359
  ms.innerHTML = '<option value="">' + t("随厂商推荐") + '</option>' + names.map((name) =>
357
360
  '<option value="' + esc(name) + '">' + esc(name) + "</option>").join("");
@@ -1145,9 +1148,10 @@ async function poll() {
1145
1148
  S.providers = models.providers; S.bindings = models.bindings;
1146
1149
  S.modelCatalog = models.catalog || [];
1147
1150
  S.sourceNames = models.source_names || S.sourceNames || {};
1148
- if ($("f-direct-model") && !$('f-direct-model').classList.contains("hidden")) {
1149
- renderDirectModelPicker();
1150
- }
1151
+ // /api/models 到手即重建对话模型选项(无条件):旧逻辑只在「更多选项」
1152
+ // 旧块可见时重建——旧块隐藏后选项永远只有「自动推荐」,厂商写值全部
1153
+ // 静默失效(2026-09-21 用户实测「没法选模型」根因)
1154
+ renderDirectModelPicker();
1151
1155
  // 供应商集合/启停/密钥变了才重拉编排者:否则「生效中」状态点会在停用厂商后失真
1152
1156
  const provSig = JSON.stringify((S.providers || []).map((p) => [p.id, p.enabled, !!p.api_key]));
1153
1157
  if (provSig !== S.provSig) { S.provSig = provSig; loadOrchestrator(); }
@@ -6610,7 +6614,10 @@ async function renderChat(run, active) {
6610
6614
  let rel = typeof a === "string" ? a : ((a && (a.path || a.name)) || nm);
6611
6615
  rel = String(rel).replace(/\\/g, "/");
6612
6616
  if (!rel.startsWith("_attachments/")) rel = "_attachments/" + rel.split("/").pop();
6613
- return { nm, rel };
6617
+ const isImg = /\.(png|jpe?g|gif|webp|bmp|svg)$/i.test(nm);
6618
+ const src = isImg && chatRunId
6619
+ ? "/api/runs/" + encodeURIComponent(chatRunId) + "/file?name=" + encodeURIComponent(rel) : "";
6620
+ return { nm, rel, img: src };
6614
6621
  }).filter(Boolean);
6615
6622
  return '<div class="chat-row me">' +
6616
6623
  '<div class="chat-bubble me">' +
@@ -6619,8 +6626,11 @@ async function renderChat(run, active) {
6619
6626
  esc(it.text || t("(仅附件)")) +
6620
6627
  (atts.length
6621
6628
  ? '<div class="chat-atts">' + atts.map((a) =>
6622
- '<span class="att-chip2 att-open" data-att="' + esc(a.rel) +
6623
- '" title="' + esc(t("点击查看")) + '">' + esc(a.nm) + "</span>").join("") + "</div>"
6629
+ (a.img
6630
+ ? '<span class="att-open chat-img" data-att="' + esc(a.rel) + '" title="' + esc(a.nm) + '">' +
6631
+ '<img src="' + esc(a.img) + '" alt="' + esc(a.nm) + '" loading="lazy" draggable="false"></span>'
6632
+ : '<span class="att-chip2 att-open" data-att="' + esc(a.rel) +
6633
+ '" title="' + esc(t("点击查看")) + '">' + esc(a.nm) + "</span>")).join("") + "</div>"
6624
6634
  : "") +
6625
6635
  "</div></div>";
6626
6636
  }
@@ -6883,6 +6893,14 @@ async function chatUploadFiles(files) {
6883
6893
  const r = await api("/api/attachments", { method: "POST",
6884
6894
  body: JSON.stringify({ name: f.name, data: b64 }) });
6885
6895
  chatAtts.push(r.attachment); drawChatAtts();
6896
+ // 图片能力前置提醒:模型 image_in 未开启时收不到图(内置智能体会降级为
6897
+ // 纯文本,模型只能回复「读不到」——与其事后看模型解释,不如发图时提一句)
6898
+ if (/^image\//i.test(String(f.type || "")) || /\.(png|jpe?g|gif|webp|bmp)$/i.test(f.name || "")) {
6899
+ if (!window._chatImgTipShown) {
6900
+ window._chatImgTipShown = true;
6901
+ toast(t("已添加图片。若当前模型未开启「图」输入(模型接入页可开),模型将看不到这张图"));
6902
+ }
6903
+ }
6886
6904
  } catch (e) { toast(t("附件上传失败:") + f.name + " — " + e.message, true); }
6887
6905
  }
6888
6906
  }
package/app/ui/i18n.js CHANGED
@@ -291,6 +291,7 @@
291
291
  "指令已入箱,将在下一个步骤下达": "Directive queued — it will be delivered with the next step",
292
292
  "发送失败:": "Send failed: ",
293
293
  "附件上传失败:": "Upload failed: ",
294
+ "已添加图片。若当前模型未开启「图」输入(模型接入页可开),模型将看不到这张图": "Image added. If the model doesn't have the image-input badge on (enable it on the Models page), it won't see this picture",
294
295
  "读取文件失败": "Failed to read file",
295
296
  ":超过 8MB,已跳过": ": over 8MB, skipped",
296
297
  "附件最多 ": "Max ",
package/app/ui/style.css CHANGED
@@ -5632,8 +5632,11 @@ button.request-busy > * { opacity: .18; }
5632
5632
  font: inherit; color: inherit; cursor: pointer; }
5633
5633
  /* 「厂商/模型」组合名比单档选项长:放宽截断(其他 pill 保持 88px 短名) */
5634
5634
  #f-direct-wrap .cmp-sel-btn { max-width: 200px; overflow: hidden; text-overflow: ellipsis; }
5635
- /* 厂商/模型钻取菜单:脱离 pill 宽度限制(type-menu 默认 left/right 撑满父级) */
5636
- #cmp-model-menu { left: 0; right: auto; min-width: 250px; }
5635
+ /* 厂商/模型钻取菜单:脱离 pill 宽度限制(type-menu 默认 left/right 撑满父级)。
5636
+ * 向上弹+z-index 提到 95:composer 靠页面底部,向下展开会被右栏 aside 盖住
5637
+ * (真机实测 elementFromPoint 命中 ASIDE——2026-09-21「选不了模型」) */
5638
+ #cmp-model-menu { left: 0; right: auto; min-width: 250px; z-index: 95;
5639
+ top: auto; bottom: calc(100% + 6px); }
5637
5640
 
5638
5641
  /* 插件市场:内置/已安装/可安装分区标题与已装启停开关(对齐 ZCode 形态) */
5639
5642
  #mk-grid .sec-title { margin: 14px 0 6px; }
@@ -5671,3 +5674,10 @@ button.request-busy > * { opacity: .18; }
5671
5674
  background: var(--panel); color: var(--muted); }
5672
5675
  .att-chip.att-image .ax:hover { background: var(--bad); color: #fff; }
5673
5676
  .att-chip.att-image .ax .ico { width: 11px; height: 11px; }
5677
+
5678
+ /* 对话气泡图片附件缩略图(2026-09-21 用户反馈:图片只显示文件名要点开才见):
5679
+ * 96×96 圆角卡,点击仍走 att-open 弹大图 */
5680
+ .chat-img { display: inline-block; cursor: zoom-in; }
5681
+ .chat-img img { display: block; width: 96px; height: 96px; object-fit: cover;
5682
+ border-radius: 10px; border: 1px solid var(--border); background: var(--panel2); }
5683
+ .chat-atts { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 8px; }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codebee",
3
- "version": "0.1.37",
3
+ "version": "0.1.39",
4
4
  "description": "CodeBee · 多智能体编排台 — 统一编排本机 AI 编码 CLI(Codex / Claude Code / QwenCode / OpenCode / Aider…):目标→拆解→路由→执行→验证→跨厂商评审→修复→报告。纯 Python 标准库实现,本地 Web UI。",
5
5
  "license": "MIT",
6
6
  "bin": {