codebee 0.1.70 → 0.1.71

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 (107) hide show
  1. package/CHANGELOG.md +589 -580
  2. package/LICENSE +21 -21
  3. package/README.md +509 -509
  4. package/app/.playwright-cli/console-2026-09-23T08-26-03-635Z.log +88 -0
  5. package/app/.playwright-cli/page-2026-09-23T08-26-03-845Z.yml +126 -0
  6. package/app/.playwright-cli/page-2026-09-23T08-29-44-203Z.png +0 -0
  7. package/app/.playwright-cli/page-2026-09-23T08-35-42-046Z.yml +210 -0
  8. package/app/.playwright-cli/page-2026-09-23T08-36-52-858Z.png +0 -0
  9. package/app/.playwright-cli/page-2026-09-23T08-37-33-938Z.png +0 -0
  10. package/app/.playwright-cli/run-detail-after.png +0 -0
  11. package/app/core/acceptance.py +100 -100
  12. package/app/core/aiflavor.py +197 -197
  13. package/app/core/artifacts.py +148 -148
  14. package/app/core/assets.py +110 -110
  15. package/app/core/attachments.py +623 -623
  16. package/app/core/audit.py +30 -30
  17. package/app/core/backup.py +470 -470
  18. package/app/core/beekeeper.py +183 -183
  19. package/app/core/benchstore.py +95 -95
  20. package/app/core/board.py +209 -209
  21. package/app/core/bookmeta.py +935 -935
  22. package/app/core/bookmeta_catalog.py +180 -180
  23. package/app/core/branching.py +137 -137
  24. package/app/core/builtin_agent.py +1673 -1673
  25. package/app/core/capability.py +144 -144
  26. package/app/core/chaptersafe.py +32 -32
  27. package/app/core/cleanup.py +303 -303
  28. package/app/core/compaction.py +201 -201
  29. package/app/core/covergen.py +356 -356
  30. package/app/core/defectretro.py +43 -43
  31. package/app/core/diagnostics.py +114 -114
  32. package/app/core/dispatch.py +190 -190
  33. package/app/core/dispatch_log.py +128 -128
  34. package/app/core/env_scrub.py +85 -85
  35. package/app/core/errorlog.py +137 -137
  36. package/app/core/evalbench.py +720 -720
  37. package/app/core/evaluation.py +174 -174
  38. package/app/core/failure_notify.py +290 -290
  39. package/app/core/flows.py +7 -7
  40. package/app/core/goal_service.py +167 -167
  41. package/app/core/health.py +374 -374
  42. package/app/core/history.py +38 -38
  43. package/app/core/hooks.py +162 -162
  44. package/app/core/manager.py +2143 -2143
  45. package/app/core/mocks.py +64 -64
  46. package/app/core/notify.py +201 -201
  47. package/app/core/novel_quality.py +189 -189
  48. package/app/core/operations.py +228 -228
  49. package/app/core/paihang.py +138 -138
  50. package/app/core/portguard.py +111 -111
  51. package/app/core/preview.py +293 -293
  52. package/app/core/publish/__init__.py +7 -7
  53. package/app/core/publish/auto.py +367 -367
  54. package/app/core/publish/browser.py +655 -655
  55. package/app/core/publish/fanqie.py +165 -165
  56. package/app/core/publish/flow.py +589 -589
  57. package/app/core/publish/flows-fanqie-calibrated.json +336 -336
  58. package/app/core/publish/ledger.py +229 -229
  59. package/app/core/publish/manager.py +824 -824
  60. package/app/core/publish/qimao.py +146 -146
  61. package/app/core/publish/ws.py +139 -139
  62. package/app/core/redact.py +38 -38
  63. package/app/core/repeat_guard.py +129 -129
  64. package/app/core/router.py +398 -398
  65. package/app/core/runner.py +2142 -2142
  66. package/app/core/selfupdate.py +384 -384
  67. package/app/core/session_log.py +161 -161
  68. package/app/core/sessions.py +312 -312
  69. package/app/core/settings.py +3 -2
  70. package/app/core/settings_schema.py +270 -270
  71. package/app/core/share_page.py +114 -114
  72. package/app/core/skill_scan.py +122 -122
  73. package/app/core/skillpacks/fanqie-novel.md +80 -80
  74. package/app/core/skillpacks/market/character-bible.md +66 -66
  75. package/app/core/skillpacks/market/code-risk-checklist.md +58 -58
  76. package/app/core/skillpacks/market/git-workflow.md +57 -57
  77. package/app/core/skillpacks/market/release-notes.md +72 -72
  78. package/app/core/skillpacks/market/weekly-report.md +71 -71
  79. package/app/core/skillpacks/market/worldview-consistency.md +70 -70
  80. package/app/core/step_runner.py +132 -132
  81. package/app/core/task_compile.py +199 -199
  82. package/app/core/telemetry.py +292 -292
  83. package/app/core/tlsctx.py +64 -64
  84. package/app/core/token_meter.py +147 -147
  85. package/app/core/tracing.py +144 -144
  86. package/app/core/upstream.py +41 -41
  87. package/app/core/usage.py +1035 -1035
  88. package/app/core/volumes.py +389 -389
  89. package/app/core/wxdigest.py +858 -858
  90. package/app/core/zentao.py +2614 -2614
  91. package/app/main.py +38 -3
  92. package/app/mcp_server.py +175 -175
  93. package/app/pet.py +1 -1
  94. package/app/pick_dialog.py +110 -110
  95. package/app/ui/app.js +11 -4
  96. package/app/ui/artdiff.js +74 -74
  97. package/app/ui/board.html +162 -162
  98. package/app/ui/board.js +305 -305
  99. package/app/ui/i18n.js +1 -1
  100. package/app/ui/icons/bee.svg +79 -79
  101. package/app/ui/manifest.json +16 -16
  102. package/app/ui/qrcode.js +2297 -2297
  103. package/app/ui/style.css +65 -18
  104. package/app//344/275/234/345/223/201/344/277/241/346/201/257-/344/270/203/347/214/253.md +18 -0
  105. package/bin/tutti.js +147 -147
  106. package/package.json +47 -47
  107. package/tools/wx_reader.py +261 -261
