codebee 0.1.100 → 0.1.101

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
@@ -4,6 +4,14 @@ CodeBee 的用户可感知变更记录。发布新版时:最上面加一节,
4
4
  `<!-- relnotes:start -->…<!-- relnotes:end -->` 段(那段会被 `npm view` 的
5
5
  README 元数据带回,供老版本在「发现新版本」时展示新版更新内容)。
6
6
 
7
+ ## v0.1.101(2026-10-10)
8
+
9
+ - **重写未达标章带上旧账(连载)**:上一轮评审的 major 意见(章节点名+全书级)随重写提示词下发(`__REDO_ISSUES__` 渲染「上一轮评审未达标原因」块),被 major 点名的过线章一并回炉——总判定卡 major 时只重写未过线章消不掉账的空转实案修复,作者第一遍就知道往哪改
10
+ - **评审口径事前下发作者面(连载)**:单章评审清单、全局关注点、签约专项维度与评审面同源提取(单一真源常量装配,两边永不脱节),经 `__REVIEW_RUBRIC__` 注入起草/修订/打磨提示词——作者开写前就知道按什么标准被审;评分机制本体(分值/阈值)刻意不下发
11
+ - **插件安装失败回滚修复**:回滚标记提前绑定+残缺目标自愈重装;此前标记未绑定把人话错误炸成 500 且跳过残留清理(Mac 装 Auto Retry 撞 429 永久撞车实案)
12
+ - **全局兜底错误行带崩溃现场**:任务异常时 traceback 尾帧(文件:行号:函数)追加进错误字段,UI 错误行直接说清死在哪、不必翻 run 目录日志;同因止损签名按崩溃位置区分,代码升级行号漂移自动允许再试一轮
13
+ - **代码块长行自动换行缺省改为开**:运行详情对话里长 JSON/代码行自动折行,不再撑横向滚动条;设置页「长行自动换行」仍可显式关闭
14
+
7
15
  ## v0.1.100(2026-10-10)
8
16
 
9
17
  - **ACP 智能体接入(Agent Client Protocol v1)**:设置页新增「ACP Backend Profiles」,可把遵循 ACP v1 的本地/SSH stdio Agent 登记为执行后端(名称/启动命令/参数/工作区),登记后进入智能体注册表、可被编排路由选用;密钥只保存在本机、读取视图始终脱敏
package/README.md CHANGED
@@ -25,11 +25,13 @@ Trae Agent、OpenClaw……),提供
25
25
  ---
26
26
 
27
27
  <!-- relnotes:start -->
28
- ### 最新版更新内容(v0.1.100)
28
+ ### 最新版更新内容(v0.1.101)
29
29
 
30
- - 新增 ACP 智能体接入:设置页登记 Agent Client Protocol v1 的本地/SSH stdio Agent,即可作为执行后端参与编排(密钥只存本机、读取脱敏)
31
- - 新增命令执行治理:restricted 档逐条等待批准 + 脱敏审计,默认 legacy 行为不变
32
- - 插件市场与工作台 git 传输抖动统一 HTTP/1.1 兜底,clone 更稳
30
+ - 连载重写带旧账:上一轮评审 major 意见随重写提示词下发,被点名章一并回炉
31
+ - 评审口径事前下发作者面:写作前就知道按什么标准被审(与评审面同源,永不脱节)
32
+ - 插件安装失败回滚修复(人话报错+残缺自愈,不再 500/永久撞车)
33
+ - 错误行带崩溃现场(traceback 尾帧直达 UI,同因止损按位置区分)
34
+ - 代码块长行缺省自动换行,运行详情不再撑横向滚动条
33
35
  <!-- relnotes:end -->
34
36
 
35
37
 
@@ -2591,6 +2591,36 @@ __CRITIQUE__
2591
2591
  - 针对性改进所有 major 问题;保持既定风格与设定。
