codebee 0.1.66 → 0.1.68

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 (116) hide show
  1. package/CHANGELOG.md +543 -520
  2. package/LICENSE +21 -21
  3. package/README.md +11 -7
  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 -0
  12. package/app/core/aiflavor.py +197 -197
  13. package/app/core/artifacts.py +148 -148
  14. package/app/core/assets.py +110 -0
  15. package/app/core/attachments.py +623 -532
  16. package/app/core/automation.py +758 -758
  17. package/app/core/backup.py +470 -470
  18. package/app/core/beekeeper.py +183 -183
  19. package/app/core/board.py +209 -209
  20. package/app/core/bookmeta.py +930 -796
  21. package/app/core/bookmeta_catalog.py +180 -180
  22. package/app/core/branching.py +137 -137
  23. package/app/core/builtin_agent.py +1234 -1234
  24. package/app/core/capability.py +144 -144
  25. package/app/core/catalog.py +42 -0
  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 -0
  31. package/app/core/diagnostics.py +114 -114
  32. package/app/core/dispatch.py +173 -173
  33. package/app/core/dispatch_log.py +128 -113
  34. package/app/core/env_scrub.py +85 -85
  35. package/app/core/error_codes.py +23 -23
  36. package/app/core/errorlog.py +137 -137
  37. package/app/core/evaluation.py +174 -174
  38. package/app/core/failure_notify.py +290 -290
  39. package/app/core/flows.py +48 -2
  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/jobs.py +46 -26
  45. package/app/core/knowledge.py +57 -3
  46. package/app/core/manager.py +2122 -2115
  47. package/app/core/market.py +362 -362
  48. package/app/core/market_remote.py +124 -26
  49. package/app/core/mocks.py +64 -64
  50. package/app/core/modelhub.py +51 -3
  51. package/app/core/notify.py +106 -106
  52. package/app/core/operations.py +228 -0
  53. package/app/core/paihang.py +138 -109
  54. package/app/core/paths.py +2 -2
  55. package/app/core/pipeline.py +221 -12
  56. package/app/core/planner.py +6 -1
  57. package/app/core/portguard.py +111 -111
  58. package/app/core/preview.py +293 -293
  59. package/app/core/publish/__init__.py +7 -7
  60. package/app/core/publish/auto.py +367 -367
  61. package/app/core/publish/browser.py +539 -533
  62. package/app/core/publish/fanqie.py +159 -128
  63. package/app/core/publish/flow.py +440 -432
  64. package/app/core/publish/flows-fanqie-calibrated.json +332 -332
  65. package/app/core/publish/flows-qimao-calibrated.json +7 -3
  66. package/app/core/publish/ledger.py +203 -198
  67. package/app/core/publish/manager.py +692 -623
  68. package/app/core/publish/qimao.py +141 -141
  69. package/app/core/publish/ws.py +139 -139
  70. package/app/core/redact.py +38 -38
  71. package/app/core/repeat_guard.py +129 -129
  72. package/app/core/revisions.py +51 -0
  73. package/app/core/router.py +398 -398
  74. package/app/core/runner.py +2067 -2050
  75. package/app/core/selfupdate.py +384 -384
  76. package/app/core/session_log.py +161 -161
  77. package/app/core/sessions.py +312 -312
  78. package/app/core/settings.py +149 -149
  79. package/app/core/settings_schema.py +262 -262
  80. package/app/core/share_page.py +114 -114
  81. package/app/core/skill_scan.py +122 -114
  82. package/app/core/skillpacks/fanqie-novel.md +80 -80
  83. package/app/core/skillpacks/market/character-bible.md +66 -66
  84. package/app/core/skillpacks/market/code-risk-checklist.md +58 -58
  85. package/app/core/skillpacks/market/git-workflow.md +57 -57
  86. package/app/core/skillpacks/market/release-notes.md +72 -72
  87. package/app/core/skillpacks/market/weekly-report.md +71 -71
  88. package/app/core/skillpacks/market/worldview-consistency.md +70 -70
  89. package/app/core/skills.py +109 -2
  90. package/app/core/step_runner.py +132 -132
  91. package/app/core/store.py +278 -41
  92. package/app/core/task_compile.py +199 -199
  93. package/app/core/telemetry.py +292 -292
  94. package/app/core/tlsctx.py +64 -64
  95. package/app/core/token_meter.py +147 -147
  96. package/app/core/tracing.py +144 -0
  97. package/app/core/upstream.py +41 -41
  98. package/app/core/usage.py +862 -858
  99. package/app/core/volumes.py +389 -355
  100. package/app/core/wxdigest.py +858 -858
  101. package/app/core/zentao.py +2465 -2447
  102. package/app/main.py +2981 -2838
  103. package/app/pick_dialog.py +110 -110
  104. package/app/ui/app.js +14399 -13994
  105. package/app/ui/artdiff.js +74 -0
  106. package/app/ui/board.html +162 -162
  107. package/app/ui/board.js +305 -305
  108. package/app/ui/i18n.js +2770 -2716
  109. package/app/ui/icons/bee.svg +79 -79
  110. package/app/ui/index.html +6 -0
  111. package/app/ui/manifest.json +16 -16
  112. package/app/ui/qrcode.js +2297 -2297
  113. package/app/ui/style.css +96 -24
  114. package/bin/tutti.js +147 -147
  115. package/package.json +46 -46
  116. package/tools/wx_reader.py +261 -261