package/app/ui/style.css CHANGED
@@ -992,15 +992,15 @@ button.small { padding: 4px 10px; font-size: 12px; }
992
992
  }
993
993
  .token-gate .primary { width: 100%; padding: 10px; font-size: 14.5px; }
994
994
 
995
- /* 轻提示 */
995
+ /* 轻提示:视口正中(水平+垂直居中)。贴底时会被详情页输入条遮挡看不见 */
996
996
  .toast {
997
- position: fixed; left: 50%; bottom: 34px; transform: translateX(-50%) translateY(12px);
997
+ position: fixed; left: 50%; top: 50%; transform: translate(-50%, -46%);
998
998
  z-index: 300; max-width: min(460px, calc(100vw - 40px));
999
999
  background: var(--panel); color: var(--text); border: 1px solid var(--border-strong);
1000
1000
  border-radius: 10px; box-shadow: var(--shadow); padding: 10px 16px; font-size: 13px;
1001
1001
  opacity: 0; pointer-events: none; transition: opacity .2s, transform .2s;
1002
1002
  }
1003
- .toast.show { opacity: 1; transform: translateX(-50%) translateY(0); }
1003
+ .toast.show { opacity: 1; transform: translate(-50%, -50%); }
1004
1004
  .toast.bad { border-color: var(--bad); color: var(--bad); }
1005
1005
 
1006
1006
  /* 减少动效(无障碍;headless 截图验证也靠它定格最终态) */
@@ -1264,7 +1264,6 @@ body.inspector-open #inspector { display: flex; }
1264
1264
  input, select, textarea { font-size: 16px; }
1265
1265
  .detail-head { flex-wrap: wrap; gap: 8px; }
1266
1266
  .detail-head h2 { flex-basis: 100%; order: -1; }
1267
- .toast { bottom: 20px; }
1268
1267
  }
1269
1268
 
1270
1269
  /* ---- 模型画廊 ---- */
@@ -3002,7 +3001,7 @@ main.chat-fill #run-detail.chat-mode {
3002
3001
  #run-detail.chat-mode .rd-pane[data-pane="chat"] { flex: 1; min-height: 380px; height: auto; }
3003
3002
  .rd-chat { display: flex; flex-direction: column; height: 100%; min-height: 0; }
3004
3003
  .rd-chat-flow { flex: 1 1 auto; overflow-y: auto; display: flex; flex-direction: column; gap: 24px;
3005
- max-width: 1000px; width: 100%; margin: 0 auto; padding: 20px 12px 12px;
3004
+ max-width: min(1200px, 100%); width: 100%; margin: 0 auto; padding: 20px 12px 12px;
3006
3005
  scroll-behavior: smooth; }
3007
3006
  .chat-row { display: flex; gap: 12px; align-items: flex-start; }
3008
3007
  .chat-row.me { justify-content: flex-end; }
@@ -3044,7 +3043,7 @@ main.chat-fill #run-detail.chat-mode {
3044
3043
  /* 思考过程块(2026-09-22 用户诉求「把思考过程打印出来」):内置智能体流式抓的
3045
3044
  * 模型思维链。运行中=常开实时块(呼吸点+逐句长出);结束后=折叠摘要可回看。
3046
3045
  * 正文位等宽缩一号、弱化——它是「过程」,不能盖过下面的正式回答。 */