2592
2592
  - 完成后用 3 句话说明本轮改了什么。"""
2593
2593
 
2594
+ # 评审口径单一真源:评审面提示词(单章/全局)与作者面注入块从这里同源提取,
2595
+ # 两边永不脱节。此前评审清单/签约专项/全局口径只活在评审面,作者第一遍写完
2596
+ # 被打回才知道按什么标准被审(2026-10-10 实案:全章过线仍卡全局 major)。
2597
+ # 作者面刻意不下发评分机制本体(1-10 分/阈值/宁严勿宽)——对写作者零价值,
2598
+ # 透下去只喂响应试素材;清单条目本身全是质量正向要求,事前下发只有好处。
2599
+ REVIEW_CHECKLIST_ITEMS = (
2600
+ "叙述语域与视角统一",
2601
+ "复杂情感落到动作、五感或生理细节",
2602
+ "开篇在前 600 字进入具体冲突",
2603
+ "每章发生真实的局面变化",
2604
+ "无可删的重复铺垫与长段解释",
2605
+ )
2606
+ GLOBAL_REVIEW_FOCUS = ("主线一致性、人物弧光、开篇吸引力、文风表现、情感表达、"
2607
+ "情节推进与节奏控制")
2608
+
2609
+
2610
+ def author_review_rubric_block(dims, signing_dims):
2611
+ """作者面评审口径块:起草/修订提示词里事前下发「会按什么标准被审」,
2612
+ 与评审面同源(REVIEW_CHECKLIST_ITEMS / GLOBAL_REVIEW_FOCUS)。静态文本,
2613
+ 同批各章字节一致,不碎前缀缓存。"""
2614
+ lines = [
2615
+ "## 评审口径(本书会按以下标准审稿,写作时对照自查)",
2616
+ "- 评分维度:" + " / ".join(dims) + "。",
2617
+ "- 逐项硬清单:" + ";".join(REVIEW_CHECKLIST_ITEMS) + "。",
2618
+ "- 全书级关注:" + GLOBAL_REVIEW_FOCUS + "。",
2619
+ "- 签约专项维度:" + "、".join(signing_dims) + "。",
2620
+ ]
2621
+ return "\n".join(lines) + "\n"
2622
+
2623
+
2594
2624
  NOVEL_CRITIQUE_PROMPT = """你是严格的评审(不要使用任何工具、不要修改文件,只依据下方稿件内容评审)。
