codebee 0.1.40 → 0.1.42

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.42(2026-09-22)
10
+
11
+ ### ✨ 新功能
12
+
13
+ - 新任务类型「标书编制」:上传招标文件,AI 按评分标准逐条应答起草投标文件——五大评审维度(应答完整性/合规符合度/方案针对性/评分点覆盖/商务清晰度),资质业绩只引真实材料(缺失标注待补,绝不虚构),应答先结论后细节,并全文自查废标风险项(密封/签章/格式/有效期)。
14
+
15
+ ## v0.1.41(2026-09-22)
16
+
17
+ ### 🛠 问题修复
18
+
19
+ - 断点续跑/重跑更省钱:同一任务再次生成大纲或执行计划时直接命中上次结果的精确缓存(1 小时内有效),不再重复花一次全量规划调用;大纲首试成功但格式不合规时的重试仍会重新生成(保「换一次样本」语义,不会拿同一份坏文本空转)。
20
+
9
21
  ## v0.1.40(2026-09-21)
10
22
 
11
23
  ### 🛠 问题修复
package/README.md CHANGED
@@ -21,8 +21,16 @@ Kimi Code、MiMo Code、Grok Build、Pi、DeepSeek Harness……),提供
21
21
  不会把你的任务内容交给任何第三方。
22
22
 
23
23
  <!-- relnotes:start -->
24
+ ### ✨ 新功能 · New
25
+
26
+ - 新任务类型「标书编制」:上传招标文件,AI 按评分标准逐条应答起草投标文件;资质不虚构(缺失标注待补)、应答先结论、全文自查废标风险项。
27
+ - New "Bid Document" task type: upload the tender file and the AI drafts point-by-point responses against scoring criteria — no fabricated credentials, conclusion-first answers, and a full disqualification-risk sweep.
28
+
24
29
  ### 🛠 问题修复 · Fixes
25
30
 
31
+ - 断点续跑/重跑更省钱:同任务再次生成大纲或计划直接命中精确缓存(1 小时内),不重复花全量规划调用;格式不合规的重试仍重新生成。
32
+ - Resuming or re-running a task now hits an exact cache for outline/plan regeneration (within 1 hour), skipping a full planning call; format-failure retries still regenerate.
33
+
26
34
  - 修复长任务上下文撑爆后重试仍失败:压缩摘要此前只写日志、没真正替换发往模型的上下文;现在重试用「压缩摘要+最近消息」重建请求并放弃膨胀的旧会话,超长任务能真正续跑。
27
35
  - 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
36
 
@@ -25,6 +25,7 @@ TYPE_DIMENSIONS = {
25
25
  "email": "writing",
26
26
  "tech_proposal": "reasoning",
27
27
  "resume": "writing",
28
+ "bid_doc": "writing", # 标书:正文应答写作量大(合规判断由契约规则约束)
28
29
  "zentao": "coding",
29
30
  }
30
31
 
package/app/core/flows.py CHANGED
@@ -113,6 +113,12 @@ BUILTIN_FLOWS = [
113
113
  "threshold": 7.0, "rounds": 2,
114
114
  "goal_hint": "目标岗位 + 个人经历(可贴旧简历附件),几年经验投什么职级",
115
115
  "note": "简历优化/定制:起草 → 多维评审 → 修订循环 → 发布门禁"},
116
+ {"id": "bid_doc", "name": "标书编制", "icon": "i-file-search", "engine": "review", "builtin": True,
117
+ "manuscript": "bid.md",
118
+ "rubric": ["应答完整性", "合规符合度", "方案针对性", "评分点覆盖", "商务清晰度"],
119
+ "threshold": 7.5, "rounds": 2,
120
+ "goal_hint": "招标文件(可作附件)+ 标段/包件 + 我方资质与业绩亮点",
121
+ "note": "标书/投标文件:起草 → 多维评审 → 修订循环 → 发布门禁"},
116
122
  ]
117
123
 
118
124
  _ID_RE = re.compile(r"^[a-z][a-z0-9_-]{0,31}$")
@@ -1675,6 +1675,14 @@ CONTENT_DELIVERY_CONTRACTS = {
1675
1675
  "围绕目标岗位提炼真实经历,用行动、结果和技能关键词表达岗位匹配度。",
1676
1676
  "不得虚构经历、公司、学历、指标或技术栈;缺少量化数据时保留待补提示。",
1677
1677
  ]),
