codebee 0.1.36 → 0.1.38

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,20 @@ README 元数据带回,供老版本在「发现新版本」时展示新版更
6
6
 
7
7
  ## 未发布
8
8
 
9
+ ## v0.1.38(2026-09-21)
10
+
11
+ ### ✨ 新功能
12
+
13
+ - 对话里发出的图片附件现在直接以 96×96 缩略图展示在气泡中(此前只有一串文件名),点击可放大查看;发图片时会提醒一次:若当前模型未开启「图」输入,模型将看不到这张图(可在模型接入页为模型打开图片能力)。
14
+
15
+ ## v0.1.37(2026-09-21)
16
+
17
+ ### ✨ 新功能
18
+
19
+ - 新建任务输入框焕新:编排模式、思考程度、对话模型收进输入框工具行右侧(与主流 AI 输入框一致的左右分区——左附件、右偏好+发送),不再是散落在框外的下拉;对话模型点开是「厂商 → 模型」两级菜单(选中打勾、底部「管理模型」直达),点厂商即带出该厂商的推荐模型,pill 以「厂商/模型」组合名显示,只对本次对话生效。
20
+ - 图片附件在输入框内以 88×88 缩略卡展示(此前首页按原图尺寸渲染,直接压住输入文字)。
21
+ - 「更多选项」里旧的对话模型下拉已移除(与工具行 pill 重复)。
22
+
9
23
  ## v0.1.36(2026-09-21)
10
24
 
11
25
  ### 🛠 问题修复
package/README.md CHANGED
@@ -23,8 +23,8 @@ Kimi Code、MiMo Code、Grok Build、Pi、DeepSeek Harness……),提供
23
23
  <!-- relnotes:start -->
24
24
  ### ✨ 新功能 · New
25
25
 
26
- - 编排模式 / 思考程度 / 对话模型从「更多选项」提到输入框正下方,随开随用。
27
- - Orchestration mode, reasoning level and chat model now sit right under the input box instead of being folded away.
26
+ - 新建任务输入框焕新:编排模式、思考程度、「厂商/模型」选择器收进输入框工具行右侧(左附件、右偏好+发送的主流布局);对话模型点开是「厂商 → 模型」两级菜单,选中打勾、底部「管理模型」直达,pill 显示「厂商/模型」组合名。
27
+ - The composer was refreshed: orchestration mode, reasoning level and a "provider/model" picker now sit on the input box's right side (attachments left, preferences + send right, like mainstream AI input boxes). The model picker opens a provider → model cascading menu with checkmarks and a shortcut to model management.
28
28
 
29
29
  - 经验库越用越准:教训按任务结局自动记胜负,真正帮上忙的经验优先注入、帮倒忙的被挤出——随真实运行自我提纯。
30
30
  - The lesson library now tracks outcomes: lessons that helped a task pass gain weight, ones that failed to prevent issues fade — it self-refines with every run.
@@ -171,26 +171,46 @@ def installed_ids():
171
171
  def view():