2595
2625
  请输出一个 ```json 代码块,不要输出其他内容。JSON 结构:
2596
2626
  {
@@ -2599,7 +2629,7 @@ NOVEL_CRITIQUE_PROMPT = """你是严格的评审(不要使用任何工具、
2599
2629
  "summary": "一句话总评"
2600
2630
  }
2601
2631
  每个维度打 1-10 分(可为小数),宁严勿宽。major 问题必须给 quote(系统会逐条校验引文是否真在稿件中——编造的引文会被降档标记)。
2602
- 评审必须逐项回答:叙述语域与视角是否统一;复杂情感是否有动作、五感或生理细节;开篇是否在前 600 字进入具体冲突;本章是否发生局面变化;是否存在可删的重复铺垫或长段解释。
2632
+ 评审逐项核查以下各项是否成立:%s。""" % (";".join(REVIEW_CHECKLIST_ITEMS)) + """
2603
2633
 
2604
2634
  ## 待评审稿件
2605
2635
  ---
@@ -2885,6 +2915,7 @@ __VOLUME__
2885
2915
  - 本章主要情绪/戏剧变化:__HIGHLIGHT__(按其重要性决定篇幅和表现方式;不强制放大,不为满足标签伪造高潮)
2886
2916
  __LOCKED_CH__
2887
2917
  __STALE_NOTE__
2918
+ __REDO_ISSUES__
2888
2919
  - 正文约 __WORDS__ 字,中文,直接开写正文(可含本章标题行)。
2889
2920
 
2890
2921
  ## 前情提要(此前各章结尾摘录,衔接用)
@@ -2899,6 +2930,7 @@ __STYLE__
2899
2930
  ## 签约质量门禁
2900
2931
  __QUALITY_GATE__
2901
2932
 
2933
+ __REVIEW_RUBRIC__
2902
2934
  - 写完文件后,最终回复只输出一行:`第 __I__ 章完成(约 __WORDS__ 字)`——不要在回复里复述或解释正文。"""
2903
2935
 
2904
2936
  SERIAL_REVISE_PROMPT = """你是一名网文作者。第 __I__ 章没有通过评审,请修订文件 `__FILE__`(直接改写该文件)。文件必须以 UTF-8 编码保存(PowerShell 显式加 -Encoding UTF8,禁止依赖默认编码)。
@@ -2913,6 +2945,7 @@ __BEATS__
2913
2945
  (本章爽点/情绪爆点:__HIGHLIGHT__)
2914
2946
  __LOCKED_CH__
2915
2947
 
2948
+ __REVIEW_RUBRIC__
2916
2949
  ## 本章评审意见
2917
2950
  __CRITIQUE__
2918
2951
 
@@ -2936,7 +2969,7 @@ SERIAL_GLOBAL_PROMPT = """你是独立小说质量评审(不要修改任何文
2936
2969
  "issues": [{"dim": "维度名", "severity": "major|minor", "note": "具体问题(指明哪一章)", "quote": "支撑该问题的稿件原文连续片段(≥8字,逐字摘录不许改写)"}],
2937
2970
  "summary": "一句话总评:所读范围内的文本优势、主要风险与未验证项;不得预测签约结果"
2938
2971
  }
2939
- 每个维度打 1-10 分,按统一文本 rubric 与所读证据评估,不以“宁严勿宽”压分。重点关注:主线一致性、人物弧光、开篇吸引力、文风表现、情感表达、情节推进与节奏控制。
2972
+ 每个维度打 1-10 分,按统一文本 rubric 与所读证据评估,不以“宁严勿宽”压分。重点关注:%s。""" % GLOBAL_REVIEW_FOCUS + """
2940
2973
  若是全书最初的前 3 章,可观察视角人物、故事承诺、开篇兑现和读者理解成本;固定字数点、钩子/爽点密度仅作参考,不是扣分门槛。发现问题须给可定位原文引文,并区分文本事实、解释和建议。
2941
2974
  major 问题必须给 quote(系统会逐条校验引文是否真在稿件中——编造的引文会被降档标记)。
2942
2975
 
@@ -3342,6 +3375,8 @@ def _run_serial_review(run, task, agents, ev, stats, mode, critics, impl, route,
3342
3375
  # 签约专项维度作为额外硬性检查注入提示词与报告证据。
3343
3376
  dims = task.get("rubric") or DEFAULT_RUBRIC
3344
3377
  signing_dims = novel_quality.rubric_for({"type": "serial_novel"})
3378
+ # 作者面评审口径块(与评审面同源提取):起草/修订/打磨提示词都带
3379
+ rubric_block = author_review_rubric_block(dims, signing_dims)
3345
3380
  threshold = task.get("threshold", 7.0)
3346
3381
  threshold_ch = threshold - 0.5 if threshold >= 7.5 else threshold # 单章阈值略放宽 0.5 分
3347
3382
  dimkey = ", ".join('"%s": 0' % d for d in dims)
@@ -3412,6 +3447,9 @@ def _run_serial_review(run, task, agents, ev, stats, mode, critics, impl, route,
3412
3447
  inherit = run.get("inherit") or {}
3413
3448
  done_set = set(inherit.get("done_chapters") or [])
3414
3449
  inh_scores = {c.get("chapter"): c for c in (inherit.get("chapter_scores") or [])}
3450
+ # 未达标重写的原因账(store.retry_task 从 verdict.major_issues 下发):
3451
+ # 章节级+全局级 major,渲染进对应重写章的起草提示词;达标续跑为空表
3452
+ redo_issues = [x for x in (inherit.get("redo_issues") or []) if isinstance(x, dict)]
3415
3453
  if inherit.get("outline"):
3416
3454
  outline = inherit["outline"]
3417
3455
  # 历史遗留:降级/模板大纲被继承时,真实任务宁可中止重生成,也不按空模板写全书
@@ -3840,6 +3878,26 @@ def _run_serial_review(run, task, agents, ev, stats, mode, critics, impl, route,
3840
3878
  if os.path.exists(os.path.join(workdir, ch_file)):
3841
3879
  stale_note = ("- 注意:目录里已有一版**未通过评审**的旧稿 `%s`——不要校验沿用、"
3842
3880
  "不要增量修补,按本章任务直接覆盖重写。" % ch_file)
3881
+ # 上一轮未达标原因(「重写未达标章」链路):本章被点名的 major 与
3882
+ # 全书级 major 随起草提示词下发——原因只躺在报告里、作者盲写等
3883
+ # 修订拉回,白白多烧一轮评审(2026-10-10 实案)。
3884
+ def _issue_ch(x):
3885
+ try:
3886
+ return int(x.get("chapter"))
3887
+ except (TypeError, ValueError):
3888
+ return None
3889
+
3890
+ redo_lines = []
3891
+ mine = [x for x in redo_issues if _issue_ch(x) == i]
3892
+ book_lvl = [x for x in redo_issues if _issue_ch(x) is None]
3893
+ if mine:
3894
+ redo_lines.append("- 本章被上一轮评审点名的问题(重写必须逐条消解):")
3895
+ redo_lines += _major_lines(mine)
3896
+ if book_lvl:
3897
+ redo_lines.append("- 上一轮全书级 major 问题(本章重写须配合消解,不得相抵):")
3898
+ redo_lines += _major_lines(book_lvl)
3899
+ redo_note = ("## 上一轮评审未达标原因(必读)\n" + "\n".join(redo_lines) + "\n"
3900
+ ) if redo_lines else ""
3843
3901
  # stable_order:同一任务 8 个章节的技能块必须字节级一致(§07 T1.2' 前缀缓存)
3844
3902
  # 三块分开留底:起草重试的分层降级(_serial_shrunk_block)要按
3845
3903
  # 「经验库/圣经」各自的身份收缩,折成一整块就只剩 4K 硬截一层了
@@ -3878,6 +3936,8 @@ def _run_serial_review(run, task, agents, ev, stats, mode, critics, impl, route,
3878
3936
  .replace("__HOOK__", branch_hook or ch.get("hook") or "留下悬念")
3879
3937
  .replace("__LOCKED_CH__", serial_locked_chapter_block(workdir, i))
3880
3938
  .replace("__STALE_NOTE__", stale_note)
3939
+ .replace("__REDO_ISSUES__", redo_note)
3940
+ .replace("__REVIEW_RUBRIC__", rubric_block)
3881
3941
  .replace("__HIGHLIGHT__", ch.get("highlight") or "按剧情要点自然铺设一处小冲突/小反转")
3882
3942
  .replace("__STYLE__", style_tpl)
3883
3943
  .replace("__QUALITY_GATE__", novel_quality.opening_requirements(i, wpc))
@@ -4031,7 +4091,7 @@ def _run_serial_review(run, task, agents, ev, stats, mode, critics, impl, route,
4031
4091
  if not res["ok"]:
4032
4092
  # 成品是文件不是退出码:CLI 超时但章稿已完整落盘(终章长文实测
4033
4093
  # 反复出现——文件写完、收尾声明没等到)就送评审门把关,别整章作废
4034
- live = (store.get_run(run_id).get("steps") or [])
4094
+ live = ((store.get_run(run_id) or {}).get("steps") or [])
4035
4095
  if live:
4036
4096
  store.finish_step(run_id, live[-1]["n"], "done",
4037
4097
  summary="起草调用超时,但章稿已完整落盘(约 %d 字)——交评审门判质量"
@@ -4290,6 +4350,7 @@ def _run_serial_review(run, task, agents, ev, stats, mode, critics, impl, route,
4290
4350
  .replace("__VOLUME__", vol_block_for(i))
4291
4351
  .replace("__BEATS__", ch.get("beats") or "按大纲推进")
4292
4352
  .replace("__LOCKED_CH__", serial_locked_chapter_block(workdir, i))
4353
+ .replace("__REVIEW_RUBRIC__", rubric_block)
4293
4354
  .replace("__HIGHLIGHT__", ch.get("highlight")
4294
4355
  or "按剧情要点自然铺设一处小冲突/小反转")
4295
4356
  .replace("__STYLE__", style_tpl)
@@ -4445,6 +4506,7 @@ def _run_serial_review(run, task, agents, ev, stats, mode, critics, impl, route,
4445
4506
  .replace("__VOLUME__", vol_block_for(j))
4446
4507
  .replace("__BEATS__", _wch.get("beats") or "按大纲推进")
4447
4508
  .replace("__LOCKED_CH__", serial_locked_chapter_block(workdir, j))
4509
+ .replace("__REVIEW_RUBRIC__", rubric_block)
4448
4510
  .replace("__HIGHLIGHT__", _wch.get("highlight")
4449
4511
  or "按剧情要点自然铺设一处小冲突/小反转")
4450
4512
  .replace("__STYLE__", style_tpl)
@@ -4656,6 +4718,7 @@ def _run_serial_review(run, task, agents, ev, stats, mode, critics, impl, route,
4656
4718
  .replace("__VOLUME__", vol_block_for(i))
4657
4719
  .replace("__BEATS__", ch.get("beats") or "按大纲推进")
4658
4720
  .replace("__LOCKED_CH__", serial_locked_chapter_block(workdir, i))
4721
+ .replace("__REVIEW_RUBRIC__", rubric_block)
4659
4722
  .replace("__HIGHLIGHT__", ch.get("highlight")
4660
4723
  or "按剧情要点自然铺设一处小冲突/小反转")
4661
4724
  .replace("__CRITIQUE__", crit)
@@ -5940,8 +6003,23 @@ def execute_run(run_id):
5940
6003
  status="cancelled", ended_at=_now())
5941
6004
  except Exception as e:
5942
6005
  import traceback
6006
+ # 崩溃现场随错误行下发(2026-10-10 Mac 实案:连载连烧两轮自动续跑都
6007
+ # 死于 AttributeError("'NoneType' object has no attribute 'get'"),
6008
+ # 错误行只有一行 repr 无从定位;traceback 全文虽落 run 目录 error.log,
6009
+ # 但 UI 上看不见)。尾帧 = 文件:行号:函数名,一行说清死在哪;同因止损
6010
+ # 的错误签名因此按崩溃位置区分——同位置才算同因,代码升级后行号漂移
6011
+ # 自动能多试一轮(新代码可能已修复),次数上限仍兜底。
6012
+ _tb_tail = ""
6013
+ try:
6014
+ _frames = traceback.extract_tb(e.__traceback__)
6015
+ if _frames:
6016
+ _f = _frames[-1]
6017
+ _tb_tail = "(崩溃于 %s:%s %s)" % (
6018
+ os.path.basename(_f.filename), _f.lineno, _f.name)
6019
+ except Exception:
6020
+ pass
5943
6021
  store.update_run(run_id, expected_status="running", status="failed",
5944
- error=repr(e)[:500], ended_at=_now())
6022
+ error=(repr(e)[:380] + _tb_tail)[:500], ended_at=_now())
5945
6023
  try:
5946
6024
  err_path = store.run_dir(run_id) / "error.log"
5947
6025
  if _inside(str(store.run_dir(run_id).parent), str(err_path)):
@@ -371,14 +371,21 @@ def install(plugin_id, roots=None):
371
371
  "skill_count": len(old.get("skill_pack_ids") or [])}, None
372
372
  source = Path(desc["dir"]).resolve()
373
373
  target = (_install_root() / plugin_id).resolve()
374
+ market_id = "plugin-" + plugin_id
375
+ # 回滚标记必须在 try 之前就绑定:_copy_plugin/load_manifest 在记账前
376
+ # 抛错时 except 要靠它决定是否清 market 包,未初始化会把人话错误
377
+ # 炸成 UnboundLocalError→500,还会跳过残留目录清理让重装永远撞车。
378
+ market_written = False
374
379
  try:
375
380
  target.relative_to(_install_root().resolve())
381
+ if target.exists() and target != source:
382
+ # 有登记记录的已装插件在上面 already 分支就返回了;走到这里
383
+ # 还存在目标目录只可能是上次安装中途失败的残缺副本,清掉重装。
384
+ shutil.rmtree(target, ignore_errors=True)
376
385
  _copy_plugin(source, target)
377
386
  installed_desc = load_manifest(target)
378
387
  files = _skill_files(installed_desc)
379
- market_id = "plugin-" + plugin_id
380
388
  skill_result = None
381
- market_written = False
382
389
  if files:
383
390
  skill_result, err = market.install_files(
384
391
  market_id, installed_desc["display_name"], files,
package/app/core/store.py CHANGED
@@ -1723,12 +1723,27 @@ def retry_task(task_id):
1723
1723
  # 结构——这正是「重写未达标章」按钮(前端 done+未达标态放行
1724
1724
  # retry)区别于断点续跑的语义。
1725
1725
  redo = set()
1726
+ redo_issues = []
1726
1727
  if (prev.get("verdict") or {}).get("publishable") is False:
1727
1728
  redo = {int(c["chapter"]) for c in v_scores
1728
1729
  if not c.get("passed") and c.get("chapter") is not None}
1730
+ # 未达标原因账(verdict.major_issues,章节级+全局级)随继承
1731
+ # 下发:流水线把本章点名项与全书级项渲染进重写章起草提示词,
1732
+ # 作者第一遍就知道往哪改,不再盲写等修订拉回。被 major 点名
1733
+ # 的过线章一并回炉——总判定卡在 major 上时只重写未过线章
1734
+ # 消不掉账,「重写未达标章」会空转(2026-10-10 实案)。
1735
+ for x in (prev.get("verdict") or {}).get("major_issues") or []:
1736
+ if not isinstance(x, dict):
1737
+ continue
1738
+ redo_issues.append(x)
1739
+ try:
1740
+ redo.add(int(x.get("chapter")))
1741
+ except (TypeError, ValueError):
1742
+ pass
1729
1743
  if redo:
1730
1744
  done = [n for n in done if n not in redo]
1731
- v_scores = [c for c in v_scores if c.get("passed")]
1745
+ v_scores = [c for c in v_scores if c.get("passed")
1746
+ and c.get("chapter") not in redo]
1732
1747
  if done or redo:
1733
1748
  if redo:
1734
1749
  scores = v_scores # 未达标重写:只带已过线章的分数
@@ -1743,6 +1758,8 @@ def retry_task(task_id):
1743
1758
  # 落账的 chapter_scores——否则多轮失败恢复会把全部
1744
1759
  # 已过线章节重新评审(实测一晚白烧数百万 token)
1745
1760
  "chapter_scores": scores,
1761
+ # 未达标重写的原因账(达标续跑时为空表)
1762
+ "redo_issues": redo_issues,
1746
1763
  }
1747
1764
  break
1748
1765
  else:
package/app/ui/app.js CHANGED
@@ -10389,7 +10389,7 @@ function codeThemeFor(mode) {
10389
10389
  return ok ? ok.id : (mode === "dark" ? "github-dark" : "github");
10390
10390
  }
10391
10391
  function codeLineNum() { return localStorage.getItem(CT_LINENUM_KEY) !== "0"; } // 缺省开
10392
- function codeWrap() { return localStorage.getItem(CT_WRAP_KEY) === "1"; }
10392
+ function codeWrap() { return localStorage.getItem(CT_WRAP_KEY) !== "0"; } // 缺省开:长行自动换行,别撑出横向滚动条
10393
10393
  function codeFontSize() {
10394
10394
  const n = parseFloat(localStorage.getItem(CT_SIZE_KEY) || "12.5");
10395
10395
  return isFinite(n) ? Math.min(22, Math.max(10, n)) : 12.5;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codebee",
3
- "version": "0.1.100",
3
+ "version": "0.1.101",
4
4
  "description": "CodeBee · 多智能体编排台 — 统一编排本机 AI 编码 CLI(Codex / Claude Code / QwenCode / OpenCode / Aider…):目标→拆解→路由→执行→验证→跨厂商评审→修复→报告。纯 Python 标准库实现,本地 Web UI。",
5
5
  "license": "MIT",
6
6
  "bin": {