3047
- .chat-think { max-width: 1000px; margin: 0 0 6px; border: 1px dashed var(--border-strong);
3046
+ .chat-think { max-width: min(1200px, 100%); margin: 0 0 6px; border: 1px dashed var(--border-strong);
3048
3047
  border-radius: 10px; background: color-mix(in srgb, var(--muted) 7%, transparent);
3049
3048
  font-size: 12.5px; }
3050
3049
  .chat-think .ct-body { max-height: 240px; overflow: auto; padding: 7px 10px 8px;
@@ -3073,7 +3072,7 @@ details.chat-think[open] > summary { border-bottom: 1px dashed var(--border-stro
3073
3072
  .chat-think .ct-dot, .chat-live-text::after { animation: none; opacity: .6; }
3074
3073
  }
3075
3074
  /* 底部输入条:整块圆角卡片,+ 在左、发送在右(flex 列布局天然钉底) */
3076
- .rd-chat-bar { flex: none; max-width: 1000px; width: calc(100% - 24px); margin: 8px auto 8px;
3075
+ .rd-chat-bar { flex: none; max-width: min(1200px, 100%); width: calc(100% - 24px); margin: 8px auto 8px;
3077
3076
  border: 1px solid var(--border-strong); border-radius: 18px; background: var(--panel);
3078
3077
  padding: 8px 10px 9px; transition: border-color .15s, box-shadow .15s;
3079
3078
  box-shadow: 0 1px 6px color-mix(in srgb, var(--text) 5%, transparent); }
@@ -3414,8 +3413,8 @@ body.side-collapsed #app { grid-template-columns: 46px 0 minmax(0, 1fr); }
3414
3413
  main { padding: 24px 34px 72px; }
3415
3414
 
3416
3415
  #sub-tasks { min-height: calc(100vh - 112px); justify-content: center; gap: 12px; }
3417
- .composer { width: min(860px, 100%); max-width: 860px; margin: 0 auto; padding: 14px 0 0; }
3418
- .hero-row { justify-content: center; margin-bottom: 18px; }
3416
+ .composer { width: min(680px, 100%); max-width: 680px; margin: 0 auto; padding: 14px 0 0; }
3417
+ .hero-row { justify-content: center; margin-bottom: 30px; }
3419
3418
  .hero { color: var(--cb-ink); font-size: clamp(24px, 3vw, 31px); font-weight: 500 !important; letter-spacing: -.025em !important; text-align: center; }
3420
3419
  .form { gap: 0; }
3421
3420
  .cmp-box { overflow: visible; border: 1px solid #d9dfe4; border-radius: 18px; background: var(--cb-surface); box-shadow: 0 10px 28px rgba(41, 53, 63, .08); transition: border-color .16s ease, box-shadow .16s ease; }
@@ -4303,8 +4302,8 @@ main:has(> #page-settings > #sub-runs:not(.hidden)) { padding-bottom: 28px; }
4303
4302
  justify-content: center;
4304
4303
  }
4305
4304
  #sub-tasks .composer {
4306
- width: min(100%, 820px);
4307
- max-width: 820px;
4305
+ width: min(100%, 680px);
4306
+ max-width: 680px;
4308
4307
  margin: clamp(12px, 7vh, 72px) auto 0;
4309
4308
  padding: 0 0 36px;
4310
4309
  overflow: visible;
@@ -4458,9 +4457,14 @@ main:has(> #page-settings > #sub-runs:not(.hidden)) { padding-bottom: 28px; }
4458
4457
  height: 31px;
4459
4458
  box-shadow: none;
4460
4459
  }
4461
- /* 类型只留底部工具栏一个入口(胶囊+下拉菜单):下方快捷 chips 与其内容重复,
4462
- 不再展示;DOM 保留(测试做存在性计数,display:none 不影响)。 */
4463
- #sub-tasks .cmp-quick { display: none; }
4460
+ /* 类型快捷 chips(2026-09-29 按 ZCode 首页构图恢复展示):卡下一排居中
4461
+ ghost pills,点选即定类型;与底部工具栏入口并存,不算重复入口 */
4462
+ #sub-tasks .cmp-quick {
4463
+ display: flex;
4464
+ justify-content: center;
4465
+ flex-wrap: wrap;
4466
+ margin: 16px 0 2px;
4467
+ }
4464
4468
  #sub-tasks .cmp-chip {
4465
4469
  min-height: 28px;
4466
4470
  padding: 4px 10px;
@@ -4997,6 +5001,49 @@ main.chat-fill #run-detail.chat-mode {
4997
5001
  }
4998
5002
  main.chat-fill #run-detail.chat-mode > .rd-main { overflow: hidden; }
4999
5003
 
5004
+ /* Codex 式构图(≥1600px 宽屏):时间线在整窗居中成一窄柱,任务卡脱离栏位
5005
+ 浮在右上留白里(实底+投影),不再占一条停靠列;对话柱宽度自动给浮卡让位,
5006
+ 任何窗宽下都不重叠。<1600px 回落停靠网格,≤980px 维持竖排。 */
5007
+ @media (min-width: 1600px) {
5008
+ main.chat-fill #run-detail.chat-mode {
5009
+ --rd-card-w: clamp(248px, 13.5vw, 284px);
5010
+ --rd-card-gap: clamp(28px, 2.2vw, 40px);
5011
+ display: block;
5012
+ padding-bottom: 6px; /* 输入条往视口底压(默认 20px 呼吸太大) */
5013
+ /* 摘掉玻璃面板的 transform/backdrop-filter:它们会把 fixed 后代的包含块
5014
+ 劫持到工作台上,浮卡就没法贴视口右上角了(模糊在 92% 实底上本就不可见)。
5015
+ transform 还被 cb-rise 入场动画的 fill=both 永久钉住(动画值压过普通声明),
5016
+ 所以动画一并关掉 */
5017
+ transform: none;
5018
+ backdrop-filter: none;
5019
+ animation: none;
5020
+ }
5021
+ /* 输入条往视口底压:4298 的详情态 28px 底距 + 工作台呼吸 + 条自身边距
5022
+ 叠出来 42px 太悬浮;浮卡模式收到 ~22px(同特异性靠文件序靠后取胜) */
5023
+ main:has(> #page-settings > #sub-runs:not(.hidden)) { padding-bottom: 8px; }
5024
+ main.chat-fill #run-detail.chat-mode > .rd-main { height: 100%; }
5025
+ main.chat-fill #run-detail.chat-mode > #rd-railcol {
5026
+ position: fixed;
5027
+ top: 76px; /* topbar 64 + 呼吸 */
5028
+ right: clamp(14px, 1.5vw, 28px);
5029
+ width: var(--rd-card-w);
5030
+ max-height: calc(100vh - 92px);
5031
+ z-index: 40; /* 浮卡层级;日志抽屉(50)展开时盖过它 */
5032
+ }
5033
+ main.chat-fill #run-detail.chat-mode #rd-rail {
5034
+ background: var(--panel); /* 浮卡实底,不透出底下时间线 */
5035
+ box-shadow: 0 18px 44px rgba(6, 18, 35, .16), 0 2px 10px rgba(6, 18, 35, .08);
5036
+ }
5037
+ #run-detail.chat-mode .rd-chat-flow,
5038
+ #run-detail.chat-mode .rd-chat-bar {
5039
+ max-width: min(960px, calc(100% - 2 * (var(--rd-card-w) + var(--rd-card-gap))));
5040
+ }
5041
+ /* 对话模式里按需打开的其它分区(步骤/蜂巢…):右侧整体给浮卡让位 */
5042
+ #run-detail.chat-mode .rd-pane:not([data-pane="chat"]) {
5043
+ padding-right: calc(var(--rd-card-w) + 24px);
5044
+ }
5045
+ }
5046
+
5000
5047
  @media (max-width: 980px) {
5001
5048
  #run-detail,
5002
5049
  main.chat-fill #run-detail.chat-mode {
@@ -6462,7 +6509,7 @@ body.side-collapsed #app { grid-template-columns: 46px 0 minmax(0, 1fr); }
6462
6509
  .top-actions .pill:hover { background:#e9edf0; border-color:transparent; color:#18212b; }
6463
6510
  main { padding:24px 34px 72px; }
6464
6511
  #sub-tasks { min-height:calc(100vh - 112px); justify-content:center; gap:12px; }
6465
- .composer { width:min(860px,100%); max-width:860px; margin:0 auto; padding:14px 0 0; }
6512
+ .composer { width:min(680px,100%); max-width:680px; margin:0 auto; padding:14px 0 0; }
6466
6513
  .hero-row { justify-content:center; margin-bottom:18px; }
6467
6514
  .hero { color:#18212b; font-size:clamp(24px,3vw,31px); font-weight:500 !important; letter-spacing:-.025em !important; text-align:center; }
6468
6515
  .cmp-box { overflow:visible; border:1px solid #d9dfe4; border-radius:18px; background:#fff; box-shadow:0 10px 28px rgba(41,53,63,.08); }
@@ -6648,10 +6695,10 @@ body.side-collapsed.inspector-open #app {
6648
6695
 
6649
6696
  /* Shared page rhythm: every secondary workspace uses one readable content axis.
6650
6697
  The task composer remains its own centered surface; all other pages align
6651
- headings, cards, tables, and lists to the same 1040px column. */
6698
+ headings, cards, tables, and lists to the same content column. */
6652
6699
  #page-settings > .subpage:not(#sub-tasks) {
6653
- width: min(1040px, 100%);
6654
- max-width: 1040px;
6700
+ width: min(1440px, 100%);
6701
+ max-width: 1440px;
6655
6702
  margin-inline: auto;
6656
6703
  gap: 16px;
6657
6704
  }
@@ -0,0 +1,18 @@
1
+ # 作品信息(七猫)
2
+
3
+ > 任务:x 生成于 2026-09-28 11:21 来源:
4
+
5
+ - **作品名称**:n
6
+ - **目标读者**:男生
7
+ - **一级分类**:玄幻奇幻
8
+ - **二级分类**:东方玄幻
9
+ - **风格标签**:热血
10
+ - **角色标签**:兵王
11
+ - **情节标签**:鉴宝
12
+ - **背景标签**:诸天万界
13
+ - **主角名1**:陆沉
14
+ - **主角名2**:苏晚
15
+ - **作品状态**:(待补充)
16
+ - **作品简介**:(待补充)
17
+
18
+ > 各字段可直接复制进 七猫 建书表单;分类与标签均选自平台真实选项表。
package/bin/tutti.js CHANGED
@@ -1,147 +1,147 @@
1
- #!/usr/bin/env node
2
- 'use strict';
3
- /*
4
- * CodeBee 启动垫片:在本机找 Python 3.8+,以子进程方式运行包内 app/main.py。
5
- * npm 只负责分发与版本管理,不携带解释器——所以运行时才探测 python,
6
- * 找不到就报错并给出安装指引(不写 postinstall,避免在别人机器上制造环境事故)。
7
- *
8
- * 安全边界:
9
- * - 子进程可执行文件全部是本文件内的字面量(py / python / python3)——包括
10
- * TUTTI_PYTHON 覆盖时:它只允许在这三个字面量里选一个,绝不拼接任意路径;
11
- * - 参数数组全由字面量常量拼成,不启用 shell;
12
- * - 探测只传 `--version`(不向解释器传递任何代码);Windows 商店的 python.exe
13
- * 假占位会非零退出,被 catch 后自然跳过;
14
- * - 用户参数逐个过白名单校验(仅字母数字与 _ - . : = / @ % 空格),
15
- * 含 shell 元字符/控制字符的参数直接拒绝。
16
- */
17
- const { execFileSync, spawn } = require('child_process');
18
- const path = require('path');
19
-
20
- const MIN_MAJOR = 3;
21
- const MIN_MINOR = 8;
22
- const MAIN = path.join(__dirname, '..', 'app', 'main.py');
23
- const SAFE_ARG = /^[A-Za-z0-9_\-.:=/@% ]+$/;
24
-
25
- // 解析 `--version` 输出:形如 "Python 3.12.4",返回 {ver, major, minor} 或 null。
26
- // 纯字符串切分,不用正则匹配执行路径。
27
- function parseVersion(text) {
28
- const at = String(text).indexOf('Python ');
29
- if (at === -1) return null;
30
- const parts = String(text).slice(at + 7).trim().split(' ')[0].split('.');
31
- const major = Number(parts[0]);
32
- const minor = Number(parts[1]);
33
- if (!Number.isFinite(major) || !Number.isFinite(minor)) return null;
34
- return { ver: major + '.' + minor, major: major, minor: minor };
35
- }
36
-
37
- function probePy() {
38
- try {
39
- return parseVersion(execFileSync('py', ['-3', '--version'],
40
- { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }));
41
- } catch (e) { return null; }
42
- }
43
- function probePython() {
44
- try {
45
- return parseVersion(execFileSync('python', ['--version'],
46
- { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }));
47
- } catch (e) { return null; }
48
- }
49
- function probePython3() {
50
- try {
51
- return parseVersion(execFileSync('python3', ['--version'],
52
- { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }));
53
- } catch (e) { return null; }
54
- }
55
-
56
- // 探测顺序:win32 优先 py 启动器(跨版本选真 3.x),其余按 python3 → python。
57
- function resolvePython() {
58
- const order = process.platform === 'win32'
59
- ? [['py', probePy], ['python', probePython], ['python3', probePython3]]
60
- : [['python3', probePython3], ['python', probePython]];
61
- for (const [name, probe] of order) {
62
- const hit = probe();
63
- if (!hit) continue;
64
- if (hit.major > MIN_MAJOR || (hit.major === MIN_MAJOR && hit.minor >= MIN_MINOR)) {
65
- return { name: name };
66
- }
67
- return { tooOld: true, name: name, ver: hit.ver };
68
- }
69
- return null;
70
- }
71
-
72
- function guide() {
73
- const url = { win32: 'https://www.python.org/downloads/windows/',
74
- darwin: 'https://www.python.org/downloads/macos/',
75
- linux: 'https://docs.python.org/3/using/unix.html' }[process.platform]
76
- || 'https://www.python.org/downloads/';
77
- return 'CodeBee 需要本机安装 Python 3.8+(npm 包不含解释器)。\n'
78
- + ' · 安装:' + url + '\n'
79
- + ' · Windows 安装时勾选 “Add python.exe to PATH”(自带 py 启动器)\n'
80
- + '装好后重新运行:codebee';
81
- }
82
-
83
- const py = resolvePython();
84
- if (!py) {
85
- console.error('[CodeBee] 未找到可用的 Python。\n' + guide());
86
- process.exit(1);
87
- }
88
- if (py.tooOld) {
89
- console.error('[CodeBee] 找到 ' + py.name + '(' + py.ver + '),但版本过低。\n' + guide());
90
- process.exit(1);
91
- }
92
-
93
- const userArgs = [];
94
- for (const a of process.argv.slice(2)) {
95
- if (typeof a === 'string' && SAFE_ARG.test(a)) {
96
- userArgs.push(a);
97
- } else {
98
- console.error('[CodeBee] 参数含非法字符,已拒绝:' + String(a).slice(0, 80));
99
- process.exit(2);
100
- }
101
- }
102
-
103
- // CLI 转发契约:codebee 之后的用户参数(已过白名单)原样作为 main.py 的 argv。
104
- // 控制台日志即见:GBK 控制台下强制 UTF-8 输出(否则中文 print 触发 UnicodeEncodeError 静默卡死)。
105
- const childEnv = Object.assign({}, process.env, { PYTHONIOENCODING: 'utf-8', PYTHONUTF8: '1', PYTHONUNBUFFERED: '1' });
106
- // TUTTI_PYTHON 覆盖解释器:只允许在三个字面量里选(py/python/python3,.exe
107
- // 后缀可选、大小写不限)——环境变量永远只做「选择题」,不做「填空题」;
108
- // 机器上有多个解释器且探测选错时,用它切换。
109
- let exe = py.name;
110
- const ovRaw = String(process.env.TUTTI_PYTHON || '').trim().toLowerCase();
111
- const ov = ovRaw.endsWith('.exe') ? ovRaw.slice(0, -4) : ovRaw;
112
- if (ov === 'python') {
113
- exe = 'python';
114
- } else if (ov === 'python3') {
115
- exe = 'python3';
116
- } else if (ov === 'py') {
117
- exe = 'py';
118
- }
119
-
120
- let child;
121
- if (exe === 'py') {
122
- child = spawn('py', ['-3', MAIN, ...userArgs], { stdio: 'inherit', env: childEnv });
123
- } else if (exe === 'python') {
124
- child = spawn('python', [MAIN, ...userArgs], { stdio: 'inherit', env: childEnv });
125
- } else {
126
- child = spawn('python3', [MAIN, ...userArgs], { stdio: 'inherit', env: childEnv });
127
- }
128
- console.log('[CodeBee] 正在启动(Python: ' + exe + ')… 浏览器将自动打开 http://127.0.0.1:8765;'
129
- + '本窗口保持开着就是服务,Ctrl+C 退出。');
130
-
131
- for (const sig of ['SIGINT', 'SIGTERM']) {
132
- process.on(sig, () => { try { child.kill(sig); } catch (e) { /* 已退出 */ } });
133
- }
134
- const t0 = Date.now();
135
- child.on('exit', (code, signal) => {
136
- if (!signal && code != null && code !== 0 && Date.now() - t0 < 4000) {
137
- // 秒退:多半是 py 启动器指到了坏解释器(如损坏的 Anaconda)。给排障出口。
138
- console.error('[CodeBee] 启动失败:Python 进程 ' + (code ? '退出码 ' + code : '异常终止') + '。'
139
- + '\n · 运行 python --version 确认可用且为 3.8+'
140
- + '\n · 若装有多个 Python,可设环境变量 TUTTI_PYTHON 切换(cmd:set TUTTI_PYTHON=python)');
141
- }
142
- process.exit(signal ? 1 : (code == null ? 0 : code));
143
- });
144
- child.on('error', (e) => {
145
- console.error('[CodeBee] 无法启动 Python:' + e.message);
146
- process.exit(1);
147
- });
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+ /*
4
+ * CodeBee 启动垫片:在本机找 Python 3.8+,以子进程方式运行包内 app/main.py。
5
+ * npm 只负责分发与版本管理,不携带解释器——所以运行时才探测 python,
6
+ * 找不到就报错并给出安装指引(不写 postinstall,避免在别人机器上制造环境事故)。
7
+ *
8
+ * 安全边界:
9
+ * - 子进程可执行文件全部是本文件内的字面量(py / python / python3)——包括
10
+ * TUTTI_PYTHON 覆盖时:它只允许在这三个字面量里选一个,绝不拼接任意路径;
11
+ * - 参数数组全由字面量常量拼成,不启用 shell;
12
+ * - 探测只传 `--version`(不向解释器传递任何代码);Windows 商店的 python.exe
13
+ * 假占位会非零退出,被 catch 后自然跳过;
14
+ * - 用户参数逐个过白名单校验(仅字母数字与 _ - . : = / @ % 空格),
15
+ * 含 shell 元字符/控制字符的参数直接拒绝。
16
+ */
17
+ const { execFileSync, spawn } = require('child_process');
18
+ const path = require('path');
19
+
20
+ const MIN_MAJOR = 3;
21
+ const MIN_MINOR = 8;
22
+ const MAIN = path.join(__dirname, '..', 'app', 'main.py');
23
+ const SAFE_ARG = /^[A-Za-z0-9_\-.:=/@% ]+$/;
24
+
25
+ // 解析 `--version` 输出:形如 "Python 3.12.4",返回 {ver, major, minor} 或 null。
26
+ // 纯字符串切分,不用正则匹配执行路径。
27
+ function parseVersion(text) {
28
+ const at = String(text).indexOf('Python ');
29
+ if (at === -1) return null;
30
+ const parts = String(text).slice(at + 7).trim().split(' ')[0].split('.');
31
+ const major = Number(parts[0]);
32
+ const minor = Number(parts[1]);
33
+ if (!Number.isFinite(major) || !Number.isFinite(minor)) return null;
34
+ return { ver: major + '.' + minor, major: major, minor: minor };
35
+ }
36
+
37
+ function probePy() {
38
+ try {
39
+ return parseVersion(execFileSync('py', ['-3', '--version'],
40
+ { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }));
41
+ } catch (e) { return null; }
42
+ }
43
+ function probePython() {
44
+ try {
45
+ return parseVersion(execFileSync('python', ['--version'],
46
+ { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }));
47
+ } catch (e) { return null; }
48
+ }
49
+ function probePython3() {
50
+ try {
51
+ return parseVersion(execFileSync('python3', ['--version'],
52
+ { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }));
53
+ } catch (e) { return null; }
54
+ }
55
+
56
+ // 探测顺序:win32 优先 py 启动器(跨版本选真 3.x),其余按 python3 → python。
57
+ function resolvePython() {
58
+ const order = process.platform === 'win32'
59
+ ? [['py', probePy], ['python', probePython], ['python3', probePython3]]
60
+ : [['python3', probePython3], ['python', probePython]];
61
+ for (const [name, probe] of order) {
62
+ const hit = probe();
63
+ if (!hit) continue;
64
+ if (hit.major > MIN_MAJOR || (hit.major === MIN_MAJOR && hit.minor >= MIN_MINOR)) {
65
+ return { name: name };
66
+ }
67
+ return { tooOld: true, name: name, ver: hit.ver };
68
+ }
69
+ return null;
70
+ }
71
+
72
+ function guide() {
73
+ const url = { win32: 'https://www.python.org/downloads/windows/',
74
+ darwin: 'https://www.python.org/downloads/macos/',
75
+ linux: 'https://docs.python.org/3/using/unix.html' }[process.platform]
76
+ || 'https://www.python.org/downloads/';
77
+ return 'CodeBee 需要本机安装 Python 3.8+(npm 包不含解释器)。\n'
78
+ + ' · 安装:' + url + '\n'
79
+ + ' · Windows 安装时勾选 “Add python.exe to PATH”(自带 py 启动器)\n'
80
+ + '装好后重新运行:codebee';
81
+ }
82
+
83
+ const py = resolvePython();
84
+ if (!py) {
85
+ console.error('[CodeBee] 未找到可用的 Python。\n' + guide());
86
+ process.exit(1);
87
+ }
88
+ if (py.tooOld) {
89
+ console.error('[CodeBee] 找到 ' + py.name + '(' + py.ver + '),但版本过低。\n' + guide());
90
+ process.exit(1);
91
+ }
92
+
93
+ const userArgs = [];
94
+ for (const a of process.argv.slice(2)) {
95
+ if (typeof a === 'string' && SAFE_ARG.test(a)) {
96
+ userArgs.push(a);
97
+ } else {
98
+ console.error('[CodeBee] 参数含非法字符,已拒绝:' + String(a).slice(0, 80));
99
+ process.exit(2);
100
+ }
101
+ }
102
+
103
+ // CLI 转发契约:codebee 之后的用户参数(已过白名单)原样作为 main.py 的 argv。
104
+ // 控制台日志即见:GBK 控制台下强制 UTF-8 输出(否则中文 print 触发 UnicodeEncodeError 静默卡死)。
105
+ const childEnv = Object.assign({}, process.env, { PYTHONIOENCODING: 'utf-8', PYTHONUTF8: '1', PYTHONUNBUFFERED: '1' });
106
+ // TUTTI_PYTHON 覆盖解释器:只允许在三个字面量里选(py/python/python3,.exe
107
+ // 后缀可选、大小写不限)——环境变量永远只做「选择题」,不做「填空题」;
108
+ // 机器上有多个解释器且探测选错时,用它切换。
109
+ let exe = py.name;
110
+ const ovRaw = String(process.env.TUTTI_PYTHON || '').trim().toLowerCase();
111
+ const ov = ovRaw.endsWith('.exe') ? ovRaw.slice(0, -4) : ovRaw;
112
+ if (ov === 'python') {
113
+ exe = 'python';
114
+ } else if (ov === 'python3') {
115
+ exe = 'python3';
116
+ } else if (ov === 'py') {
117
+ exe = 'py';
118
+ }
119
+
120
+ let child;
121
+ if (exe === 'py') {
122
+ child = spawn('py', ['-3', MAIN, ...userArgs], { stdio: 'inherit', env: childEnv });
123
+ } else if (exe === 'python') {
124
+ child = spawn('python', [MAIN, ...userArgs], { stdio: 'inherit', env: childEnv });
125
+ } else {
126
+ child = spawn('python3', [MAIN, ...userArgs], { stdio: 'inherit', env: childEnv });
127
+ }
128
+ console.log('[CodeBee] 正在启动(Python: ' + exe + ')… 浏览器将自动打开 http://127.0.0.1:8765;'
129
+ + '本窗口保持开着就是服务,Ctrl+C 退出。');
130
+
131
+ for (const sig of ['SIGINT', 'SIGTERM']) {
132
+ process.on(sig, () => { try { child.kill(sig); } catch (e) { /* 已退出 */ } });
133
+ }
134
+ const t0 = Date.now();
135
+ child.on('exit', (code, signal) => {
136
+ if (!signal && code != null && code !== 0 && Date.now() - t0 < 4000) {
137
+ // 秒退:多半是 py 启动器指到了坏解释器(如损坏的 Anaconda)。给排障出口。
138
+ console.error('[CodeBee] 启动失败:Python 进程 ' + (code ? '退出码 ' + code : '异常终止') + '。'
139
+ + '\n · 运行 python --version 确认可用且为 3.8+'
140
+ + '\n · 若装有多个 Python,可设环境变量 TUTTI_PYTHON 切换(cmd:set TUTTI_PYTHON=python)');
141
+ }
142
+ process.exit(signal ? 1 : (code == null ? 0 : code));
143
+ });
144
+ child.on('error', (e) => {
145
+ console.error('[CodeBee] 无法启动 Python:' + e.message);
146
+ process.exit(1);
147
+ });
package/package.json CHANGED
@@ -1,47 +1,47 @@
1
- {
2
- "name": "codebee",
3
- "version": "0.1.70",
4
- "description": "CodeBee · 多智能体编排台 — 统一编排本机 AI 编码 CLI(Codex / Claude Code / QwenCode / OpenCode / Aider…):目标→拆解→路由→执行→验证→跨厂商评审→修复→报告。纯 Python 标准库实现,本地 Web UI。",
5
- "license": "MIT",
6
- "bin": {
7
- "codebee": "bin/tutti.js"
8
- },
9
- "files": [
10
- "app",
11
- "!app/tests",
12
- "bin",
13
- "tools/wx_reader.py",
14
- "CHANGELOG.md",
15
- "README.md",
16
- "LICENSE"
17
- ],
18
- "engines": {
19
- "node": ">=14"
20
- },
21
- "os": [
22
- "win32",
23
- "darwin",
24
- "linux"
25
- ],
26
- "keywords": [
27
- "agent",
28
- "orchestration",
29
- "codex",
30
- "claude",
31
- "llm",
32
- "cli",
33
- "multi-agent"
34
- ],
35
- "scripts": {
36
- "release:gate": "python scripts/release_gate.py",
37
- "release": "python scripts/release_gate.py --publish",
38
- "prepublishOnly": "python scripts/release_gate.py"
39
- },
40
- "repository": {
41
- "type": "git",
42
- "url": "git+https://github.com/Vercel-By-WXP/CodeBee.git"
43
- },
44
- "publishConfig": {
45
- "access": "public"
46
- }
47
- }
1
+ {
2
+ "name": "codebee",
3
+ "version": "0.1.71",
4
+ "description": "CodeBee · 多智能体编排台 — 统一编排本机 AI 编码 CLI(Codex / Claude Code / QwenCode / OpenCode / Aider…):目标→拆解→路由→执行→验证→跨厂商评审→修复→报告。纯 Python 标准库实现,本地 Web UI。",
5
+ "license": "MIT",
6
+ "bin": {
7
+ "codebee": "bin/tutti.js"
8
+ },
9
+ "files": [
10
+ "app",
11
+ "!app/tests",
12
+ "bin",
13
+ "tools/wx_reader.py",
14
+ "CHANGELOG.md",
15
+ "README.md",
16
+ "LICENSE"
17
+ ],
18
+ "engines": {
19
+ "node": ">=14"
20
+ },
21
+ "os": [
22
+ "win32",
23
+ "darwin",
24
+ "linux"
25
+ ],
26
+ "keywords": [
27
+ "agent",
28
+ "orchestration",
29
+ "codex",
30
+ "claude",
31
+ "llm",
32
+ "cli",
33
+ "multi-agent"
34
+ ],
35
+ "scripts": {
36
+ "release:gate": "python scripts/release_gate.py",
37
+ "release": "python scripts/release_gate.py --publish",
38
+ "prepublishOnly": "python scripts/release_gate.py"
39
+ },
40
+ "repository": {
41
+ "type": "git",
42
+ "url": "git+https://github.com/Vercel-By-WXP/CodeBee.git"
43
+ },
44
+ "publishConfig": {
45
+ "access": "public"
46
+ }
47
+ }