172
172
  """市场目录(给 UI/API):{catalog: [...], categories: [...]}。
173
173
  每项带 installed: bool + enabled: bool(已装包的启停态,卡片开关直接
174
- 切换,不用再去经验库翻——对齐 ZCode 插件管理形态)。"""
174
+ 切换,不用再去经验库翻——对齐 ZCode 插件管理形态)。
175
+ 注意 id 双轨:市场 id(git-workflow)≠ skills 用户包 id(user-哈希),
176
+ 启停态要经安装记录的 file 路径映射到真实用户包再查。"""
175
177
  installed = installed_ids()
178
+ reg = _load_registry().get("installed") or {}
179
+ ups = skills.user_packs()
180
+
181
+ def _lookup(pack_id, is_builtin):
182
+ """返回 (enabled, 可启停的真实包 id)。内置即市场 id;市场包装成的
183
+ 用户包 id 是 user-哈希,必须经安装记录的 file 路径映射。"""
184
+ if is_builtin:
185
+ return skills._pack_enabled(pack_id), pack_id
186
+ rec = reg.get(pack_id) or {}
187
+ fhint = str(rec.get("file") or "").replace("\\", "/")
188
+ for up in ups:
189
+ f = str(up.get("file") or "").replace("\\", "/")
190
+ if fhint and (f == fhint or f.endswith("/" + fhint)):
191
+ return skills._pack_enabled(up["id"]), up["id"]
192
+ return True, pack_id
193
+
176
194
  items = []
177
195
  for p in skills.BUILTIN_PACKS: # 已内置:已存在,不可安装
178
196
  meta = _BUILTIN_META.get(p["id"]) or {}
197
+ en, pid = _lookup(p["id"], True)
179
198
  items.append({"id": p["id"], "name": p["name"],
180
199
  "desc": meta.get("desc") or p.get("note") or "",
181
200
  "category": meta.get("category") or skills.LESSON_UNCATEGORIZED,
182
201
  "scopes": list(p.get("scopes") or ["*"]),
183
202
  "builtin": True, "installed": True, "installable": False,
184
- "enabled": skills._pack_enabled(p["id"]),
203
+ "enabled": en, "pack_id": pid,
185
204
  "chars": len(skills.pack_text(p))})
186
205
  for p in BUILTIN_PACKS: # 可安装市场包
187
206
  files = p.get("files") or {}
207
+ inst = p["id"] in installed
208
+ en, pid = _lookup(p["id"], False) if inst else (False, p["id"])
188
209
  items.append({"id": p["id"], "name": p["name"], "desc": p["desc"],
189
210
  "category": p["category"], "scopes": list(p["scopes"]),
190
- "builtin": False, "installed": p["id"] in installed,
211
+ "builtin": False, "installed": inst,
191
212
  "installable": True,
192
- "enabled": (p["id"] in installed and
193
- skills._pack_enabled(p["id"])),
213
+ "enabled": en, "pack_id": pid,
194
214
  "files": sorted(files),
195
215
  "chars": sum(len(v) for v in files.values())})
196
216
  cats = list(skills.LESSON_CATEGORIES) # 闭集全枚举先给全(UI 下拉不缺项)
@@ -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
@@ -80,9 +80,124 @@ function applyTaskPreferenceDefaults() {
80
80
  if (thinking && ["auto", "low", "standard", "high"].includes(lp.thinking)) thinking.value = lp.thinking;
81
81
  const manual = $("f-manual-only");
82
82
  if (manual && mode) manual.classList.toggle("hidden", mode.value !== "manual");
83
+ syncCmpSelFace("f-mode");
84
+ syncCmpSelFace("f-thinking");
83
85
  S.prefsApplied = true;
84
86
  }
85
87
 
88
+ /* composer 内「编排模式/思考程度」pill 的短名:原生 select 透明覆盖在 pill 上,
89
+ * face 只显示选项的短前缀(「自动(推荐):…」→「自动」),值真源仍是 select。 */
90
+ function cmpSelShort(text) {
91
+ return (String(text || "").split(/[::]/)[0]
92
+ .replace(/[((].*?[))]/g, "").trim()) || "";
93
+ }
94
+
95
+ function syncCmpSelFace(id) {
96
+ const sel = $(id), face = $(id + "-face");
97
+ if (!sel || !face) return;
98
+ const opt = sel.options[sel.selectedIndex];
99
+ face.textContent = cmpSelShort(opt && opt.textContent) || sel.value;
100
+ }
101
+ window.syncCmpSelFace = syncCmpSelFace; // applyI18n 重译 option 后同步短名
102
+
103
+ /* ---- 对话模型 pill:厂商 → 模型两级钻取菜单(ChatGPT 选择器同款语汇)。
104
+ * 真源仍是 f-direct-provider / f-direct-model-name 两个隐藏 select,
105
+ * 菜单只改它们的值——既有提交流 / onTypeChange 显隐 / 测试全兼容。 */
106
+ let cmpModelProv = ""; // ""=厂商级;否则=已钻入的厂商 id(模型级)
107
+
108
+ function cmpDirectBtnSync() {
109
+ const btn = $("f-direct-btn");
110
+ if (!btn) return;
111
+ const pv = $("f-direct-provider").value, mv = $("f-direct-model-name").value;
112
+ if (!pv) { btn.textContent = t("自动推荐"); return; }
113
+ const p = directProviders().find((x) => x.id === pv);
114
+ btn.textContent = (p ? (p.name || p.id) : pv) + (mv ? "/" + mv : "/" + t("推荐"));
115
+ }
116
+
117
+ function cmpModelMenuRender() {
118
+ const menu = $("cmp-model-menu");
119
+ if (!menu) return;
120
+ const pv = $("f-direct-provider").value, mv = $("f-direct-model-name").value;
121
+ const chk = '<svg class="ico ti-check" aria-hidden="true"><use href="#i-check"></use></svg>';
122
+ const arr = '<svg class="ico ti-check" aria-hidden="true"><use href="#i-chevron-r"></use></svg>';
123
+ if (!cmpModelProv) {
124
+ menu.innerHTML =
125
+ '<button type="button" class="type-item" data-p="" data-m=""><span class="ti-body"><span class="ti-name">' +
126
+ t("自动推荐") + "</span></span>" + (!pv ? chk : "") + "</button>" +
127
+ directProviders().map((p) =>
128
+ '<button type="button" class="type-item" data-p="' + esc(p.id) + '"><span class="ti-body"><span class="ti-name">' +
129
+ esc(p.name || p.id) + "</span></span>" + (pv === p.id ? chk : arr) + "</button>").join("") +
130
+ '<div class="type-menu-foot"><button type="button" class="type-item" data-manage="1"><span class="ti-body"><span class="ti-name">' +
131
+ t("管理模型…") + "</span></span></button></div>";
132
+ } else {
133
+ const p = directProviders().find((x) => x.id === cmpModelProv);
134
+ const names = p ? (p.models || []).filter((m) => m.enabled !== false && !m.hidden)
135
+ .map((m) => m.name).filter(Boolean) : [];
136
+ menu.innerHTML =
137
+ '<button type="button" class="type-item" data-back="1"><span class="ti-body"><span class="ti-name">‹ ' +
138
+ esc(p ? (p.name || p.id) : cmpModelProv) + "</span></span></button>" +
139
+ '<button type="button" class="type-item" data-p="' + esc(cmpModelProv) + '" data-m=""><span class="ti-body"><span class="ti-name">' +
140
+ t("随厂商推荐") + "</span></span>" + (pv === cmpModelProv && !mv ? chk : "") + "</button>" +
141
+ names.map((n) =>
142
+ '<button type="button" class="type-item" data-p="' + esc(cmpModelProv) + '" data-m="' + esc(n) + '"><span class="ti-body"><span class="ti-name">' +
143
+ esc(n) + "</span></span>" + (pv === cmpModelProv && mv === n ? chk : "") + "</button>").join("");
144
+ }
145
+ }
146
+
147
+ function toggleModelMenu(force) {
148
+ const menu = $("cmp-model-menu");
149
+ if (!menu) return;
150
+ const show = force !== undefined ? force : menu.classList.contains("hidden");
151
+ if (show) { cmpModelProv = ""; cmpModelMenuRender(); }
152
+ menu.classList.toggle("hidden", !show);
153
+ }
154
+ window.toggleModelMenu = toggleModelMenu;
155
+
156
+ (function () {
157
+ const menu = $("cmp-model-menu");
158
+ if (!menu || menu.dataset.cmpBound) return;
159
+ menu.dataset.cmpBound = "1";
160
+ menu.addEventListener("click", (e) => {
161
+ const b = e.target.closest("[data-p],[data-back],[data-manage]");
162
+ if (!b) return;
163
+ if (b.dataset.manage) { toggleModelMenu(false); switchTab("models"); return; }
164
+ if (b.dataset.back) { cmpModelProv = ""; cmpModelMenuRender(); return; }
165
+ if (b.dataset.m === undefined) {
166
+ // 点厂商 = 厂商+推荐模型一起定(用户反馈「只选了厂商」):带出该厂商
167
+ // 排序最前的模型并展开模型级菜单,可立即改选;已是当前厂商则只钻取。
168
+ // 顺序关键:先 picker 重建 options 再赋值——select.value 对不存在的
169
+ // option 会静默忽略(真机「没法选模型」根因之一)
170
+ const pid = b.dataset.p;
171
+ if ($("f-direct-provider").value !== pid) {
172
+ renderDirectModelPicker();
173
+ $("f-direct-provider").value = pid;
174
+ renderDirectModelPicker();
175
+ const p = directProviders().find((x) => x.id === pid);
176
+ const names = p ? (p.models || []).filter((m) => !m.hidden)
177
+ .map((m) => m.name).filter(Boolean) : [];
178
+ const ms = $("f-direct-model-name");
179
+ if (names.length && names[0]) ms.value = names[0];
180
+ cmpDirectBtnSync();
181
+ }
182
+ cmpModelProv = pid;
183
+ cmpModelMenuRender();
184
+ return;
185
+ }
186
+ // 选定:写回隐藏 select 真源(提交流/回归测试全走原路径)
187
+ $("f-direct-provider").value = b.dataset.p;
188
+ $("f-direct-model-name").value = b.dataset.m;
189
+ cmpDirectBtnSync();
190
+ toggleModelMenu(false);
191
+ });
192
+ document.addEventListener("click", (e) => {
193
+ const wrap = $("f-direct-wrap");
194
+ if (wrap && !wrap.contains(e.target)) toggleModelMenu(false);
195
+ });
196
+ document.addEventListener("keydown", (e) => {
197
+ if (e.key === "Escape") toggleModelMenu(false);
198
+ });
199
+ })();
200
+
86
201
  /* 类型选择的可见层:带黑白图标的按钮 + 弹出菜单。原生 <option> 渲染不了 SVG,
87
202
  * 所以隐藏 select 只当值真源(既有联动/测试全部照旧),外观全靠这层。 */
88
203
  function renderTypeMenu() {
@@ -178,8 +293,11 @@ function onTypeChange() {
178
293
  // 直连:验证命令与评审参数都不适用,两块一起收起(目标+附件即全部输入)
179
294
  if (codeOnly) codeOnly.classList.toggle("hidden", isReview || isDirect);
180
295
  if (reviewOnly) reviewOnly.classList.toggle("hidden", !isReview);
181
- if ($("f-direct-model")) $("f-direct-model").classList.toggle("hidden", !isDirect);
182
- if (isDirect) renderDirectModelPicker();
296
+ // 对话模型选择已收进工具行 pill:更多选项里的旧块永久隐藏(select 真源保留)
297
+ if ($("f-direct-model")) $("f-direct-model").classList.add("hidden");
298
+ const dwrap = $("f-direct-wrap");
299
+ if (dwrap) dwrap.classList.toggle("hidden", !isDirect);
300
+ if (isDirect) { renderDirectModelPicker(); cmpDirectBtnSync(); }
183
301
  const bibleCreate = $("bible-create-field");
184
302
  const isSerialReview = isReview && !!(flow && flow.serial);
185
303
  if (bibleCreate) bibleCreate.classList.toggle("hidden", !isSerialReview);
@@ -222,6 +340,7 @@ function onTypeChange() {
222
340
  }
223
341
 
224
342
  function directProviders() {
343
+ /* 停用厂商不列出(用户拍板 2026-09-21「停用的不弄出来」);只列启用且有 key 的 */
225
344
  return (S.providers || []).filter((p) => p.enabled !== false && p.api_key);
226
345
  }
227
346
 
@@ -235,11 +354,12 @@ function renderDirectModelPicker() {
235
354
  if (prev && provs.some((p) => p.id === prev)) ps.value = prev;
236
355
  const p = provs.find((x) => x.id === ps.value);
237
356
  const oldModel = ms.value;
238
- const names = p ? (p.models || []).filter((m) => m.enabled !== false && !m.hidden)
357
+ const names = p ? (p.models || []).filter((m) => !m.hidden)
239
358
  .map((m) => m.name).filter(Boolean) : [];
240
359
  ms.innerHTML = '<option value="">' + t("随厂商推荐") + '</option>' + names.map((name) =>
241
360
  '<option value="' + esc(name) + '">' + esc(name) + "</option>").join("");
242
361
  if (oldModel && names.includes(oldModel)) ms.value = oldModel;
362
+ cmpDirectBtnSync(); // 工具行 pill 文字跟随真源
243
363
  }
244
364
 
245
365
  function recommendTaskType(goal) {
@@ -1028,9 +1148,10 @@ async function poll() {
1028
1148
  S.providers = models.providers; S.bindings = models.bindings;
1029
1149
  S.modelCatalog = models.catalog || [];
1030
1150
  S.sourceNames = models.source_names || S.sourceNames || {};
1031
- if ($("f-direct-model") && !$('f-direct-model').classList.contains("hidden")) {
1032
- renderDirectModelPicker();
1033
- }
1151
+ // /api/models 到手即重建对话模型选项(无条件):旧逻辑只在「更多选项」
1152
+ // 旧块可见时重建——旧块隐藏后选项永远只有「自动推荐」,厂商写值全部
1153
+ // 静默失效(2026-09-21 用户实测「没法选模型」根因)
1154
+ renderDirectModelPicker();
1034
1155
  // 供应商集合/启停/密钥变了才重拉编排者:否则「生效中」状态点会在停用厂商后失真
1035
1156
  const provSig = JSON.stringify((S.providers || []).map((p) => [p.id, p.enabled, !!p.api_key]));
1036
1157
  if (provSig !== S.provSig) { S.provSig = provSig; loadOrchestrator(); }
@@ -6493,7 +6614,10 @@ async function renderChat(run, active) {
6493
6614
  let rel = typeof a === "string" ? a : ((a && (a.path || a.name)) || nm);
6494
6615
  rel = String(rel).replace(/\\/g, "/");
6495
6616
  if (!rel.startsWith("_attachments/")) rel = "_attachments/" + rel.split("/").pop();
6496
- 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 };
6497
6621
  }).filter(Boolean);
6498
6622
  return '<div class="chat-row me">' +
6499
6623
  '<div class="chat-bubble me">' +
@@ -6502,8 +6626,11 @@ async function renderChat(run, active) {
6502
6626
  esc(it.text || t("(仅附件)")) +
6503
6627
  (atts.length
6504
6628
  ? '<div class="chat-atts">' + atts.map((a) =>
6505
- '<span class="att-chip2 att-open" data-att="' + esc(a.rel) +
6506
- '" 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>"
6507
6634
  : "") +
6508
6635
  "</div></div>";
6509
6636
  }
@@ -6766,6 +6893,14 @@ async function chatUploadFiles(files) {
6766
6893
  const r = await api("/api/attachments", { method: "POST",
6767
6894
  body: JSON.stringify({ name: f.name, data: b64 }) });
6768
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
+ }
6769
6904
  } catch (e) { toast(t("附件上传失败:") + f.name + " — " + e.message, true); }
6770
6905
  }
6771
6906
  }
@@ -8978,11 +9113,12 @@ function mkCardHtml(p, section) {
8978
9113
  else if (p.installed) ops = '<span class="tag ok">' + t("已安装") + "</span>" +
8979
9114
  '<button class="ghost small" onclick="mkRemove(\'' + esc(p.id) + '\')">' + t("卸载") + "</button>";
8980
9115
  else ops = '<button class="primary small" onclick="mkInstall(\'' + esc(p.id) + '\')">' + t("安装") + "</button>";
8981
- // 已装插件卡片直接给启停开关(对齐 ZCode 插件管理形态:启停不用去经验库翻)
9116
+ // 已装插件卡片直接给启停开关(对齐 ZCode 插件管理形态:启停不用去经验库翻)。
9117
+ // 注意传 pack_id(user-哈希)而非市场 id——启停记账在 skills 用户包上
8982
9118
  const toggle = p.installed
8983
9119
  ? '<label class="switch" title="' + (p.enabled ? t("已启用") : t("已停用")) + '">' +
8984
9120
  '<input type="checkbox"' + (p.enabled ? " checked" : "") +
8985
- ' onchange="mkTogglePack(\'' + esc(p.id) + '\', this.checked)"></label>'
9121
+ ' onchange="mkTogglePack(\'' + esc(p.pack_id || p.id) + '\', this.checked)"></label>'
8986
9122
  : "";
8987
9123
  const sec = section ? '<span class="tag mk-sec">' + esc(section) + "</span>" : "";
8988
9124
  return '<div class="card mk-card"><div class="head">' +
@@ -9034,21 +9170,22 @@ function renderMarket() {
9034
9170
  (p.name || "").toLowerCase().indexOf(q) >= 0 || (p.desc || "").toLowerCase().indexOf(q) >= 0);
9035
9171
  const cnt = $("mk-count");
9036
9172
  if (cnt && mkActiveView() === "local") cnt.textContent = t("共 ") + items.length + t(" 个");
9037
- // 分区渲染(对齐 ZCode 插件管理形态):内置 / 已安装 / 可安装三组,
9038
- // 各组小标题+卡片;「全部」以外状态只出命中的那组
9173
+ // 分区渲染(对齐 ZCode 插件管理形态):内置 / 已安装 / 可安装三组。
9174
+ // #mk-grid 本身是 grid 容器,直接塞 h3 会把标题当格子排(样式乱)——
9175
+ // 分区模式给外层加 .mk-grouped 转块级,每组自带独立 .mk-grid。
9039
9176
  if (st === "all" && !cat && !q) {
9040
9177
  const builtin = items.filter((p) => p.builtin);
9041
9178
  const inst = items.filter((p) => !p.builtin && p.installed);
9042
9179
  const avail = items.filter((p) => !p.builtin && !p.installed);
9043
- const sec = (title, list) => list.length
9044
- ? '<h3 class="sec-title">' + title + " " + list.length + "</h3>" +
9045
- '<div class="mk-grid">' + list.map((p) => mkCardHtml(p, t(title))).join("") + "</div>"
9046
- : "";
9047
- grid.innerHTML =
9048
- sec(t("内置"), builtin) + sec(t("已安装"), inst) + sec(t("可安装"), avail) ||
9180
+ const sec = (title, list) => !list.length ? "" :
9181
+ '<h3 class="sec-title">' + esc(t(title)) + ' <span class="tag">' + list.length + "</span></h3>" +
9182
+ '<div class="mk-grid">' + list.map((p) => mkCardHtml(p)).join("") + "</div>";
9183
+ grid.classList.add("mk-grouped");
9184
+ grid.innerHTML = sec(t("内置"), builtin) + sec(t("已安装"), inst) + sec(t("可安装"), avail) ||
9049
9185
  '<div class="empty">' + t("没有符合条件插件") + "</div>";
9050
9186
  return;
9051
9187
  }
9188
+ grid.classList.remove("mk-grouped");
9052
9189
  grid.innerHTML = items.map((p) => mkCardHtml(p)).join("") || '<div class="empty">' + t("没有符合条件插件") + "</div>";
9053
9190
  }
9054
9191
 
@@ -11321,6 +11458,9 @@ document.addEventListener("DOMContentLoaded", () => {
11321
11458
  $("f-git-rev").addEventListener("change", renderGitHint);
11322
11459
  if ($("f-workdir").value) queueGitProbe(); // 回填的目录:进页面即探测代码版本
11323
11460
  $("f-type").addEventListener("change", onTypeChange);
11461
+ // 编排模式/思考程度 pill:选项变化同步短名(含语言切换后的 option 文本变化)
11462
+ $("f-mode").addEventListener("change", () => syncCmpSelFace("f-mode"));
11463
+ $("f-thinking").addEventListener("change", () => syncCmpSelFace("f-thinking"));
11324
11464
  // 类型菜单点外面收起(Escape 同收);按钮自身点击由 toggleTypeMenu 负责
11325
11465
  document.addEventListener("click", (e) => {
11326
11466
  const menu = $("type-menu"), btn = $("f-type-btn");
@@ -11346,6 +11486,7 @@ document.addEventListener("DOMContentLoaded", () => {
11346
11486
  $("f-thinking").addEventListener("change", refreshEstimate);
11347
11487
  $("f-rounds").addEventListener("change", refreshEstimate);
11348
11488
  $("f-direct-provider").addEventListener("change", renderDirectModelPicker);
11489
+ $("f-direct-model-name").addEventListener("change", cmpDirectBtnSync); // 更多选项里改模型也要同步 pill
11349
11490
  $("f-type").dispatchEvent(new Event("change"));
11350
11491
  cmpGreeting(); // 问候语按时段刷新(切语言/回任务页也会重算)
11351
11492
  $("btn-reload-catalog").addEventListener("click", async () => {
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 ",
@@ -2476,6 +2477,13 @@
2476
2477
  scope.querySelectorAll("[data-i18n-aria]").forEach((el) => {
2477
2478
  el.setAttribute("aria-label", t(el.dataset.i18nAria));
2478
2479
  });
2480
+ // 编排 pill 短名取自 option 文本,重译后要跟着换语言
2481
+ if (typeof window.syncCmpSelFace === "function") {
2482
+ ["f-mode", "f-thinking"].forEach((id) => window.syncCmpSelFace(id));
2483
+ }
2484
+ if (typeof window.cmpDirectBtnSync === "function") {
2485
+ window.cmpDirectBtnSync(); // 「自动推荐/推荐」等词也要换语言
2486
+ }
2479
2487
  }
2480
2488
 
2481
2489
  // 暴露
package/app/ui/index.html CHANGED
@@ -294,7 +294,37 @@
294
294
  <div class="att-strip">
295
295
  <button type="button" class="ghost small" id="btn-attach" title="添加附件:截图 / 文本 / Word·Excel·PPT / PDF 等(普通 8MB,Office 24MB),创建任务时落盘到工作目录 _attachments/;也可直接粘贴截图" data-i18n-title="添加附件:截图 / 文本 / Word·Excel·PPT / PDF 等(普通 8MB,Office 24MB),创建任务时落盘到工作目录 _attachments/;也可直接粘贴截图"><svg class="ico" aria-hidden="true"><use href="#i-paperclip"></use></svg><span data-i18n="附件">附件</span></button>
296
296
  </div>
297
+ <!-- 右组(参考主流输入框):编排/思考/对话模型偏好 + 发送。
298
+ pill 用透明原生 select 覆盖,点哪儿都弹原生下拉;短名由
299
+ syncCmpSelFace 同步。2026-09-21 用户拍板「整理到对话框里」。 -->
297
300
  <div class="cmp-send">
301
+ <span class="cmp-sel" data-i18n-title="编排模式" title="编排模式">
302
+ <select id="f-mode" class="cmp-sel-native" aria-label="编排模式" data-i18n-aria="编排模式">
303
+ <option data-i18n="自动(推荐):按任务类型与风险动态决定步骤" value="auto">自动(推荐):按任务类型与风险动态决定步骤</option>
304
+ <option data-i18n="快速:少步骤优先,先做确定性验收" value="fast">快速:少步骤优先,先做确定性验收</option>
305
+ <option data-i18n="专家:完整规划、多评审、失败修复与换将" value="expert">专家:完整规划、多评审、失败修复与换将</option>
306
+ <option data-i18n="手动:自行指定实现者与评审组" value="manual">手动:自行指定实现者与评审组</option>
307
+ </select>
308
+ <span class="cmp-sel-face" id="f-mode-face">自动</span>
309
+ <svg class="ico chev" aria-hidden="true"><use href="#i-chevron-r"></use></svg>
310
+ </span>
311
+ <span class="cmp-sel" data-i18n-title="思考程度" title="思考程度">
312
+ <select id="f-thinking" class="cmp-sel-native" aria-label="思考程度" data-i18n-aria="思考程度">
313
+ <option data-i18n="标准:平衡质量与速度" value="standard" selected>标准:平衡质量与速度</option>
314
+ <option data-i18n="自动:随任务难度调整" value="auto">自动:随任务难度调整</option>
315
+ <option data-i18n="快速:少推理、优先响应" value="low">快速:少推理、优先响应</option>
316
+ <option data-i18n="深度:更多推理与检查" value="high">深度:更多推理与检查</option>
317
+ </select>
318
+ <span class="cmp-sel-face" id="f-thinking-face">标准</span>
319
+ <svg class="ico chev" aria-hidden="true"><use href="#i-chevron-r"></use></svg>
320
+ </span>
321
+ <!-- 厂商/模型 pill(仅「直接执行」类型显示):点开厂商→模型两级
322
+ 钻取菜单,选定写回隐藏 select 真源 -->
323
+ <span class="cmp-sel hidden" id="f-direct-wrap" data-i18n-title="对话模型(可选)" title="对话模型(可选)">
324
+ <button type="button" class="cmp-sel-face cmp-sel-btn" id="f-direct-btn" onclick="toggleModelMenu()" aria-haspopup="menu" aria-controls="cmp-model-menu">自动推荐</button>
325
+ <svg class="ico chev" aria-hidden="true"><use href="#i-chevron-r"></use></svg>
326
+ <div id="cmp-model-menu" class="type-menu hidden" role="menu" aria-label="对话模型" data-i18n-aria="对话模型"></div>
327
+ </span>
298
328
  <span id="create-msg" class="msg"></span>
299
329
  <button id="btn-create" class="primary round" title="创建并运行" data-i18n-title="创建并运行" aria-label="创建并运行" data-i18n-aria="创建并运行"><svg class="ico" aria-hidden="true"><use href="#i-arrow-up"></use></svg></button>
300
330
  </div>
@@ -305,31 +335,6 @@
305
335
  <div id="cmp-quick" class="cmp-quick" role="group" aria-label="常用类型" data-i18n-aria="常用类型"></div>
306
336
  <!-- 开跑前成本预估(refreshEstimate 填充;无同类样本时保持隐藏) -->
307
337
  <p class="hint cmp-estimate hidden" id="cmp-estimate"></p>
308
- <!-- 编排偏好贴输入框(用户拍板「放上来」:不再折叠进更多选项) -->
309
- <div class="cmp-prefs" id="cmp-prefs">
310
- <div class="field">
311
- <label data-i18n="编排模式">编排模式</label>
312
- <select id="f-mode">
313
- <option data-i18n="自动(推荐):按任务类型与风险动态决定步骤" value="auto">自动(推荐):按任务类型与风险动态决定步骤</option>
314
- <option data-i18n="快速:少步骤优先,先做确定性验收" value="fast">快速:少步骤优先,先做确定性验收</option>
315
- <option data-i18n="专家:完整规划、多评审、失败修复与换将" value="expert">专家:完整规划、多评审、失败修复与换将</option>
316
- <option data-i18n="手动:自行指定实现者与评审组" value="manual">手动:自行指定实现者与评审组</option>
317
- </select>
318
- </div>
319
- <div class="field">
320
- <label data-i18n="思考程度">思考程度</label>
321
- <select id="f-thinking">
322
- <option data-i18n="标准:平衡质量与速度" value="standard" selected>标准:平衡质量与速度</option>
323
- <option data-i18n="自动:随任务难度调整" value="auto">自动:随任务难度调整</option>
324
- <option data-i18n="快速:少推理、优先响应" value="low">快速:少推理、优先响应</option>
325
- <option data-i18n="深度:更多推理与检查" value="high">深度:更多推理与检查</option>
326
- </select>
327
- </div>
328
- <div id="f-direct-model" class="field hidden"><label data-i18n="对话模型(可选)">对话模型(可选)</label><div class="grid-2">
329
- <select id="f-direct-provider" aria-label="对话厂商" data-i18n-aria="对话厂商"><option data-i18n="自动推荐">自动推荐</option></select>
330
- <select id="f-direct-model-name" aria-label="对话模型" data-i18n-aria="对话模型"><option data-i18n="随厂商推荐">随厂商推荐</option></select>
331
- </div><p class="hint" data-i18n="只对本次对话生效,不会修改全局绑定;指定渠道不可用时会明确报错。">只对本次对话生效,不会修改全局绑定;指定渠道不可用时会明确报错。</p></div>
332
- </div>
333
338
  <!-- 需求拷问采访卡(renderClarify 渲染;goal 模糊时出现,跳过=直接创建) -->
334
339
  <div id="clarify-box" class="clarify-box hidden"></div>
335
340
  <div class="field hidden" id="row-resume-session">
@@ -341,6 +346,11 @@
341
346
  <details id="f-advanced">
342
347
  <summary data-i18n="更多选项">更多选项</summary>
343
348
  <div class="adv-body">
349
+ <!-- 对话模型(可选)是低频高级项:只对「直接执行」类型生效,收在这 -->
350
+ <div id="f-direct-model" class="field hidden"><label data-i18n="对话模型(可选)">对话模型(可选)</label><div class="grid-2">
351
+ <select id="f-direct-provider" aria-label="对话厂商" data-i18n-aria="对话厂商"><option data-i18n="自动推荐">自动推荐</option></select>
352
+ <select id="f-direct-model-name" aria-label="对话模型" data-i18n-aria="对话模型"><option data-i18n="随厂商推荐">随厂商推荐</option></select>
353
+ </div><p class="hint" data-i18n="只对本次对话生效,不会修改全局绑定;指定渠道不可用时会明确报错。">只对本次对话生效,不会修改全局绑定;指定渠道不可用时会明确报错。</p></div>
344
354
  <div class="grid-2">
345
355
  <div class="field">
346
356
  <label data-i18n="继续会话">继续会话</label>
package/app/ui/style.css CHANGED
@@ -5616,18 +5616,24 @@ button.request-busy > * { opacity: .18; }
5616
5616
  .ch-ev.ok { border-left-color: var(--ok); color: inherit; }
5617
5617
  .ch-ev.bad { border-left-color: var(--bad); color: inherit; }
5618
5618
 
5619
- /* composer 内编排偏好行:编排模式/思考程度/对话模型贴着输入框(2026-09-21
5620
- * 用户拍板「放上来」,不再折叠进「更多选项」)。字段横向紧凑排布,窄屏换行。 */
5621
- .cmp-prefs { display: flex; flex-wrap: wrap; gap: 8px 20px; padding: 6px 2px 2px; }
5622
- .cmp-prefs > .field { display: flex; align-items: center; gap: 8px; flex: 1 1 260px;
5623
- min-width: 0; margin: 0; }
5624
- .cmp-prefs > .field > label { white-space: nowrap; flex: none; margin: 0;
5625
- font-size: 12.5px; color: var(--muted); }
5626
- .cmp-prefs > .field > select { flex: 1 1 auto; min-width: 0; width: auto; }
5627
- /* 对话模型(可选)默认整行:label + 双下拉 + 说明文字 */
5628
- .cmp-prefs > #f-direct-model { flex: 1 1 100%; align-items: center; }
5629
- .cmp-prefs > #f-direct-model > .grid-2 { flex: 1 1 auto; min-width: 0; }
5630
- .cmp-prefs > #f-direct-model > .hint { flex: 1 1 100%; margin: 0; }
5619
+ /* composer 工具行「编排模式/思考程度」pill(2026-09-21 用户拍板「整理到对话
5620
+ * 框里面去」):透明原生 select 覆盖整颗 pill,点哪儿都是原生下拉;face 只显示
5621
+ * 选项短前缀(syncCmpSelFace 同步),视觉与「直接执行」类型 pill 同一语汇。 */
5622
+ .cmp-sel { position: relative; display: inline-flex; align-items: center; gap: 5px;
5623
+ padding: 5px 11px; border: 1px solid var(--border); border-radius: 999px;
5624
+ font-size: 12.5px; color: var(--muted); background: transparent;
5625
+ white-space: nowrap; user-select: none; }
5626
+ .cmp-sel:hover { border-color: var(--border-strong); color: var(--text); }
5627
+ .cmp-sel-face { max-width: 88px; overflow: hidden; text-overflow: ellipsis; }
5628
+ .cmp-sel .chev { width: 13px; height: 13px; transform: rotate(90deg); opacity: .65; flex: none; }
5629
+ .cmp-sel-native { position: absolute; inset: 0; width: 100%; height: 100%;
5630
+ opacity: 0; cursor: pointer; border: none; margin: 0; }
5631
+ .cmp-sel-btn { appearance: none; background: none; border: none; padding: 0;
5632
+ font: inherit; color: inherit; cursor: pointer; }
5633
+ /* 「厂商/模型」组合名比单档选项长:放宽截断(其他 pill 保持 88px 短名) */
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; }
5631
5637
 
5632
5638
  /* 插件市场:内置/已安装/可安装分区标题与已装启停开关(对齐 ZCode 形态) */
5633
5639
  #mk-grid .sec-title { margin: 14px 0 6px; }
@@ -5640,3 +5646,35 @@ button.request-busy > * { opacity: .18; }
5640
5646
  .mk-card .switch input::after { content: ""; position: absolute; top: 2px; left: 2px;
5641
5647
  width: 15px; height: 15px; border-radius: 50%; background: #fff; transition: left .15s; }
5642
5648
  .mk-card .switch input:checked::after { left: 17px; }
5649
+
5650
+ /* 插件市场分区模式:外层转块级,标题不当格子排(此前 h3 塞进 grid 导致
5651
+ 标题/卡片错位——用户截图反馈「样式乱了」);每组自带独立 grid。 */
5652
+ #mk-grid.mk-grouped { display: block; }
5653
+ #mk-grid.mk-grouped .mk-grid { display: grid; gap: 12px;
5654
+ grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); }
5655
+ #mk-grid.mk-grouped .sec-title { margin: 16px 0 8px; }
5656
+
5657
+ /* 首页 composer 图片附件缩略图:与任务页同款 88×88 卡(此前样式只写在
5658
+ * #sub-tasks 作用域,首页 img 按原始尺寸渲染直接压住输入文字——2026-09-21
5659
+ * 用户实测「有遮挡」)。删除钮右上角悬浮。 */
5660
+ .att-chip.att-image { position: relative; width: 88px; height: 88px; max-width: none;
5661
+ padding: 0; overflow: visible; border-radius: 12px; background: var(--panel2);
5662
+ border-color: var(--border); box-shadow: 0 2px 10px color-mix(in srgb, #000 10%, transparent); }
5663
+ .att-chip.att-image img { display: block; width: 100%; height: 100%; object-fit: cover;
5664
+ border-radius: inherit; background: var(--panel2); }
5665
+ .att-chip.att-image .att-image-name { position: absolute; width: 1px; height: 1px;
5666
+ padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0);
5667
+ white-space: nowrap; border: 0; }
5668
+ .att-chip.att-image .ax { position: absolute; z-index: 2; top: -6px; right: -6px;
5669
+ display: inline-flex; align-items: center; justify-content: center; width: 20px;
5670
+ height: 20px; padding: 0; border: 1px solid var(--panel); border-radius: 50%;
5671
+ background: var(--panel); color: var(--muted); }
5672
+ .att-chip.att-image .ax:hover { background: var(--bad); color: #fff; }
5673
+ .att-chip.att-image .ax .ico { width: 11px; height: 11px; }
5674
+
5675
+ /* 对话气泡图片附件缩略图(2026-09-21 用户反馈:图片只显示文件名要点开才见):
5676
+ * 96×96 圆角卡,点击仍走 att-open 弹大图 */
5677
+ .chat-img { display: inline-block; cursor: zoom-in; }
5678
+ .chat-img img { display: block; width: 96px; height: 96px; object-fit: cover;
5679
+ border-radius: 10px; border: 1px solid var(--border); background: var(--panel2); }
5680
+ .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.36",
3
+ "version": "0.1.38",
4
4
  "description": "CodeBee · 多智能体编排台 — 统一编排本机 AI 编码 CLI(Codex / Claude Code / QwenCode / OpenCode / Aider…):目标→拆解→路由→执行→验证→跨厂商评审→修复→报告。纯 Python 标准库实现,本地 Web UI。",
5
5
  "license": "MIT",
6
6
  "bin": {