package/app/ui/style.css CHANGED
@@ -18,6 +18,7 @@
18
18
  --log-bg: #161616;
19
19
  --log-text: #c3c9d6;
20
20
  --shadow: 0 18px 48px rgba(7, 19, 38, .16);
21
+ --mask: rgba(0, 0, 0, .5);
21
22
  --mono: "Cascadia Code", Consolas, monospace;
22
23
  --font-ui: "Aptos", "Segoe UI", sans-serif;
23
24
  --font-display: "Aptos Display", "Trebuchet MS", sans-serif;
@@ -1134,7 +1135,7 @@ body.inspector-open #inspector { display: flex; }
1134
1135
  }
1135
1136
  body.inspector-open #insp-mask {
1136
1137
  display: block; position: fixed; inset: 0; z-index: 55;
1137
- background: rgba(0, 0, 0, .45);
1138
+ background: var(--mask);
1138
1139
  }
1139
1140
  }
1140
1141
 
@@ -1155,7 +1156,7 @@ body.inspector-open #inspector { display: flex; }
1155
1156
  body:not(.side-collapsed) #sidebar { transform: none; }
1156
1157
  body:not(.side-collapsed) #drawer-mask {
1157
1158
  display: block; position: fixed; inset: 0; z-index: 55;
1158
- background: rgba(0, 0, 0, .45);
1159
+ background: var(--mask);
1159
1160
  }
1160
1161
  main { padding: 18px 14px 70px; }
1161
1162
  .panel { padding: 16px; }
@@ -1424,7 +1425,7 @@ details.prow-hidden[open] > summary { margin-bottom: 6px; }
1424
1425
 
1425
1426
  /* ---- 通用弹框 ---- */
1426
1427
  .modal-wrap { position: fixed; inset: 0; z-index: 200; display: flex; align-items: center; justify-content: center; padding: 24px; }