1678
+ "bid_doc": ("投标经理", [
1679
+ # BidCraft 标书匠灵感(2026-09-22):逐条对齐招标文件实质性要求与评分标准
1680
+ "逐条对齐招标文件的实质性要求和评分标准,每个评分点都有对应的应答段落。",
1681
+ "资质、业绩、人员证书只引用真实材料;缺失项明确标注「待补」并说明需要哪类材料。",
1682
+ # sepia 分场合规则(投标文体):先结论后展开;废标项零容忍
1683
+ "应答开门见山给结论(满足/优于/偏离),再给支撑细节,不写套话式开头。",
1684
+ "全文自查废标风险项(密封/签章/格式/有效期),任何疑似偏离必须显式列出。",
1685
+ ]),
1678
1686
  }
1679
1687
 
1680
1688
 
@@ -114,13 +114,13 @@ def _log_usage(source, role, task, res, agent=None, tool="", model="", provider=
114
114
  except Exception:
115
115
  pass
116
116
 
117
- CODE_PLAN_PROMPT = """你是技术负责人。请把下面的开发目标拆解为 __N__ 个以内、按顺序执行的子任务,
118
- 并判定任务难度和后续质量步骤。只输出一个 ```json 代码块,不要输出其他内容。JSON 结构:
119
- {"difficulty": "easy 或 hard", "workflow": {"review_required": true, "max_repair_rounds": 0, "allow_switch": false}, "subtasks": [{"title": "简短标题", "detail": "具体要做什么,给执行工程师的直接指令", "files": ["涉及的文件路径"]}]}
120
- 难度判定:常规增删改查/小函数/格式调整 = easy;跨模块改动/架构调整/复杂算法/安全相关 = hard。
121
- 质量步骤判定:涉及安全、数据迁移、并发、权限、公共 API 或跨模块改动时必须评审;
122
- 有明确自动验收的低风险小改可不做模型评审。max_repair_rounds 取 0-2,只有复杂任务才建议换将。
123
- 子任务粒度要可独立验证;最后一个子任务必须包含整体联调/收尾。
117
+ CODE_PLAN_PROMPT = """你是技术负责人。请把下面的开发目标拆解为 __N__ 个以内、按顺序执行的子任务,
118
+ 并判定任务难度和后续质量步骤。只输出一个 ```json 代码块,不要输出其他内容。JSON 结构:
119
+ {"difficulty": "easy 或 hard", "workflow": {"review_required": true, "max_repair_rounds": 0, "allow_switch": false}, "subtasks": [{"title": "简短标题", "detail": "具体要做什么,给执行工程师的直接指令", "files": ["涉及的文件路径"]}]}
120
+ 难度判定:常规增删改查/小函数/格式调整 = easy;跨模块改动/架构调整/复杂算法/安全相关 = hard。
121
+ 质量步骤判定:涉及安全、数据迁移、并发、权限、公共 API 或跨模块改动时必须评审;
122
+ 有明确自动验收的低风险小改可不做模型评审。max_repair_rounds 取 0-2,只有复杂任务才建议换将。
123
+ 子任务粒度要可独立验证;最后一个子任务必须包含整体联调/收尾。
124
124
 
125
125
  ## 先探索再计划(重要,借鉴 OpenSpec explore)
126
126
  拆解之前先用你的读文件/搜索工具**实际查看工作目录**,找到目标相关的真实文件与函数,再据此拆解。
@@ -137,12 +137,12 @@ __CONTEXT__
137
137
  ## 验收命令(最终必须通过)
138
138
  __VERIFY__"""
139
139
 
140
- REVIEW_OUTLINE_PROMPT = """你是内容主编。请为下面的创作任务拟一份写作大纲(要点列表,3-8 条),
141
- 并按内容风险建议后续质量步骤。
142
- 只输出一个 ```json 代码块,不要输出其他内容。JSON 结构:
143
- {"outline": ["要点1", "要点2", ...], "workflow": {"reviewers": 1, "review_rounds": 1}}
144
- reviewers 取 1-2,review_rounds 取 1-3;研究报告、技术方案、长文和高发布门槛应增加,
145
- 普通邮件、短翻译、工作汇报等低风险短内容保持 1。
140
+ REVIEW_OUTLINE_PROMPT = """你是内容主编。请为下面的创作任务拟一份写作大纲(要点列表,3-8 条),
141
+ 并按内容风险建议后续质量步骤。
142
+ 只输出一个 ```json 代码块,不要输出其他内容。JSON 结构:
143
+ {"outline": ["要点1", "要点2", ...], "workflow": {"reviewers": 1, "review_rounds": 1}}
144
+ reviewers 取 1-2,review_rounds 取 1-3;研究报告、技术方案、长文和高发布门槛应增加,
145
+ 普通邮件、短翻译、工作汇报等低风险短内容保持 1。
146
146
 
147
147
  ## 创作任务
148
148
  __GOAL__
@@ -327,9 +327,14 @@ def make_serial_outline(task, author_agent=None, workdir=None, ev=None, log_path
327
327
  # 否则 stop_reason=max_tokens、正文为空(实测 2048 全被思考吞掉)
328
328
  # 流式回调把增量实时写进步骤日志,直连生成不再是一行标题的黑箱
329
329
  cb = _log_streamer(log_path)
330
- res = modelhub.chat(prov["id"], model, prompt,
331
- max_tokens=16000, timeout=300, on_delta=cb,
332
- reasoning_effort=_reasoning_effort(task))
330
+ # §07 T2.2 幂等精确缓存(TTL 1h):断点续跑/重跑同任务时大纲
331
+ # prompt 逐字节相同,命中省一次全量规划调用。第 2 次尝试故意
332
+ # 旁路缓存——首试 ok 但 JSON 不合法时,重试要的是「再抽一次样
333
+ # 本」而不是同一份坏文本。
334
+ res = modelhub.chat(prov["id"], model, prompt,
335
+ max_tokens=16000, timeout=300, on_delta=cb,
336
+ reasoning_effort=_reasoning_effort(task),
337
+ cache_ttl=3600 if _attempt == 1 else 0)
333
338
  cb.flush()
334
339
  _log_usage("outline", "outline", task, res, model=model,
335
340
  provider=prov.get("name", prov.get("id", "")),
@@ -409,56 +414,56 @@ def _norm_subtasks(data):
409
414
  return steps or None
410
415
 
411
416
 
412
- def _plan_difficulty(data):
417
+ def _plan_difficulty(data):
413
418
  d = str((data or {}).get("difficulty") or "").lower()
414
- return d if d in ("easy", "hard") else None
415
-
416
-
417
- def _plan_workflow(data):
418
- """规范化规划器给出的后续步骤建议;最终安全下限由任务编译器裁决。"""
419
- raw = (data or {}).get("workflow")
420
- if not isinstance(raw, dict):
421
- return None
422
- out = {}
423
- if isinstance(raw.get("review_required"), bool):
424
- out["review_required"] = raw["review_required"]
425
- try:
426
- if "max_repair_rounds" in raw:
427
- out["max_repair_rounds"] = max(0, min(2, int(raw["max_repair_rounds"])))
428
- except (TypeError, ValueError):
429
- pass
430
- if isinstance(raw.get("allow_switch"), bool):
431
- out["allow_switch"] = raw["allow_switch"]
432
- return out or None
433
-
434
-
435
- def _orchestrator():
436
- """编排者可用时返回 (provider, model),否则 None。"""
437
- try:
438
- return modelhub.resolve_orchestrator()
439
- except Exception:
440
- return None
441
-
442
-
443
- def _reasoning_effort(task):
444
- """把任务级思考程度映射为直连编排者支持的 reasoning 参数。"""
445
- task = task if isinstance(task, dict) else {}
446
- selected = str(task.get("thinking") or "standard").strip().lower()
447
- if selected in ("low", "standard", "high"):
448
- return {"low": "low", "standard": "medium", "high": "high"}[selected]
449
- mode = str(task.get("mode") or "auto").lower()
450
- difficulty = str(task.get("difficulty") or "auto").lower()
451
- ttype = str(task.get("type") or "").lower()
452
- try:
453
- threshold = float(task.get("threshold") or 7.0)
454
- except (TypeError, ValueError):
455
- threshold = 7.0
456
- if mode == "fast" or difficulty == "easy" or threshold <= 6.0:
457
- return "low"
458
- if (mode == "expert" or difficulty == "hard" or threshold >= 8.5
459
- or ttype in ("research", "tech_proposal", "serial_novel")):
460
- return "high"
461
- return "medium"
419
+ return d if d in ("easy", "hard") else None
420
+
421
+
422
+ def _plan_workflow(data):
423
+ """规范化规划器给出的后续步骤建议;最终安全下限由任务编译器裁决。"""
424
+ raw = (data or {}).get("workflow")
425
+ if not isinstance(raw, dict):
426
+ return None
427
+ out = {}
428
+ if isinstance(raw.get("review_required"), bool):
429
+ out["review_required"] = raw["review_required"]
430
+ try:
431
+ if "max_repair_rounds" in raw:
432
+ out["max_repair_rounds"] = max(0, min(2, int(raw["max_repair_rounds"])))
433
+ except (TypeError, ValueError):
434
+ pass
435
+ if isinstance(raw.get("allow_switch"), bool):
436
+ out["allow_switch"] = raw["allow_switch"]
437
+ return out or None
438
+
439
+
440
+ def _orchestrator():
441
+ """编排者可用时返回 (provider, model),否则 None。"""
442
+ try:
443
+ return modelhub.resolve_orchestrator()
444
+ except Exception:
445
+ return None
446
+
447
+
448
+ def _reasoning_effort(task):
449
+ """把任务级思考程度映射为直连编排者支持的 reasoning 参数。"""
450
+ task = task if isinstance(task, dict) else {}
451
+ selected = str(task.get("thinking") or "standard").strip().lower()
452
+ if selected in ("low", "standard", "high"):
453
+ return {"low": "low", "standard": "medium", "high": "high"}[selected]
454
+ mode = str(task.get("mode") or "auto").lower()
455
+ difficulty = str(task.get("difficulty") or "auto").lower()
456
+ ttype = str(task.get("type") or "").lower()
457
+ try:
458
+ threshold = float(task.get("threshold") or 7.0)
459
+ except (TypeError, ValueError):
460
+ threshold = 7.0
461
+ if mode == "fast" or difficulty == "easy" or threshold <= 6.0:
462
+ return "low"
463
+ if (mode == "expert" or difficulty == "hard" or threshold >= 8.5
464
+ or ttype in ("research", "tech_proposal", "serial_novel")):
465
+ return "high"
466
+ return "medium"
462
467
 
463
468
 
464
469
  def make_code_plan(task, planner_agent, workdir, ev=None, resume=None, log_path=None):
@@ -482,10 +487,10 @@ def make_code_plan(task, planner_agent, workdir, ev=None, resume=None, log_path=
482
487
  _log_usage("plan", "plan", task, res, agent=planner_agent)
483
488
  data = runner.extract_json(res.get("text") or "")
484
489
  steps = _norm_subtasks(data)
485
- if steps:
486
- return {"source": "llm(%s)" % planner_agent["id"], "steps": steps,
487
- "difficulty": _plan_difficulty(data),
488
- "workflow": _plan_workflow(data)}
490
+ if steps:
491
+ return {"source": "llm(%s)" % planner_agent["id"], "steps": steps,
492
+ "difficulty": _plan_difficulty(data),
493
+ "workflow": _plan_workflow(data)}
489
494
  return _fallback_code_plan(task, "LLM 计划解析失败,退化为单步模板")
490
495
 
491
496
 
@@ -496,13 +501,15 @@ def _fallback_code_plan(task, note):
496
501
 
497
502
  def _orch_code_plan(task, prov, model, log_path=None):
498
503
  cb = _log_streamer(log_path)
504
+ # §07 T2.2:代码计划同任务同 prompt(断点续跑/重试),TTL 1h 精确缓存
499
505
  res = modelhub.chat(prov["id"], model,
500
506
  (CODE_PLAN_PROMPT.replace("__N__", str(MAX_SUBTASKS))
501
507
  .replace("__GOAL__", task["goal"])
502
508
  .replace("__CONTEXT__", task.get("context") or "(无)")
503
509
  .replace("__VERIFY__", task.get("verify_command") or "(未配置)")),
504
- max_tokens=8000, timeout=300, on_delta=cb,
505
- reasoning_effort=_reasoning_effort(task))
510
+ max_tokens=8000, timeout=300, on_delta=cb,
511
+ reasoning_effort=_reasoning_effort(task),
512
+ cache_ttl=3600)
506
513
  cb.flush()
507
514
  _log_usage("plan", "plan", task, res, model=model,
508
515
  provider=prov.get("name", prov.get("id", "")))
@@ -515,9 +522,9 @@ def _orch_code_plan(task, prov, model, log_path=None):
515
522
  if not steps:
516
523
  _append_log(log_path, "编排者计划返回内容无法解析为子任务,回退 CLI")
517
524
  return None
518
- return {"source": "编排者(%s · %s)" % (prov.get("name", prov["id"]), model),
519
- "steps": steps, "difficulty": _plan_difficulty(data),
520
- "workflow": _plan_workflow(data)}
525
+ return {"source": "编排者(%s · %s)" % (prov.get("name", prov["id"]), model),
526
+ "steps": steps, "difficulty": _plan_difficulty(data),
527
+ "workflow": _plan_workflow(data)}
521
528
 
522
529
 
523
530
  def make_review_outline(task):
@@ -530,8 +537,9 @@ def make_review_outline(task):
530
537
  (REVIEW_OUTLINE_PROMPT
531
538
  .replace("__GOAL__", task["goal"])
532
539
  .replace("__CONTEXT__", task.get("context") or "(无)")),
533
- max_tokens=8000, timeout=300,
534
- reasoning_effort=_reasoning_effort(task))
540
+ max_tokens=8000, timeout=300,
541
+ reasoning_effort=_reasoning_effort(task),
542
+ cache_ttl=3600) # §07 T2.2:同任务重试幂等,TTL 1h
535
543
  _log_usage("outline", "outline", task, res, model=model,
536
544
  provider=prov.get("name", prov.get("id", "")))
537
545
  if not res["ok"]:
@@ -541,20 +549,20 @@ def make_review_outline(task):
541
549
  if not isinstance(outline, list):
542
550
  return None
543
551
  items = [str(x).strip()[:120] for x in outline if str(x).strip()][:8]
544
- if not items:
545
- return None
546
- workflow = data.get("workflow") if isinstance(data.get("workflow"), dict) else {}
547
- try:
548
- reviewers = max(1, min(2, int(workflow.get("reviewers") or 1)))
549
- except (TypeError, ValueError):
550
- reviewers = 1
551
- try:
552
- review_rounds = max(1, min(3, int(workflow.get("review_rounds") or 1)))
553
- except (TypeError, ValueError):
554
- review_rounds = 1
555
- return {"source": "编排者(%s · %s)" % (prov.get("name", prov["id"]), model),
556
- "items": items,
557
- "workflow": {"reviewers": reviewers, "review_rounds": review_rounds}}
552
+ if not items:
553
+ return None
554
+ workflow = data.get("workflow") if isinstance(data.get("workflow"), dict) else {}
555
+ try:
556
+ reviewers = max(1, min(2, int(workflow.get("reviewers") or 1)))
557
+ except (TypeError, ValueError):
558
+ reviewers = 1
559
+ try:
560
+ review_rounds = max(1, min(3, int(workflow.get("review_rounds") or 1)))
561
+ except (TypeError, ValueError):
562
+ review_rounds = 1
563
+ return {"source": "编排者(%s · %s)" % (prov.get("name", prov["id"]), model),
564
+ "items": items,
565
+ "workflow": {"reviewers": reviewers, "review_rounds": review_rounds}}
558
566
 
559
567
 
560
568
  def make_novel_plan(task, author, critics):
package/app/ui/i18n.js CHANGED
@@ -1576,6 +1576,14 @@
1576
1576
  "商务邮件": "Business Email",
1577
1577
  "技术方案": "Tech Proposal",
1578
1578
  "简历": "Resume",
1579
+ "标书编制": "Bid Document",
1580
+ "标书/投标文件:起草 → 多维评审 → 修订循环 → 发布门禁": "Bid/tender documents: draft → multi-dim review → revision loop → publish gate",
1581
+ "招标文件(可作附件)+ 标段/包件 + 我方资质与业绩亮点": "Tender documents (attachable) + lot/package + our qualifications & track-record highlights",
1582
+ "应答完整性": "Response completeness",
1583
+ "合规符合度": "Compliance",
1584
+ "方案针对性": "Solution fit",
1585
+ "评分点覆盖": "Scoring coverage",
1586
+ "商务清晰度": "Commercial clarity",
1579
1587
  "扫榜选材": "Rank Scan for Topics",
1580
1588
  "抓取七猫排行榜公开数据 → AI 提炼热门题材/人设/差异化切入点(快档直出报告)": "Grabs public Qimao ranking data → AI distills hot genres / character archetypes / differentiation angles (fast lane, report straight out)",
1581
1589
  "想写哪个方向(一句话,可留空默认分析总榜热门题材)": "Which direction to write (one sentence; leave empty to analyze the overall hot genres)",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codebee",
3
- "version": "0.1.40",
3
+ "version": "0.1.42",
4
4
  "description": "CodeBee · 多智能体编排台 — 统一编排本机 AI 编码 CLI(Codex / Claude Code / QwenCode / OpenCode / Aider…):目标→拆解→路由→执行→验证→跨厂商评审→修复→报告。纯 Python 标准库实现,本地 Web UI。",
5
5
  "license": "MIT",
6
6
  "bin": {