codebee 0.1.87 → 0.1.89

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,16 @@ README 元数据带回,供老版本在「发现新版本」时展示新版更
6
6
 
7
7
  ## 未发布
8
8
 
9
+ ## v0.1.89(2026-10-07)
10
+
11
+ - **连载起草「相关历史章节推荐」**:起草前情注入新增历史章回看指针——用连载追踪里逐章持久化的伏笔/人物/事实记录,与本章大纲要点做纯本地重叠计分(零模型调用、零额外存储),按命中行数推荐最相关的至多 3 个历史章摘录供起草时回看;近 2 章窗口外的伏笔回收、人物回场不再只靠账本督促兜底;零匹配零输出(老书无感)。
12
+ - **经验库存量去重**:新增教训并类管理通道(历史标题与正文留痕、召回证据并入主条不缩水),存量近重复教训 8→3 条并类、1 条归位分类,经验库 73→68 条——重复教训不再稀释召回排序。
13
+
14
+ ## v0.1.88(2026-10-06)
15
+
16
+ - **封面生成前提示词预览确认**:「生成封面」先只读展示将使用的图像提示词(新增 GET /api/tasks/{id}/cover/prompt,不触发任何模型/图像调用),确认后才走付费生成链——防误触花钱、封面元素所见即所得;取消零请求。
17
+ - **调研沉淀**:09-13 时班全类型竞品雷达(批2 学习记忆/批5 检索知识/批6 框架生态)与封面预览 12 项回归测试入库。
18
+
9
19
  ## v0.1.87(2026-10-06)
10
20
 
11
21
  - **全类型注册表对账**:任务类型注册表(18 型)与调度维度表、跑前耗时预估表逐一对账——修复「禅道工单」改型「缺陷复盘」后调度维度漏配(长期靠兜底回落)与一处死映射;补齐演示文稿/标书/缺陷复盘三型的跑前耗时预估(此前一律回落统一档,预估失真)。日后加型漏配、删型留尸会在测试先红。
package/README.md CHANGED
@@ -33,11 +33,10 @@ Trae Agent、OpenClaw……),提供
33
33
  ---
34
34
 
35
35
  <!-- relnotes:start -->
36
- ### 最新版更新内容(v0.1.87)
36
+ ### 最新版更新内容(v0.1.89)
37
37
 
38
- - 全类型注册表对账:18 型任务类型与调度维度/跑前耗时预估逐一对账,修复缺陷复盘维度漏配与演示文稿/标书预估失真
39
- - 英文界面 7 处残件词条补齐(本地插件、报告导出等不再回落中文)
40
- - 插件市场外部目录提示行补 CocoLoop,六源与实现一致
38
+ - 连载起草「相关历史章节推荐」:起草前情自动带历史章回看指针(伏笔回收/人物回场),零模型调用零额外开销
39
+ - 经验库存量去重:近重复教训并类留痕,召回排序证据不缩水
41
40
  <!-- relnotes:end -->
42
41
 
43
42
 
@@ -2587,6 +2587,55 @@ def _serial_shrunk_block(lessons, bible, kb_block, budget=12000):
2587
2587
  return "\n\n".join(p for p in (sk2, bible2, kb_block) if p), note
2588
2588
 
2589
2589
 