1427
- .modal-mask { position: absolute; inset: 0; background: rgba(0, 0, 0, .5); }
1428
+ .modal-mask { position: absolute; inset: 0; background: var(--mask); }
1428
1429
  .modal {
1429
1430
  position: relative; width: 100%; max-width: 620px; max-height: calc(100vh - 48px);
1430
1431
  display: flex; flex-direction: column; background: var(--bg);
@@ -1901,7 +1902,7 @@ html[data-theme="light"] .seg-btn.active { box-shadow: 0 1px 3px rgba(0, 0, 0, .
1901
1902
  * 层级 240:高于主 modal(200)、低于 #ask(250),Esc 逐层关 */
1902
1903
  #file-pop {
1903
1904
  position: fixed; inset: 0; z-index: 240; display: flex; align-items: center; justify-content: center;
1904
- padding: 4vh 4vw; background: rgba(0, 0, 0, 0.45);
1905
+ padding: 4vh 4vw; background: var(--mask);
1905
1906
  }
1906
1907
  #file-pop.hidden { display: none; }
1907
1908
  #file-pop .fp-panel {
@@ -2386,7 +2387,7 @@ html.code-wrap #file-pop .fp-ln { white-space: pre-wrap; word-break: break-word;
2386
2387
  /* ============ 命令面板(Ctrl+K / 侧栏搜索行唤起)============ */
2387
2388
  .cmdk-mask {
2388
2389
  position: fixed; inset: 0; z-index: 300;
2389
- background: rgba(0, 0, 0, .45); backdrop-filter: blur(2px);
2390
+ background: var(--mask); backdrop-filter: blur(2px);
2390
2391
  display: flex; align-items: flex-start; justify-content: center; padding-top: 11vh;
2391
2392
  }
2392
2393
  .cmdk-mask.hidden { display: none; }
@@ -2589,15 +2590,15 @@ details.sf-dir > summary i, a.sf-file i { font-style: normal; color: var(--muted
2589
2590
  user-select: none;
2590
2591
  }
2591
2592
  .health-banner.alerting {
2592
- background: rgba(220, 38, 38, 0.14);
2593
- color: #dc2626;
2594
- border: 1px solid rgba(220, 38, 38, 0.45);
2593
+ background: color-mix(in srgb, var(--bad) 14%, transparent);
2594
+ color: var(--bad);
2595
+ border: 1px solid color-mix(in srgb, var(--bad) 45%, transparent);
2595
2596
  animation: health-pulse 2s ease-in-out infinite;
2596
2597
  }
2597
2598
  .health-banner.recovered {
2598
- background: rgba(34, 197, 94, 0.12);
2599
- color: #16a34a;
2600
- border: 1px solid rgba(34, 197, 94, 0.4);
2599
+ background: color-mix(in srgb, var(--ok) 12%, transparent);
2600
+ color: var(--ok);
2601
+ border: 1px solid color-mix(in srgb, var(--ok) 40%, transparent);
2601
2602
  }
2602
2603
  @keyframes health-pulse {
2603
2604
  0%, 100% { opacity: 1; }
@@ -2612,15 +2613,15 @@ details.sf-dir > summary i, a.sf-file i { font-style: normal; color: var(--muted
2612
2613
 
2613
2614
  /* ---- 供应商健康告警弹框:禁用厂商的危险按钮 ---- */
2614
2615
  .modal-foot button.danger {
2615
- background: rgba(220, 38, 38, 0.12);
2616
- color: #dc2626;
2617
- border: 1px solid rgba(220, 38, 38, 0.45);
2616
+ background: color-mix(in srgb, var(--bad) 12%, transparent);
2617
+ color: var(--bad);
2618
+ border: 1px solid color-mix(in srgb, var(--bad) 45%, transparent);
2618
2619
  }
2619
- .modal-foot button.danger:hover { background: rgba(220, 38, 38, 0.22); }
2620
+ .modal-foot button.danger:hover { background: color-mix(in srgb, var(--bad) 22%, transparent); }
2620
2621
 
2621
2622
  /* 运行中指挥:未消费指令的撤回按钮(贴右侧,hover 提亮) */
2622
2623
  .rd-msg .m-retract { float: right; margin-left: 8px; padding: 0 7px; font-size: 11.5px; opacity: .8; }
2623
- .rd-msg .m-retract:hover { opacity: 1; color: #dc2626; border-color: rgba(220, 38, 38, .45); }
2624
+ .rd-msg .m-retract:hover { opacity: 1; color: var(--bad); border-color: color-mix(in srgb, var(--bad) 45%, transparent); }
2624
2625
 
2625
2626
  /* 外部目录滚动分页:续页提示行 + 哨兵(追加于文件尾,防并行重排) */
2626
2627
  .mkr-more { display: flex; align-items: center; justify-content: center; gap: 10px; padding: 10px 0 2px; }
@@ -5010,7 +5011,7 @@ html::-webkit-scrollbar-track { background: transparent; }
5010
5011
 
5011
5012
  /* ---- 帮助中心(首启欢迎引导升级版):左目录右内容,随皮肤 accent ---- */
5012
5013
  .welcome-wrap { position: fixed; inset: 0; z-index: 195; display: flex; align-items: center; justify-content: center; padding: 24px; }
5013
- .welcome-mask { position: absolute; inset: 0; background: rgba(0, 0, 0, .5); }
5014
+ .welcome-mask { position: absolute; inset: 0; background: var(--mask); }
5014
5015
  .welcome-card {
5015
5016
  position: relative; display: flex; width: min(920px, 100%); height: min(620px, calc(100vh - 48px));
5016
5017
  background: var(--bg); border: 1px solid var(--border-strong);
@@ -5338,7 +5339,7 @@ body.welcome-open { overflow: hidden; }
5338
5339
  flex-direction: column; gap: 10px; min-height: 46px; }
5339
5340
  .bee-digest { border: 1px solid var(--border); border-radius: var(--r-md); padding: 8px 10px; }
5340
5341
  .bee-digest-head { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-bottom: 6px; }
5341
- .bee-tag { background: rgba(245, 185, 66, .16); color: var(--warn);
5342
+ .bee-tag { background: color-mix(in srgb, var(--warn) 16%, transparent); color: var(--warn);
5342
5343
  border-radius: 6px; padding: 1px 8px; font-size: 12px; font-weight: 600; }
5343
5344
  .bee-meta { font-size: 11.5px; color: var(--muted); }
5344
5345
  .bee-text { font-size: 12.5px; line-height: 1.55; white-space: pre-wrap;
@@ -5382,7 +5383,7 @@ body.welcome-open { overflow: hidden; }
5382
5383
  .bee-g { display: flex; align-items: center; gap: 8px; padding: 3px 6px;
5383
5384
  border-radius: 6px; cursor: pointer; font-size: 12.5px; }
5384
5385
  .bee-g:hover { background: var(--panel2); }
5385
- .bee-g.on { background: rgba(245, 185, 66, .12); }
5386
+ .bee-g.on { background: color-mix(in srgb, var(--warn) 12%, transparent); }
5386
5387
  .bee-g span:first-of-type { flex: 1; overflow: hidden; text-overflow: ellipsis;
5387
5388
  white-space: nowrap; }
5388
5389
  .bee-g input { flex: none; }
@@ -6091,10 +6092,10 @@ button.request-busy > * { opacity: .18; }
6091
6092
  .outcome-warn {
6092
6093
  margin-top: 8px;
6093
6094
  padding: 8px 10px;
6094
- border: 1px solid rgba(217, 119, 6, .45);
6095
+ border: 1px solid color-mix(in srgb, var(--warn) 45%, transparent);
6095
6096
  border-radius: 8px;
6096
- background: rgba(217, 119, 6, .08);
6097
- color: #b45309;
6097
+ background: color-mix(in srgb, var(--warn) 8%, transparent);
6098
+ color: var(--warn);
6098
6099
  font-size: 12px;
6099
6100
  line-height: 1.5;
6100
6101
  }
@@ -6102,6 +6103,77 @@ button.request-busy > * { opacity: .18; }
6102
6103
  /* 「完成·未达标」:跑完但评审未过——不用成功绿,用琥珀警告色(2026-09-23
6103
6104
  * 禅道双单实案:用户把未达标误读成修好了) */
6104
6105
  .chip.done.unqualified {
6105
- background: color-mix(in srgb, #d97706 16%, transparent);
6106
- color: #b45309;
6106
+ background: color-mix(in srgb, var(--warn) 16%, transparent);
6107
+ color: var(--warn);
6108
+ }
6109
+
6110
+ /* ---- 市场安装预览弹框(先看后装,两阶段确认;追加于文件尾防并行重排) ---- */
6111
+ .mkpv-name { font-size: 14px; font-weight: 700; margin-bottom: 4px; }
6112
+ .mkpv-meta { display: flex; flex-wrap: wrap; gap: 6px 14px; font-size: 12.5px; color: var(--muted); margin-bottom: 8px; }
6113
+ .mkpv-skills { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin: 0 0 8px; font-size: 12.5px; }
6114
+ .mkpv-sum { font-size: 12.5px; color: var(--muted); margin-bottom: 6px; }
6115
+ .mkpv-files { width: 100%; border-collapse: collapse; font-family: var(--mono); font-size: 11.5px; margin: 4px 0 10px; }
6116
+ .mkpv-files th { text-align: left; font-family: var(--font-ui); font-size: 11.5px; color: var(--muted); font-weight: 600; padding: 2px 10px 4px 0; border-bottom: 1px solid var(--border-strong); }
6117
+ .mkpv-files td { padding: 2px 10px 2px 0; border-bottom: 1px solid var(--border); max-width: 400px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
6118
+ .mkpv-files td.mkpv-chars { color: var(--muted); white-space: nowrap; text-align: right; }
6119
+ .mkpv-files td.mkpv-more, .mkpv-strip li.mkpv-more { color: var(--muted); list-style: none; }
6120
+ .mkpv-sec { font-size: 12.5px; color: var(--warn); margin: 6px 0 4px; }
6121
+ .mkpv-strip { margin: 2px 0 8px 16px; padding: 0; font-family: var(--mono); font-size: 11.5px; color: var(--muted); max-height: 110px; overflow: auto; }
6122
+ .mkpv-strip li { margin: 1px 0; }
6123
+
6124
+ /* 「已安装 N」过滤入口:active 时随 accent 提亮 */
6125
+ #mkr-installed.active { border-color: color-mix(in srgb, var(--accent) 55%, transparent); color: var(--accent); }
6126
+
6127
+ /* ---- docx 只读预览(渲染器注入的 .docx-wrapper 改随皮肤;纸面保持白——
6128
+ * 文档画布是内容不是 UI,白底是刻意的(令牌规则豁免项)) ---- */
6129
+ #file-pop .fp-docx { min-height: 100%; padding: 12px; }
6130
+ #file-pop .fp-docx .docx-wrapper { background: transparent; padding: 0; }
6131
+ #file-pop .fp-docx .docx-wrapper > section.docx {
6132
+ background: #fff; color: #000; margin-bottom: 14px;
6133
+ box-shadow: 0 2px 12px rgba(0, 0, 0, .3);
6134
+ }
6135
+ #file-pop .fp-docx-load { padding: 24px 18px; color: var(--muted); font-size: 12.5px; }
6136
+
6137
+ /* 步骤卡「实际用上游」小徽章(借鉴 WorkDSH ResolvedExecutionBinding,追加于文件尾
6138
+ * 防并行重排):与 st-model 同族但用独立类名——ui_hive_model_think 断言
6139
+ * .st-model 徽章数,复用类名会破锚点。虚线边框与模型徽章区分。 */
6140
+ .step .st-prov {
6141
+ font-size: 10.5px; color: var(--muted); opacity: .85;
6142
+ border: 1px dashed var(--border); border-radius: 999px; padding: 0 7px; line-height: 16px;
6143
+ max-width: 200px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
6144
+ }
6145
+
6146
+ /* PDF/xlsx 只读预览(成品弹窗,WorkDSH「真任务以可读产物收尾」切片;
6147
+ * 追加于文件尾防并行重排)。PDF 纸面白底与 docx 同理:文档画布是内容不是 UI */
6148
+ #file-pop .fp-pdf { padding: 12px; display: flex; flex-direction: column; align-items: center; gap: 12px; }
6149
+ #file-pop .fp-pdf canvas.fp-pdf-page {
6150
+ background: #fff; box-shadow: 0 2px 12px rgba(0, 0, 0, .3); max-width: 100%;
6151
+ }
6152
+ #file-pop .fp-xlsx { padding: 12px; overflow: auto; }
6153
+ .fp-xls-tabs { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 8px; }
6154
+ .fp-xls-tabs .fp-xls-tab.active {
6155
+ border-color: color-mix(in srgb, var(--accent) 55%, transparent);
6156
+ color: var(--accent);
6107
6157
  }
6158
+ table.fp-xls { border-collapse: collapse; font-family: var(--mono); font-size: 11.5px; }
6159
+ .fp-xls th, .fp-xls td {
6160
+ border: 1px solid var(--border); padding: 2px 8px; white-space: nowrap;
6161
+ max-width: 320px; overflow: hidden; text-overflow: ellipsis;
6162
+ }
6163
+ .fp-xls th { color: var(--muted); font-weight: 600; font-family: var(--font-ui); }
6164
+
6165
+ /* ---------------- 成品两版对比(artdiff.js + artifactDiffBody) ----------------
6166
+ * 行色复用 insp-plus/insp-minus 同源变量(var(--ok)/var(--bad)),随皮肤走。 */
6167
+ .art-diff { margin: 0; padding: 6px 0 10px; font-family: var(--mono); font-size: 12px; line-height: 1.6; }
6168
+ .art-diff .dl-ln { display: flex; gap: 8px; padding: 0 14px; white-space: pre-wrap; word-break: break-word; }
6169
+ .art-diff .dl-ln i { flex: none; width: 12px; text-align: center; font-style: normal; opacity: .75; }
6170
+ .art-diff .dl-eq { color: var(--muted); }
6171
+ .art-diff .dl-add { color: var(--ok); background: color-mix(in srgb, var(--ok) 10%, transparent); }
6172
+ .art-diff .dl-del { color: var(--bad); background: color-mix(in srgb, var(--bad) 10%, transparent); text-decoration: none; }
6173
+ .art-diff .dl-gap { padding: 4px 14px; color: var(--muted); font-family: var(--font-ui); font-size: 11px; opacity: .85; }
6174
+
6175
+ /* 任务总时限行(任务信息弹层内,rd-meta 下方独立一行;不随轮询重建) */
6176
+ .rd-timeout-row { display: flex; align-items: center; gap: 8px; padding: 8px 14px; border-top: 1px solid var(--hairline, rgba(128,128,128,.18)); font-size: 12px; }
6177
+ .rd-timeout-row label { color: var(--muted); white-space: nowrap; }
6178
+ .rd-timeout-row select { flex: 0 0 auto; min-width: 96px; }
6179
+ .rd-timeout-row select:disabled { opacity: .55; cursor: not-allowed; }
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,46 +1,46 @@
1
- {
2
- "name": "codebee",
3
- "version": "0.1.66",
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
- "bin",
12
- "tools/wx_reader.py",
13
- "CHANGELOG.md",
14
- "README.md",
15
- "LICENSE"
16
- ],
17
- "engines": {
18
- "node": ">=14"
19
- },
20
- "os": [
21
- "win32",
22
- "darwin",
23
- "linux"
24
- ],
25
- "keywords": [
26
- "agent",
27
- "orchestration",
28
- "codex",
29
- "claude",
30
- "llm",
31
- "cli",
32
- "multi-agent"
33
- ],
34
- "scripts": {
35
- "release:gate": "python scripts/release_gate.py",
36
- "release": "python scripts/release_gate.py --publish",
37
- "prepublishOnly": "python scripts/release_gate.py"
38
- },
39
- "repository": {
40
- "type": "git",
41
- "url": "git+https://github.com/Vercel-By-WXP/CodeBee.git"
42
- },
43
- "publishConfig": {
44
- "access": "public"
45
- }
46
- }
1
+ {
2
+ "name": "codebee",
3
+ "version": "0.1.68",
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
+ "bin",
12
+ "tools/wx_reader.py",
13
+ "CHANGELOG.md",
14
+ "README.md",
15
+ "LICENSE"
16
+ ],
17
+ "engines": {
18
+ "node": ">=14"
19
+ },
20
+ "os": [
21
+ "win32",
22
+ "darwin",
23
+ "linux"
24
+ ],
25
+ "keywords": [
26
+ "agent",
27
+ "orchestration",
28
+ "codex",
29
+ "claude",
30
+ "llm",
31
+ "cli",
32
+ "multi-agent"
33
+ ],
34
+ "scripts": {
35
+ "release:gate": "python scripts/release_gate.py",
36
+ "release": "python scripts/release_gate.py --publish",
37
+ "prepublishOnly": "python scripts/release_gate.py"
38
+ },
39
+ "repository": {
40
+ "type": "git",
41
+ "url": "git+https://github.com/Vercel-By-WXP/CodeBee.git"
42
+ },
43
+ "publishConfig": {
44
+ "access": "public"
45
+ }
46
+ }