codebee 0.1.61 → 0.1.62

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.
Files changed (96) hide show
  1. package/CHANGELOG.md +490 -496
  2. package/LICENSE +21 -21
  3. package/README.md +486 -486
  4. package/app/core/aiflavor.py +197 -197
  5. package/app/core/attachments.py +532 -532
  6. package/app/core/automation.py +758 -758
  7. package/app/core/backup.py +470 -470
  8. package/app/core/beekeeper.py +183 -183
  9. package/app/core/board.py +209 -209
  10. package/app/core/bookmeta_catalog.py +180 -180
  11. package/app/core/branching.py +137 -137
  12. package/app/core/builtin_agent.py +1191 -1191
  13. package/app/core/capability.py +144 -144
  14. package/app/core/chaptersafe.py +32 -32
  15. package/app/core/cleanup.py +303 -303
  16. package/app/core/compaction.py +186 -186
  17. package/app/core/covergen.py +356 -356
  18. package/app/core/diagnostics.py +114 -114
  19. package/app/core/dispatch.py +173 -173
  20. package/app/core/dispatch_log.py +113 -113
  21. package/app/core/env_scrub.py +85 -85
  22. package/app/core/error_codes.py +64 -64
  23. package/app/core/errorlog.py +137 -137
  24. package/app/core/flows.py +355 -355
  25. package/app/core/goal_service.py +167 -167
  26. package/app/core/health.py +341 -341
  27. package/app/core/history.py +38 -38
  28. package/app/core/hooks.py +156 -156
  29. package/app/core/jobs.py +1022 -1022
  30. package/app/core/manager.py +1996 -1996
  31. package/app/core/market.py +362 -362
  32. package/app/core/market_remote.py +66 -66
  33. package/app/core/mocks.py +64 -64
  34. package/app/core/modelhub.py +3266 -3266
  35. package/app/core/notify.py +106 -106
  36. package/app/core/paihang.py +109 -109
  37. package/app/core/paths.py +2 -2
  38. package/app/core/pipeline.py +4426 -4384
  39. package/app/core/planner.py +27 -27
  40. package/app/core/portguard.py +111 -111
  41. package/app/core/preview.py +293 -293
  42. package/app/core/publish/__init__.py +7 -7
  43. package/app/core/publish/auto.py +367 -367
  44. package/app/core/publish/browser.py +533 -533
  45. package/app/core/publish/fanqie.py +128 -128
  46. package/app/core/publish/flow.py +406 -406
  47. package/app/core/publish/ledger.py +198 -198
  48. package/app/core/publish/manager.py +590 -590
  49. package/app/core/publish/qimao.py +141 -141
  50. package/app/core/publish/ws.py +139 -139
  51. package/app/core/redact.py +38 -38
  52. package/app/core/repeat_guard.py +124 -124
  53. package/app/core/router.py +307 -307
  54. package/app/core/selfupdate.py +384 -384
  55. package/app/core/session_log.py +161 -161
  56. package/app/core/sessions.py +312 -312
  57. package/app/core/settings.py +149 -149
  58. package/app/core/settings_schema.py +262 -262
  59. package/app/core/share_page.py +114 -114
  60. package/app/core/skill_scan.py +106 -95
  61. package/app/core/skillpacks/fanqie-novel.md +80 -80
  62. package/app/core/skillpacks/market/character-bible.md +66 -66
  63. package/app/core/skillpacks/market/code-risk-checklist.md +58 -58
  64. package/app/core/skillpacks/market/git-workflow.md +57 -57
  65. package/app/core/skillpacks/market/release-notes.md +72 -72
  66. package/app/core/skillpacks/market/weekly-report.md +71 -71
  67. package/app/core/skillpacks/market/worldview-consistency.md +70 -70
  68. package/app/core/step_runner.py +132 -132
  69. package/app/core/task_compile.py +199 -199
  70. package/app/core/telemetry.py +292 -292
  71. package/app/core/tlsctx.py +64 -64
  72. package/app/core/token_meter.py +147 -147
  73. package/app/core/volumes.py +355 -355
  74. package/app/core/wxdigest.py +858 -858
  75. package/app/core/zentao.py +2090 -2090
  76. package/app/main.py +2826 -2826
  77. package/app/pick_dialog.py +110 -110
  78. package/app/ui/app.js +13977 -13977
  79. package/app/ui/board.html +162 -162
  80. package/app/ui/board.js +305 -305
  81. package/app/ui/i18n.js +2711 -2711
  82. package/app/ui/icons/bee.svg +79 -79
  83. package/app/ui/index.html +1 -1
  84. package/app/ui/manifest.json +16 -16
  85. package/app/ui/qrcode.js +2297 -2297
  86. package/app/ui/style.css +6125 -6125
  87. package/bin/tutti.js +147 -147
  88. package/package.json +41 -41
  89. package/tools/wx_reader.py +261 -261
  90. package/app/.playwright-cli/console-2026-09-23T08-26-03-635Z.log +0 -88
  91. package/app/.playwright-cli/page-2026-09-23T08-26-03-845Z.yml +0 -126
  92. package/app/.playwright-cli/page-2026-09-23T08-29-44-203Z.png +0 -0
  93. package/app/.playwright-cli/page-2026-09-23T08-35-42-046Z.yml +0 -210
  94. package/app/.playwright-cli/page-2026-09-23T08-36-52-858Z.png +0 -0
  95. package/app/.playwright-cli/page-2026-09-23T08-37-33-938Z.png +0 -0
  96. package/app/.playwright-cli/run-detail-after.png +0 -0