2590
+ _RELATED_ROW_CHARS = 80 # 单行摘录截断(记录行本体可达 1000 字,注入面只留指针)
2591
+ _RELATED_ROWS_PER_CH = 3 # 每章最多摘录的行数
2592
+ _RELATED_MIN_GRAMS = 2 # 单行命中门槛:bigram 重叠数(1 个重叠噪音过高)
2593
+
2594
+
2595
+ def _related_chapters_note(tracking_state, ch, current=0, max_n=3):
2596
+ """连载起草的「相关历史章节推荐」(纯本地重叠计分,零 LLM 调用零存储写入)。
2597
+
2598
+ 前情注入只看近 2 章结尾+findings 摘要,中期记忆(伏笔回收/人物回场)此前
2599
+ 只有账本督促兜底;story_tracking 已逐章持久化 facts/characters/foreshadowing,
2600
+ 这里拿本章大纲要点(beats/hook/highlight)与各历史章记录行做字符 bigram
2601
+ 重叠计分(复用 skills._text_bigrams,口径与经验库召回一致),按命中行数挑
2602
+ 最相关的 max_n 章输出「第 N 章:相关行摘录」指针。零匹配/无状态返回空串
2603
+ (老书零噪音);current 传当前全书章号时只看更早的章(重跑已提交章不跟
2604
+ 自己的记录自证)。"""
2605
+ ch = ch or {}
2606
+ probe = skills._text_bigrams(" ".join(
2607
+ str(ch.get(k) or "") for k in ("beats", "hook", "highlight")))
2608
+ if not probe:
2609
+ return ""
2610
+ scored = []
2611
+ for rec in (tracking_state or {}).get("chapters") or []:
2612
+ try:
2613
+ no = int(rec.get("chapter") or 0)
2614
+ except (TypeError, ValueError):
2615
+ continue
2616
+ if no < 1 or (current and no >= current):
2617
+ continue
2618
+ rows = [str((x or {}).get("text") or "") for x in rec.get("foreshadowing") or []]
2619
+ rows += [str(x) for x in rec.get("characters") or []]
2620
+ rows += [str(x) for x in rec.get("facts") or []]
2621
+ hits = []
2622
+ for row in rows:
2623
+ row = row.strip()
2624
+ if row and len(probe & skills._text_bigrams(row)) >= _RELATED_MIN_GRAMS:
2625
+ hits.append(row[:_RELATED_ROW_CHARS]
2626
+ + ("…" if len(row) > _RELATED_ROW_CHARS else ""))
2627
+ if hits:
2628
+ scored.append((len(hits), no, hits))
2629
+ scored.sort(key=lambda x: (-x[0], x[1]))
2630
+ take = scored[:max(0, int(max_n or 0))]
2631
+ if not take:
2632
+ return ""
2633
+ lines = ["### 相关历史章节(按本章大纲要点重叠检出,起草前可回看)"]
2634
+ for _, no, hits in take:
2635
+ lines.append("- 第 %d 章:%s" % (no, ";".join(hits[:_RELATED_ROWS_PER_CH])))
2636
+ return "\n".join(lines)
2637
+
2638
+
2590
2639
  def _critic_lens(critics, agent):
2591
2640
  """该评审的专属视角;单评审/手动指定时不播种(无从轮换,也别稀释注意力)。"""
2592
2641
  try:
@@ -3411,6 +3460,16 @@ def _run_serial_review(run, task, agents, ev, stats, mode, critics, impl, route,
3411
3460
  prev += "\n\n## 此前章节发现摘要\n" + fd_txt[:3000]
3412
3461
  except Exception:
3413
3462
  pass
3463
+ # 相关历史章节推荐(story_tracking 逐章 facts/characters/foreshadowing
3464
+ # 与本章大纲要点的纯本地重叠计分):近 2 章窗口外的伏笔回收/人物回场
3465
+ # 给显式指针,经 prev 注入路径自然随降级块走(不新增预算口子);
3466
+ # 零匹配零输出(老书零噪音),失败静默。
3467
+ try:
3468
+ rel = _related_chapters_note(tracking_state, ch, current=i)
3469
+ if rel:
3470
+ prev += "\n\n" + rel
3471
+ except Exception:
3472
+ pass
3414
3473
 
3415
3474
  # 评审-修订(每章至多 1 轮修订)
3416
3475
  rounds_used = 1
@@ -513,6 +513,63 @@ def lesson_op(lesson_id, op):
513
513
  return None
514
514
 
515
515
 
516
+ def merge_lessons(primary_id, duplicate_ids):
517
+ """把近重复教训并为主条(存量聚簇的管理通道;upsert_lesson 合并路径的管理版)。
518
+
519
+ 自动去重只拦沉淀时的近似题(bigram 包含度 ≥0.8),语义近但措辞散的存量
520
+ 聚簇没有并类通道。语义=upsert 合并语义的延伸:被并条标题进主条
521
+ merged_titles(去重封顶 8,溯源+变体检索可见)、正文进 revisions 留痕
522
+ (封顶 5,带 merged_from 指明来源条),注入/结局证据计数(hits/lost/
523
+ useless/won/seen)并入主条——召回排序的 karma 证据不因合并缩水;正文与
524
+ 分类以主条为准(不批量重写内容)。返回错误说明或 None。
525
+ """
526
+ dup_ids = []
527
+ for x in (duplicate_ids or []):
528
+ did = str(x or "").strip()
529
+ if did and did not in dup_ids:
530
+ dup_ids.append(did)
531
+ if not dup_ids:
532
+ return "缺少被并条目"
533
+ with _LOCK:
534
+ data = _load()
535
+ items = data.get("lessons") or []
536
+ by_id = {}
537
+ for x in items:
538
+ by_id[x.get("id")] = x
539
+ primary = by_id.get(primary_id)
540
+ if not primary:
541
+ return "主条不存在"
542
+ if primary_id in dup_ids:
543
+ return "主条不能同时是被并条目"
544
+ dups = []
545
+ for did in dup_ids:
546
+ d = by_id.get(did)
547
+ if not d:
548
+ return "被并条目不存在 " + did
549
+ dups.append(d)
550
+ revs = primary.setdefault("revisions", [])
551
+ titles = primary.setdefault("merged_titles", [])
552
+ primary_title = str(primary.get("title") or "")
553
+ for d in dups:
554
+ content = str(d.get("content") or "")
555
+ if content:
556
+ revs.append(revisions.revision_record(
557
+ "skrev", content, source=d.get("source") or "",
558
+ extra={"content": content, "merged_from": d.get("id")}))
559
+ title = str(d.get("title") or "")
560
+ if title and title != primary_title and title not in titles:
561
+ titles.append(title)
562
+ for k in ("hits", "lost", "useless", "won", "seen"):
563
+ primary[k] = int(primary.get(k) or 0) + int(d.get(k) or 0)
564
+ primary["revisions"] = revs[-5:]
565
+ primary["merged_titles"] = titles[-8:]
566
+ primary["updated_at"] = _now()
567
+ drop = {d.get("id") for d in dups}
568
+ data["lessons"] = [x for x in items if x.get("id") not in drop]
569
+ _save(data)
570
+ return None
571
+
572
+
516
573
  def pack_op(pack_id, op):
517
574
  """启用/停用包(内置与用户自建均可停用;用户包不可通过此接口删除,删文件即可)。"""
518
575
  if op not in ("enable", "disable"):
package/app/main.py CHANGED
@@ -623,6 +623,17 @@ class Handler(BaseHTTPRequestHandler):
623
623
  p: store.chain_book_meta(task, p)
624
624
  for p in ("fanqie", "qimao")
625
625
  if store.chain_book_meta(task, p)}})
626
+ m = re.match(r"^/api/tasks/([^/]+)/cover/prompt$", path)
627
+ if m:
628
+ # 封面提示词预览(只读,drama-skills 借鉴③「先预览确认再生产」):
629
+ # 生成前先回 _cover_prompt 结果给前端展示,用户确认后再走
630
+ # POST /cover 付费链。纯只读,不触发任何模型/图像调用。
631
+ task = store.get_task(m.group(1))
632
+ if not task:
633
+ return self._json(404, {"error": "not found"})
634
+ from core import covergen
635
+ return self._json(200, {"ok": True,
636
+ "prompt": covergen._cover_prompt(task)})
626
637
  if path == "/api/publish":
627
638
  # 一键发布:平台连接状态 + 最近台账(详情页发布面板)
628
639
  from core.publish import manager as pub