@@ -69,7 +69,7 @@ def _log_streamer(log_path, min_chars=400, min_secs=0.8):
69
69
  return cb
70
70
 
71
71
 
72
- def _outline_timeout():
72
+ def _outline_timeout():
73
73
  """大纲/规划的 CLI 兜底超时(秒):env TUTTI_OUTLINE_TIMEOUT(秒)优先,
74
74
  其次设置 orchestrator.outline_timeout_s,缺省 900。"""
75
75
  try:
@@ -86,16 +86,16 @@ def _outline_timeout():
86
86
  return v
87
87
  except Exception:
88
88
  pass
89
- return DEFAULT_OUTLINE_TIMEOUT
90
-
91
-
92
- def _deadline_timeout(deadline, requested):
93
- if deadline is None:
94
- return requested
95
- try:
96
- return max(1, min(float(requested), deadline - time.monotonic()))
97
- except (TypeError, ValueError):
98
- return requested
89
+ return DEFAULT_OUTLINE_TIMEOUT
90
+
91
+
92
+ def _deadline_timeout(deadline, requested):
93
+ if deadline is None:
94
+ return requested
95
+ try:
96
+ return max(1, min(float(requested), deadline - time.monotonic()))
97
+ except (TypeError, ValueError):
98
+ return requested
99
99
 
100
100
 