package/app/ui/app.js CHANGED
@@ -10,7 +10,7 @@ function codebeeDocumentTitle(lang) {
10
10
  document.title = codebeeDocumentTitle((localStorage.getItem("orch.lang") || "zh").toLowerCase());
11
11
 
12
12
  const $ = (id) => document.getElementById(id);
13
- const S = { state: null, catalog: null, catSig: "", providers: null, bindings: null, modelsSig: "", bindSig: "", tab: "tasks", mainPage: "", /* 主栏正显示的全局页("" = 新建任务表单);左栏是任务树还是设置导航看 body.settings-mode */ detailRunId: null, pollTimer: null, showArchived: false, /* 会话内开关:每次加载默认隐藏已归档、图标不选中(不持久化,见 btn-side-arch) */ selProvs: {}, selModels: {}, selRuns: {}, provStatus: "enabled", mlistStatus: "enabled", /* 供应商/模型列表状态过滤:默认只看启用,停用的点对应 chip 才现身 */ 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, rdHiveFirst: false, _rdCtx: {}, taskContracts: {}, lastPrefs: null, prefsApplied: false, sideUsage: null, sideUsageAt: 0, ovUsage: null, ovDays: undefined };
13
+ const S = { state: null, catalog: null, catSig: "", providers: null, bindings: null, modelsSig: "", bindSig: "", tab: "tasks", mainPage: "", /* 主栏正显示的全局页("" = 新建任务表单);左栏是任务树还是设置导航看 body.settings-mode */ detailRunId: null, pollTimer: null, showArchived: false, /* 会话内开关:每次加载默认隐藏已归档、图标不选中(不持久化,见 btn-side-arch) */ selProvs: {}, selModels: {}, selRuns: {}, provStatus: "enabled", mlistStatus: "enabled", /* 供应商/模型列表状态过滤:默认只看启用,停用的点对应 chip 才现身 */ 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, rdHiveFirst: false, _rdCtx: {}, taskContracts: {}, lastPrefs: null, prefsApplied: false, sideUsage: null, sideUsageAt: 0, ovUsage: null, ovDays: undefined, coverPrompt: null };
14
14
 
15
15
  /* ---------------------------------------------------------- 内置浏览器工作区 */
16
16
  const BROWSER_STORAGE_KEY = "orch.browser.workspace.v1";
@@ -5812,10 +5812,24 @@ function renderBookMetaPanel(task) {
5812
5812
  cgBody = '<div class="bm-empty">' + esc(t("生成竖版封面插画,产出 cover.png")) + "</div>";
5813
5813
  }
5814
5814
  const cgBusy = taskBusy || cgSt === "running";
5815
+ // 生成前提示词预览确认(drama-skills 借鉴③「先预览确认再生产」):点
5816
+ // 「生成封面」先只读拉提示词展示,确认后才走 coverGen 的 POST 付费链;
5817
+ // S.coverPrompt 让预览态跨轮询重绘保持(同 bmRenaming 模式)
5818
+ // 排除 running(生成中不出确认钮)与 done(已出图预览无意义,挂回 done 卡误导)
5819
+ const cgPrev = (S.coverPrompt && S.coverPrompt.taskId === task.id
5820
+ && cgSt !== "running" && cgSt !== "done") ? S.coverPrompt : null;
5815
5821
  const cgAction = cgSt === "running"
5816
5822
  ? '<button class="ghost" disabled><svg class="ico spin" aria-hidden="true"><use href="#i-refresh"/></svg>' + t("生成中…") + "</button>"
5817
- : '<button class="primary" onclick="coverGen(\'' + esc(task.id) + '\')">' +
5818
- (cgSt === "failed" ? t("重试") : t("生成封面")) + "</button>";
5823
+ : cgPrev
5824
+ ? '<button class="primary" onclick="coverConfirm(\'' + jsq(task.id) + '\')">' + t("确认生成") + "</button>" +
5825
+ '<button class="ghost" onclick="coverCancel(\'' + jsq(task.id) + '\')">' + t("取消") + "</button>"
5826
+ : '<button class="primary" onclick="coverPreview(\'' + jsq(task.id) + '\')">' +
5827
+ (cgSt === "failed" ? t("重试") : t("生成封面")) + "</button>";
5828
+ if (cgPrev) {
5829
+ cgBody += '<div class="bm-cover-preview"><div class="bm-cover-preview-hint">' +
5830
+ esc(t("生成前请确认图像提示词(确认后才调用图像接口):")) + "</div>" +
5831
+ '<div class="bm-cover-preview-text">' + esc(cgPrev.prompt || "") + "</div></div>";
5832
+ }
5819
5833
  html += '<div class="bm-cards"><div class="bm-card st-' + (cgSt || "new") + '">' +
5820
5834
  '<div class="bm-card-head"><span class="bm-plat-name"><svg class="ico" aria-hidden="true"><use href="#i-book-open"/></svg>' + esc(t("封面图")) + "</span>" +
5821
5835
  bmStatusChip(cgSt) + '<span class="flex1"></span>' + cgAction + "</div>" + cgBody + "</div></div>";
@@ -5880,6 +5894,34 @@ window.coverGen = async function (taskId) {
5880
5894
  } catch (e) { toast(t("封面生成失败:") + e.message, true); }
5881
5895
  };
5882
5896
 