101
101
  def _log_usage(source, role, task, res, agent=None, tool="", model="", provider="",
@@ -355,8 +355,8 @@ def book_volume_plan(task, outline, upto):
355
355
  return volumes.merge_titles(plan, named)
356
356
 
357
357
 
358
- def make_serial_outline(task, author_agent=None, workdir=None, ev=None, log_path=None,
359
- deadline=None):
358
+ def make_serial_outline(task, author_agent=None, workdir=None, ev=None, log_path=None,
359
+ deadline=None):
360
360
  """连载大纲:编排者 API 优先 → 作者 CLI → 模板。返回 {book_title, chapters:[{title,beats,hook}]}。
361
361
 
362
362
  续写批次(serial.start_chapter > 1):改用续写大纲提示词,注入前情大纲与
@@ -453,8 +453,8 @@ def make_serial_outline(task, author_agent=None, workdir=None, ev=None, log_path
453
453
  # prompt 逐字节相同,命中省一次全量规划调用。第 2 次尝试故意
454
454
  # 旁路缓存——首试 ok 但 JSON 不合法时,重试要的是「再抽一次样
455
455
  # 本」而不是同一份坏文本。
456
- res = modelhub.chat(prov["id"], model, prompt,
457
- max_tokens=16000, timeout=_deadline_timeout(deadline, 300), on_delta=cb,
456
+ res = modelhub.chat(prov["id"], model, prompt,
457
+ max_tokens=16000, timeout=_deadline_timeout(deadline, 300), on_delta=cb,
458
458
  reasoning_effort=_reasoning_effort(task),
459
459
  cache_ttl=3600 if _attempt == 1 else 0)
460
460
  cb.flush()
@@ -483,8 +483,8 @@ def make_serial_outline(task, author_agent=None, workdir=None, ev=None, log_path
483
483
  _append_log(log_path, "===== 作者 CLI(%s)=====" % author_agent.get("id", "?"))
484
484
  res = runner.run_agent(modelhub.bind_agent(author_agent), prompt,
485
485
  workdir=workdir or task.get("workdir"), readonly=True,
486
- timeout=_deadline_timeout(deadline, _outline_timeout()),
487
- deadline=deadline, cancel_event=ev,
486
+ timeout=_deadline_timeout(deadline, _outline_timeout()),
487
+ deadline=deadline, cancel_event=ev,
488
488
  log_path=log_path)
489
489
  _log_usage("outline", "outline", task, res, agent=author_agent)
490
490
  if not res["ok"]:
@@ -602,13 +602,13 @@ def _reasoning_effort(task):
602
602
  return "medium"
603
603
 
604
604
 
605
- def make_code_plan(task, planner_agent, workdir, ev=None, resume=None, log_path=None,
606
- deadline=None):
605
+ def make_code_plan(task, planner_agent, workdir, ev=None, resume=None, log_path=None,
606
+ deadline=None):
607
607
  """代码任务计划:编排者 API 优先 → CLI 智能体 → 单步模板。"""
608
608
  orch = _orchestrator()
609
609
  if orch:
610
- plan = _orch_code_plan(task, orch[0], orch[1], log_path=log_path,
611
- deadline=deadline)
610
+ plan = _orch_code_plan(task, orch[0], orch[1], log_path=log_path,
611
+ deadline=deadline)
612
612
  if plan:
613
613
  return plan
614
614
  if planner_agent is None:
@@ -620,8 +620,8 @@ def make_code_plan(task, planner_agent, workdir, ev=None, resume=None, log_path=
620
620
  .replace("__CONTEXT__", task.get("context") or "(无)")
621
621
  .replace("__VERIFY__", task.get("verify_command") or "(未配置)"))
622
622
  res = runner.run_agent(planner_agent, prompt, workdir=workdir, readonly=True,
623
- timeout=_deadline_timeout(deadline, 300), deadline=deadline,
624
- cancel_event=ev, resume=resume,
623
+ timeout=_deadline_timeout(deadline, 300), deadline=deadline,
624
+ cancel_event=ev, resume=resume,
625
625
  log_path=log_path)
626
626
  _log_usage("plan", "plan", task, res, agent=planner_agent)
627
627
  data = runner.extract_json(res.get("text") or "")
@@ -638,7 +638,7 @@ def _fallback_code_plan(task, note):
638
638
  "steps": [{"title": "实现任务", "detail": task["goal"]}]}
639
639
 
640
640
 
641
- def _orch_code_plan(task, prov, model, log_path=None, deadline=None):
641
+ def _orch_code_plan(task, prov, model, log_path=None, deadline=None):
642
642
  cb = _log_streamer(log_path)
643
643
  # §07 T2.2:代码计划同任务同 prompt(断点续跑/重试),TTL 1h 精确缓存
644
644
  res = modelhub.chat(prov["id"], model,
@@ -646,7 +646,7 @@ def _orch_code_plan(task, prov, model, log_path=None, deadline=None):
646
646
  .replace("__GOAL__", task["goal"])
647
647
  .replace("__CONTEXT__", task.get("context") or "(无)")
648
648
  .replace("__VERIFY__", task.get("verify_command") or "(未配置)")),
649
- max_tokens=8000, timeout=_deadline_timeout(deadline, 300), on_delta=cb,
649
+ max_tokens=8000, timeout=_deadline_timeout(deadline, 300), on_delta=cb,
650
650
  reasoning_effort=_reasoning_effort(task),
651
651
  cache_ttl=3600)
652
652
  cb.flush()
@@ -666,7 +666,7 @@ def _orch_code_plan(task, prov, model, log_path=None, deadline=None):
666
666
  "workflow": _plan_workflow(data)}
667
667
 
668
668
 
669
- def make_review_outline(task, deadline=None):
669
+ def make_review_outline(task, deadline=None):
670
670
  """review 类任务:编排者产出写作大纲(失败返回 None,起草退回无大纲)。"""
671
671
  orch = _orchestrator()
672
672
  if not orch:
@@ -676,7 +676,7 @@ def make_review_outline(task, deadline=None):
676
676
  (REVIEW_OUTLINE_PROMPT
677
677
  .replace("__GOAL__", task["goal"])
678
678
  .replace("__CONTEXT__", task.get("context") or "(无)")),
679
- max_tokens=8000, timeout=_deadline_timeout(deadline, 300),
679
+ max_tokens=8000, timeout=_deadline_timeout(deadline, 300),
680
680
  reasoning_effort=_reasoning_effort(task),
681
681
  cache_ttl=3600) # §07 T2.2:同任务重试幂等,TTL 1h
682
682
  _log_usage("outline", "outline", task, res, model=model,
@@ -1,111 +1,111 @@
1
- # -*- coding: utf-8 -*-
2
- """启动端口清场(用户拍板 2026-09-21):端口被占时先清场再启动。
3
-
4
- - **自家旧实例**(命令行含本包 main.py 完整路径,或 npm 包内 app/main.py)
5
- → 杀树(TerminateProcess 直杀)——升级/重启最常见的占用者就是没退干净
6
- 的 CodeBee 自己;控制台进程对温和信号(WM_CLOSE)无反应,温和关不掉
7
- 正是用户「旧进程太难杀」的痛点,所以自家实例直接强杀。
8
- - **别人的进程** → 只报告占用者,不发送任何信号;启动流程不得替用户
9
- 关闭可能正在开发中的服务。
10
- - 系统/自身进程拒关(portscan 内置护栏)。
11
- """
12
- from __future__ import annotations
13
-
14
- import logging
15
- import os
16
- import shlex
17
- import subprocess
18
-
19
- from . import portscan, runner
20
-
21
- log = logging.getLogger(__name__)
22
-
23
- PS_EXE = "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe"
24
-
25
-
26
- def proc_cmdline(pid):
27
- """读进程命令行;失败返回空串。仅用于确认旧 CodeBee 实例身份。"""
28
- try:
29
- pid = int(pid)
30
- except (TypeError, ValueError):
31
- return ""
32
- if pid <= 0:
33
- return ""
34
- if os.name != "nt":
35
- try:
36
- with open("/proc/%d/cmdline" % pid, "rb") as fh:
37
- return fh.read().decode("utf-8", "replace").replace("\x00", " ").strip()
38
- except Exception:
39
- return ""
40
- try:
41
- r = subprocess.run(
42
- [PS_EXE, "-NoProfile", "-Command",
43
- "(Get-CimInstance Win32_Process -Filter 'ProcessId = %d').CommandLine" % pid],
44
- capture_output=True, timeout=10)
45
- return r.stdout.decode("utf-8", "replace").strip() if r.returncode == 0 else ""
46
- except Exception:
47
- return ""
48
-
49
-
50
- def _command_tokens(cmdline):
51
- try:
52
- return [token.strip().strip('"').strip("'")
53
- for token in shlex.split(str(cmdline or ""), posix=False)]
54
- except (TypeError, ValueError):
55
- return []
56
-
57
-
58
- def is_own_instance(cmdline, main_script, port=None):
59
- """按启动器、脚本独立参数和端口精确确认 CodeBee 实例。"""
60
- tokens = _command_tokens(cmdline)
61
- if len(tokens) < 2:
62
- return False
63
- launcher = os.path.splitext(os.path.basename(tokens[0]))[0].lower()
64
- if launcher not in ("python", "python3", "py"):
65
- return False
66
- script = ""
67
- for token in tokens[1:]:
68
- if token.startswith("-"):
69
- continue
70
- script = token
71
- break
72
- if not script or not os.path.isabs(script):
73
- return False
74
- actual = os.path.normcase(os.path.normpath(os.path.abspath(script)))
75
- expected = os.path.normcase(os.path.normpath(os.path.abspath(str(main_script))))
76
- packaged = actual.replace("\\", "/").lower().endswith(
77
- "/node_modules/codebee/app/main.py")
78
- if actual != expected and not packaged:
79
- return False
80
- if port is None:
81
- return True
82
- try:
83
- wanted = str(int(port))
84
- except (TypeError, ValueError):
85
- return False
86
- explicit_port = None
87
- for i, token in enumerate(tokens):
88
- if token == "--port" and i + 1 < len(tokens):
89
- explicit_port = tokens[i + 1]
90
- elif token.startswith("--port="):
91
- explicit_port = token.split("=", 1)[1]
92
- # argparse 默认端口可不出现在旧实例参数中;非默认端口必须显式相符。
93
- return ((explicit_port == wanted) if explicit_port is not None
94
- else wanted == "8765")
95
-
96
-
97
- def clear_stale_port(port, main_script):
98
- """清掉占用端口的进程。返回 (是否清掉, 人话说明)。"""
99
- holders = [p for p in portscan.listening_ports() if p.get("port") == int(port)]
100
- if not holders:
101
- return True, ""
102
- pid = int(holders[0].get("pid") or 0)
103
- if pid <= 4 or pid == os.getpid():
104
- return False, "占用者是系统进程/自身,拒绝清理"
105
- if is_own_instance(proc_cmdline(pid), main_script, port=port):
106
- try:
107
- runner._kill_tree(pid)
108
- return True, "已结束旧实例 PID %d" % pid
109
- except Exception as e:
110
- return False, "旧实例 PID %d 清理失败 %s" % (pid, str(e)[:80])
111
- return False, "占用者非 CodeBee(PID %d),未自动关闭" % pid
1
+ # -*- coding: utf-8 -*-
2
+ """启动端口清场(用户拍板 2026-09-21):端口被占时先清场再启动。
3
+
4
+ - **自家旧实例**(命令行含本包 main.py 完整路径,或 npm 包内 app/main.py)
5
+ → 杀树(TerminateProcess 直杀)——升级/重启最常见的占用者就是没退干净
6
+ 的 CodeBee 自己;控制台进程对温和信号(WM_CLOSE)无反应,温和关不掉
7
+ 正是用户「旧进程太难杀」的痛点,所以自家实例直接强杀。
8
+ - **别人的进程** → 只报告占用者,不发送任何信号;启动流程不得替用户
9
+ 关闭可能正在开发中的服务。
10
+ - 系统/自身进程拒关(portscan 内置护栏)。
11
+ """
12
+ from __future__ import annotations
13
+
14
+ import logging
15
+ import os
16
+ import shlex
17
+ import subprocess
18
+
19
+ from . import portscan, runner
20
+
21
+ log = logging.getLogger(__name__)
22
+
23
+ PS_EXE = "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe"
24
+
25
+
26
+ def proc_cmdline(pid):
27
+ """读进程命令行;失败返回空串。仅用于确认旧 CodeBee 实例身份。"""
28
+ try:
29
+ pid = int(pid)
30
+ except (TypeError, ValueError):
31
+ return ""
32
+ if pid <= 0:
33
+ return ""
34
+ if os.name != "nt":
35
+ try:
36
+ with open("/proc/%d/cmdline" % pid, "rb") as fh:
37
+ return fh.read().decode("utf-8", "replace").replace("\x00", " ").strip()
38
+ except Exception:
39
+ return ""
40
+ try:
41
+ r = subprocess.run(
42
+ [PS_EXE, "-NoProfile", "-Command",
43
+ "(Get-CimInstance Win32_Process -Filter 'ProcessId = %d').CommandLine" % pid],
44
+ capture_output=True, timeout=10)
45
+ return r.stdout.decode("utf-8", "replace").strip() if r.returncode == 0 else ""
46
+ except Exception:
47
+ return ""
48
+
49
+
50
+ def _command_tokens(cmdline):
51
+ try:
52
+ return [token.strip().strip('"').strip("'")
53
+ for token in shlex.split(str(cmdline or ""), posix=False)]
54
+ except (TypeError, ValueError):
55
+ return []
56
+
57
+
58
+ def is_own_instance(cmdline, main_script, port=None):
59
+ """按启动器、脚本独立参数和端口精确确认 CodeBee 实例。"""
60
+ tokens = _command_tokens(cmdline)
61
+ if len(tokens) < 2:
62
+ return False
63
+ launcher = os.path.splitext(os.path.basename(tokens[0]))[0].lower()
64
+ if launcher not in ("python", "python3", "py"):
65
+ return False
66
+ script = ""
67
+ for token in tokens[1:]:
68
+ if token.startswith("-"):
69
+ continue
70
+ script = token
71
+ break
72
+ if not script or not os.path.isabs(script):
73
+ return False
74
+ actual = os.path.normcase(os.path.normpath(os.path.abspath(script)))
75
+ expected = os.path.normcase(os.path.normpath(os.path.abspath(str(main_script))))
76
+ packaged = actual.replace("\\", "/").lower().endswith(
77
+ "/node_modules/codebee/app/main.py")
78
+ if actual != expected and not packaged:
79
+ return False
80
+ if port is None:
81
+ return True
82
+ try:
83
+ wanted = str(int(port))
84
+ except (TypeError, ValueError):
85
+ return False
86
+ explicit_port = None
87
+ for i, token in enumerate(tokens):
88
+ if token == "--port" and i + 1 < len(tokens):
89
+ explicit_port = tokens[i + 1]
90
+ elif token.startswith("--port="):
91
+ explicit_port = token.split("=", 1)[1]
92
+ # argparse 默认端口可不出现在旧实例参数中;非默认端口必须显式相符。
93
+ return ((explicit_port == wanted) if explicit_port is not None
94
+ else wanted == "8765")
95
+
96
+
97
+ def clear_stale_port(port, main_script):
98
+ """清掉占用端口的进程。返回 (是否清掉, 人话说明)。"""
99
+ holders = [p for p in portscan.listening_ports() if p.get("port") == int(port)]
100
+ if not holders:
101
+ return True, ""
102
+ pid = int(holders[0].get("pid") or 0)
103
+ if pid <= 4 or pid == os.getpid():
104
+ return False, "占用者是系统进程/自身,拒绝清理"
105
+ if is_own_instance(proc_cmdline(pid), main_script, port=port):
106
+ try:
107
+ runner._kill_tree(pid)
108
+ return True, "已结束旧实例 PID %d" % pid
109
+ except Exception as e:
110
+ return False, "旧实例 PID %d 清理失败 %s" % (pid, str(e)[:80])
111
+ return False, "占用者非 CodeBee(PID %d),未自动关闭" % pid