5897
+ /* 生成前提示词预览(drama-skills 借鉴③「先预览确认再生产」):只读拉取
5898
+ * 后端 _cover_prompt 结果展示,不发图像请求;「确认生成」才走既有 coverGen
5899
+ * 的 POST /cover 付费链,「取消」只清预览态重绘、零请求。
5900
+ * 三个处理器都作废详情签名再 render:S.coverPrompt 不在 drawTaskDetail/
5901
+ * renderRunDetail 的数据签名里,服务端数据没变时守卫会整帧跳过重绘
5902
+ * (bmRename 同陷阱,见其 :5877 注释)——不作废则预览框不上屏、取消后残留。 */
5903
+ function coverInvalidateDetail() {
5904
+ S.taskSig = "";
5905
+ S.runDetailSig = "";
5906
+ render();
5907
+ }
5908
+ window.coverPreview = async function (taskId) {
5909
+ try {
5910
+ const r = await api("/api/tasks/" + encodeURIComponent(taskId) + "/cover/prompt");
5911
+ S.coverPrompt = { taskId: taskId, prompt: r.prompt || "" };
5912
+ coverInvalidateDetail();
5913
+ } catch (e) { toast(t("封面提示词获取失败:") + e.message, true); }
5914
+ };
5915
+ window.coverConfirm = function (taskId) {
5916
+ S.coverPrompt = null;
5917
+ coverInvalidateDetail(); // 先归位成单按钮再发 POST,失败时界面不留旧预览框
5918
+ window.coverGen(taskId);
5919
+ };
5920
+ window.coverCancel = function (taskId) {
5921
+ if (S.coverPrompt && S.coverPrompt.taskId === taskId) S.coverPrompt = null;
5922
+ coverInvalidateDetail();
5923
+ };
5924
+
5883
5925
  window.bmCopyBtn = function (btn) {
5884
5926
  // 只在真实复制成功后报「已复制」——剪贴板不可用时 fallbackCopy 会弹失败提示
5885
5927
  copyText(btn.dataset.text || "", () => toast(t("已复制:") + (btn.dataset.label || "")));
package/app/ui/i18n.js CHANGED
@@ -2134,6 +2134,9 @@
2134
2134
  "生成竖版封面插画,产出 cover.png": "Generate a portrait cover illustration → cover.png",
2135
2135
  "封面生成失败:": "Cover generation failed: ",
2136
2136
  "已开始生成封面,完成后这里会自动更新": "Cover generation started — this panel updates automatically when done",
2137
+ "确认生成": "Confirm & generate",
2138
+ "生成前请确认图像提示词(确认后才调用图像接口):": "Confirm the image prompt before generating (the image API is only called after confirmation):",
2139
+ "封面提示词获取失败:": "Failed to fetch cover prompt: ",
2137
2140
  "已开始生成,完成后这里会自动更新": "Generation started — this panel updates automatically when done",
2138
2141
  "来源:": "Source: ",
2139
2142
  "(待补充)": "(to fill)",
package/app/ui/style.css CHANGED
@@ -7084,3 +7084,15 @@ main.browser-page-active #page-settings > #sub-browser .browser-shell {
7084
7084
  .foot-btn.kill { color: var(--bad); }
7085
7085
  .foot-btn.kill:hover { color: #fff; background: var(--bad); }
7086
7086
  .foot-btn.kill.busy { opacity: .5; pointer-events: none; }
7087
+
7088
+ /* 封面提示词预览框(生成前确认,drama-skills 借鉴③「先预览确认再生产」;
7089
+ * 追加尾防并行重排):面板内换行展示将使用的提示词,限高可滚动,取消不落任何请求 */
7090
+ .bm-cover-preview {
7091
+ margin: 0 18px 14px; border: 1px solid var(--border); border-radius: 10px;
7092
+ background: var(--panel); padding: 10px 12px;
7093
+ }
7094
+ .bm-cover-preview-hint { font-size: 12px; color: var(--muted); margin-bottom: 6px; }
7095
+ .bm-cover-preview-text {
7096
+ font-size: 12.5px; line-height: 1.65; white-space: pre-wrap; word-break: break-word;
7097
+ max-height: 180px; overflow: auto; color: var(--text);
7098
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codebee",
3
- "version": "0.1.87",
3
+ "version": "0.1.89",
4
4
  "description": "CodeBee · 多智能体编排台 — 统一编排本机 AI 编码 CLI(Codex / Claude Code / QwenCode / OpenCode / Aider…):目标→拆解→路由→执行→验证→跨厂商评审→修复→报告。纯 Python 标准库实现,本地 Web UI。",
5
5
  "license": "MIT",
6
6
  "bin": {