codebee 0.1.17 → 0.1.19
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 +22 -0
- package/README.md +4 -3
- package/app/core/automation.py +19 -0
- package/app/core/backup.py +470 -0
- package/app/core/bookmeta.py +4 -1
- package/app/core/builtin_agent.py +33 -1
- package/app/core/cleanup.py +303 -0
- package/app/core/flows.py +1 -1
- package/app/core/jobs.py +22 -4
- package/app/core/knowledge.py +393 -0
- package/app/core/manager.py +25 -1
- package/app/core/market.py +14 -2
- package/app/core/modelhub.py +16 -0
- package/app/core/pipeline.py +62 -9
- package/app/core/planner.py +22 -4
- package/app/core/publish/manager.py +80 -3
- package/app/core/runner.py +127 -7
- package/app/core/selfupdate.py +80 -38
- package/app/core/settings.py +32 -1
- package/app/core/skill_scan.py +86 -0
- package/app/core/store.py +62 -8
- package/app/core/wxdigest.py +710 -0
- package/app/core/zentao.py +516 -50
- package/app/main.py +276 -1
- package/app/pet.py +1323 -0
- package/app/pet_bee.png +0 -0
- package/app/pet_bee_robot.png +0 -0
- package/app/pick_dialog.py +34 -2
- package/app/ui/app.js +11063 -10267
- package/app/ui/i18n.js +194 -4
- package/app/ui/index.html +148 -1
- package/app/ui/style.css +160 -2
- package/package.json +1 -1
package/app/main.py
CHANGED
|
@@ -24,6 +24,7 @@ from core import automation, catalog, flows, jobs, manager, market, market_remot
|
|
|
24
24
|
from core import paths
|
|
25
25
|
from core import health
|
|
26
26
|
import pick_dialog
|
|
27
|
+
import pet
|
|
27
28
|
|
|
28
29
|
log = logging.getLogger(__name__)
|
|
29
30
|
|
|
@@ -233,6 +234,9 @@ class Handler(BaseHTTPRequestHandler):
|
|
|
233
234
|
if path == "/api/skills":
|
|
234
235
|
from core import skills
|
|
235
236
|
return self._json(200, skills.view())
|
|
237
|
+
if path == "/api/knowledge":
|
|
238
|
+
from core import knowledge
|
|
239
|
+
return self._json(200, knowledge.view())
|
|
236
240
|
if path == "/api/settings":
|
|
237
241
|
return self._json(200, dict(settings.load(), **jobs.workers_info()))
|
|
238
242
|
if path == "/api/settings-v2":
|
|
@@ -261,6 +265,8 @@ class Handler(BaseHTTPRequestHandler):
|
|
|
261
265
|
return self._json(200, {"ok": True, "catalog": manager.catalog_view()})
|
|
262
266
|
if path == "/api/runs":
|
|
263
267
|
return self._json(200, {"runs": store.list_runs()})
|
|
268
|
+
if path == "/api/pet_state":
|
|
269
|
+
return self._api_pet_state()
|
|
264
270
|
if path == "/api/usage":
|
|
265
271
|
from core import usage
|
|
266
272
|
q = parse_qs(urlparse(self.path).query)
|
|
@@ -462,12 +468,27 @@ class Handler(BaseHTTPRequestHandler):
|
|
|
462
468
|
if path == "/api/zentao":
|
|
463
469
|
from core import zentao
|
|
464
470
|
return self._json(200, zentao.view())
|
|
471
|
+
if path == "/api/wxdigest":
|
|
472
|
+
from core import wxdigest
|
|
473
|
+
try:
|
|
474
|
+
return self._json(200, wxdigest.view())
|
|
475
|
+
except Exception:
|
|
476
|
+
log.exception("wxdigest: view 失败")
|
|
477
|
+
return self._json(500, {"error": "群摘要视图生成失败"})
|
|
465
478
|
m = re.match(r"^/api/automation/([^/]+)$", path)
|
|
466
479
|
if m:
|
|
467
480
|
t = automation.get_task(m.group(1))
|
|
468
481
|
return self._json(200, {"task": t}) if t else self._json(404, {"error": "not found"})
|
|
469
482
|
if path == "/api/market":
|
|
470
483
|
return self._json(200, market.view())
|
|
484
|
+
if path == "/api/cleanup":
|
|
485
|
+
# 数据与备份页:清理配置 + 上次清理状态 + 当前可清理预估
|
|
486
|
+
from core import cleanup
|
|
487
|
+
try:
|
|
488
|
+
return self._json(200, cleanup.status())
|
|
489
|
+
except Exception:
|
|
490
|
+
log.exception("cleanup: status 失败")
|
|
491
|
+
return self._json(500, {"error": "清理状态生成失败"})
|
|
471
492
|
if path == "/api/market/remote":
|
|
472
493
|
q = parse_qs(urlparse(self.path).query)
|
|
473
494
|
return self._json(200, market_remote.view(
|
|
@@ -622,6 +643,9 @@ class Handler(BaseHTTPRequestHandler):
|
|
|
622
643
|
m = re.match(r"^/api/publish/task/([^/]+)/(create-book|chapter|auto-publish)$", path)
|
|
623
644
|
if m:
|
|
624
645
|
return self._api_publish_task_op(m.group(1), m.group(2))
|
|
646
|
+
m = re.match(r"^/api/publish/task/([^/]+)/register-book$", path)
|
|
647
|
+
if m:
|
|
648
|
+
return self._api_publish_register_book(m.group(1))
|
|
625
649
|
m = re.match(r"^/api/publish/task/([^/]+)/publish-all$", path)
|
|
626
650
|
if m:
|
|
627
651
|
return self._api_publish_auto(m.group(1))
|
|
@@ -835,6 +859,16 @@ class Handler(BaseHTTPRequestHandler):
|
|
|
835
859
|
from core import skills
|
|
836
860
|
n = skills.learn_from_run((self._body().get("run_id") or ""))
|
|
837
861
|
return self._json(200, {"ok": True, "learned": n})
|
|
862
|
+
if path == "/api/knowledge/op":
|
|
863
|
+
from core import knowledge
|
|
864
|
+
body = self._body()
|
|
865
|
+
err = knowledge.entry_op(body.get("id") or "", body.get("op") or "",
|
|
866
|
+
fields=body.get("fields"))
|
|
867
|
+
return self._json(400, {"error": err}) if err else self._json(200, {"ok": True})
|
|
868
|
+
if path == "/api/knowledge/learn":
|
|
869
|
+
from core import knowledge
|
|
870
|
+
n = knowledge.learn_from_run((self._body().get("run_id") or ""))
|
|
871
|
+
return self._json(200, {"ok": True, "learned": n})
|
|
838
872
|
m = re.match(r"^/api/flows/([^/]+)/reset$", path)
|
|
839
873
|
if m:
|
|
840
874
|
err = flows.reset_flow(m.group(1))
|
|
@@ -991,6 +1025,89 @@ class Handler(BaseHTTPRequestHandler):
|
|
|
991
1025
|
except ValueError as e:
|
|
992
1026
|
return self._json(400, {"error": str(e)})
|
|
993
1027
|
return self._json(200, {"ok": True, "config": cfg})
|
|
1028
|
+
if path == "/api/wxdigest/config":
|
|
1029
|
+
from core import wxdigest
|
|
1030
|
+
try:
|
|
1031
|
+
cfg = wxdigest.save_config(self._body())
|
|
1032
|
+
except ValueError as e:
|
|
1033
|
+
return self._json(400, {"error": str(e)})
|
|
1034
|
+
return self._json(200, {"ok": True, "config": cfg})
|
|
1035
|
+
if path == "/api/wxdigest/scan":
|
|
1036
|
+
from core import wxdigest
|
|
1037
|
+
res = wxdigest.scan_now() # 含模型调用,同步做(ThreadingHTTPServer 不堵别的请求)
|
|
1038
|
+
return self._json(200, dict(res, ok=bool(res.get("ok"))))
|
|
1039
|
+
if path == "/api/wxdigest/seen":
|
|
1040
|
+
from core import wxdigest
|
|
1041
|
+
return self._json(200, {"ok": True, "view": wxdigest.seen_clear()})
|
|
1042
|
+
if path == "/api/wxdigest/groups":
|
|
1043
|
+
from core import wxdigest
|
|
1044
|
+
try:
|
|
1045
|
+
groups = wxdigest.refresh_groups() # sidecar 调用,秒级到几十秒(ThreadingHTTPServer 不堵)
|
|
1046
|
+
except Exception as e:
|
|
1047
|
+
return self._json(500, {"error": str(e)})
|
|
1048
|
+
return self._json(200, {"ok": True, "groups": groups})
|
|
1049
|
+
if path == "/api/wxdigest/to-knowledge":
|
|
1050
|
+
from core import wxdigest
|
|
1051
|
+
body = self._body()
|
|
1052
|
+
try:
|
|
1053
|
+
entry = wxdigest.digest_to_knowledge(str(body.get("id") or ""))
|
|
1054
|
+
except Exception as e:
|
|
1055
|
+
return self._json(400, {"error": str(e)})
|
|
1056
|
+
return self._json(200, {"ok": True, "entry": entry})
|
|
1057
|
+
if path == "/api/cleanup":
|
|
1058
|
+
# op=run 立即清理(include_profiles=true 连浏览器缓存一起清,
|
|
1059
|
+
# 平台登录态会丢需重新扫码);否则只重扫预估不落盘
|
|
1060
|
+
from core import cleanup
|
|
1061
|
+
body = self._body()
|
|
1062
|
+
try:
|
|
1063
|
+
if body.get("op") == "run":
|
|
1064
|
+
r = cleanup.run_cleanup(
|
|
1065
|
+
include_profiles=bool(body.get("include_profiles")))
|
|
1066
|
+
return self._json(200, {"ok": True, "result": r})
|
|
1067
|
+
return self._json(200, {"ok": True,
|
|
1068
|
+
"plan": cleanup.plan(
|
|
1069
|
+
include_profiles=bool(
|
|
1070
|
+
body.get("include_profiles")))})
|
|
1071
|
+
except Exception as e:
|
|
1072
|
+
log.exception("cleanup: 执行失败")
|
|
1073
|
+
return self._json(500, {"error": "清理执行失败:%s" % e})
|
|
1074
|
+
if path == "/api/data/export":
|
|
1075
|
+
# 整机备份导出:数据目录 + 任务工作目录打包 zip(含密钥,提示用户妥善保管)
|
|
1076
|
+
from core import backup
|
|
1077
|
+
body = self._body()
|
|
1078
|
+
try:
|
|
1079
|
+
info = backup.export_data(
|
|
1080
|
+
target_dir=str(body.get("target_dir") or ""),
|
|
1081
|
+
include_logs=body.get("include_logs", True),
|
|
1082
|
+
include_workspace=body.get("include_workspace", True))
|
|
1083
|
+
except ValueError as e:
|
|
1084
|
+
return self._json(400, {"error": str(e)})
|
|
1085
|
+
except Exception as e:
|
|
1086
|
+
log.exception("backup: 导出失败")
|
|
1087
|
+
return self._json(500, {"error": "导出失败:%s" % e})
|
|
1088
|
+
return self._json(200, {"ok": True, **info})
|
|
1089
|
+
if path == "/api/data/import":
|
|
1090
|
+
# op=inspect 预览(不落盘);op=apply 应用(merge/replace + 路径重映射)
|
|
1091
|
+
from core import backup
|
|
1092
|
+
body = self._body()
|
|
1093
|
+
try:
|
|
1094
|
+
if body.get("op") == "apply":
|
|
1095
|
+
r = backup.apply_import(
|
|
1096
|
+
str(body.get("path") or ""),
|
|
1097
|
+
mode=str(body.get("mode") or "merge"),
|
|
1098
|
+
remap=body.get("remap", True),
|
|
1099
|
+
include_workspace=body.get("include_workspace", True))
|
|
1100
|
+
return self._json(200, {"ok": True, **r})
|
|
1101
|
+
return self._json(200, {"ok": True,
|
|
1102
|
+
"preview": backup.inspect_backup(
|
|
1103
|
+
str(body.get("path") or ""))})
|
|
1104
|
+
except ValueError as e:
|
|
1105
|
+
return self._json(400, {"error": str(e)})
|
|
1106
|
+
except Exception as e:
|
|
1107
|
+
log.exception("backup: 导入失败")
|
|
1108
|
+
return self._json(500, {"error": "导入失败:%s" % e})
|
|
1109
|
+
if path == "/api/pick_file":
|
|
1110
|
+
return self._api_pick_file()
|
|
994
1111
|
if path == "/api/zentao/test":
|
|
995
1112
|
from core import zentao
|
|
996
1113
|
body = self._body()
|
|
@@ -998,7 +1115,14 @@ class Handler(BaseHTTPRequestHandler):
|
|
|
998
1115
|
ok, msg = zentao.test_connection(
|
|
999
1116
|
base_url=body.get("base_url"), account=body.get("account"),
|
|
1000
1117
|
password=pw or None) # 空 = 用已保存密码
|
|
1001
|
-
|
|
1118
|
+
res = {"ok": ok, "message": msg}
|
|
1119
|
+
if ok:
|
|
1120
|
+
# 子路径部署被自动探测补全时回传有效地基,前端回填表单提醒保存
|
|
1121
|
+
eff = zentao.resolved_base_url(body.get("base_url")) or \
|
|
1122
|
+
zentao.resolved_base_url(zentao.view()["config"].get("base_url"))
|
|
1123
|
+
if eff:
|
|
1124
|
+
res["base_url"] = eff
|
|
1125
|
+
return self._json(200, res)
|
|
1002
1126
|
if path == "/api/zentao/scan":
|
|
1003
1127
|
from core import zentao
|
|
1004
1128
|
res = zentao.scan_now() # 网络操作同步做(ThreadingHTTPServer 不堵别的请求)
|
|
@@ -1008,6 +1132,12 @@ class Handler(BaseHTTPRequestHandler):
|
|
|
1008
1132
|
pid = (self._body() or {}).get("product")
|
|
1009
1133
|
res = zentao.fetch_modules(pid)
|
|
1010
1134
|
return self._json(200, dict(res, ok=bool(res.get("ok"))))
|
|
1135
|
+
if path == "/api/zentao/products":
|
|
1136
|
+
from core import zentao
|
|
1137
|
+
return self._json(200, zentao.fetch_products())
|
|
1138
|
+
if path == "/api/zentao/users":
|
|
1139
|
+
from core import zentao
|
|
1140
|
+
return self._json(200, zentao.fetch_users())
|
|
1011
1141
|
# 外部目录路由必须在通配的 market/<id>/(install|remove) 之前——
|
|
1012
1142
|
# 否则 "remote" 会被当成包名吞掉
|
|
1013
1143
|
if path == "/api/market/remote/refresh":
|
|
@@ -1175,6 +1305,20 @@ class Handler(BaseHTTPRequestHandler):
|
|
|
1175
1305
|
return self._json(400, {"error": err or "操作失败"})
|
|
1176
1306
|
return self._json(200, {"ok": True, "started": True})
|
|
1177
1307
|
|
|
1308
|
+
def _api_publish_register_book(self, task_id):
|
|
1309
|
+
"""人工登记平台已有作品(建书流程没走通/手工建书后的补账口)。"""
|
|
1310
|
+
from core import store
|
|
1311
|
+
from core.publish import manager as pub
|
|
1312
|
+
if not store.get_task(task_id):
|
|
1313
|
+
return self._json(404, {"error": "任务不存在"})
|
|
1314
|
+
body = self._body() or {}
|
|
1315
|
+
ok, err = pub.register_book(task_id, (body.get("platform") or "").strip(),
|
|
1316
|
+
str(body.get("title") or ""),
|
|
1317
|
+
book_id=str(body.get("book_id") or ""))
|
|
1318
|
+
if not ok:
|
|
1319
|
+
return self._json(400, {"error": err or "登记失败"})
|
|
1320
|
+
return self._json(200, {"ok": True})
|
|
1321
|
+
|
|
1178
1322
|
def _api_publish_auto(self, task_id):
|
|
1179
1323
|
"""批量发布全部待发章节(publish/auto.py)。
|
|
1180
1324
|
|
|
@@ -1273,6 +1417,54 @@ class Handler(BaseHTTPRequestHandler):
|
|
|
1273
1417
|
parent = "" if p.parent == p else str(p.parent)
|
|
1274
1418
|
return self._json(200, {"path": str(p), "parent": parent, "dirs": dirs})
|
|
1275
1419
|
|
|
1420
|
+
def _api_pet_state(self):
|
|
1421
|
+
"""桌面蜜蜂(app/pet.py 子进程)的喂食端点:任务近况最小集 + 相关设置。
|
|
1422
|
+
|
|
1423
|
+
数据与 /api/runs 同源(latest_run_by_task 全量扫描,内存操作),但只裁
|
|
1424
|
+
出桌宠需要的字段;每任务一条(封顶 200),蜜蜂在进程内自行差分出
|
|
1425
|
+
「刚完工/刚出事」。设置随响应回传,蜜蜂轮询到 pet_enabled=false 自行
|
|
1426
|
+
退出——关闭桌宠不需要专门的信号通道。
|
|
1427
|
+
"""
|
|
1428
|
+
st = settings.load()
|
|
1429
|
+
rows = []
|
|
1430
|
+
n_run = n_q = 0
|
|
1431
|
+
latest = store.latest_run_by_task()
|
|
1432
|
+
for tid in sorted(latest.keys(), reverse=True)[:200]:
|
|
1433
|
+
run = latest[tid]
|
|
1434
|
+
task = store.get_task(tid) or {}
|
|
1435
|
+
rs = str(run.get("status") or "")
|
|
1436
|
+
if rs == "running":
|
|
1437
|
+
n_run += 1
|
|
1438
|
+
elif rs == "queued":
|
|
1439
|
+
n_q += 1
|
|
1440
|
+
steps = run.get("steps") or []
|
|
1441
|
+
cur = next((s for s in steps if s.get("status") == "running"), None)
|
|
1442
|
+
rows.append({
|
|
1443
|
+
"id": tid,
|
|
1444
|
+
"title": str(task.get("title") or tid),
|
|
1445
|
+
"type": str(task.get("type") or ""),
|
|
1446
|
+
"run_status": rs,
|
|
1447
|
+
"steps_done": sum(1 for s in steps if s.get("status") == "done"),
|
|
1448
|
+
"steps_total": len(steps),
|
|
1449
|
+
"step_current": str((cur.get("summary") or cur.get("agent_label")
|
|
1450
|
+
or "") if cur else ""),
|
|
1451
|
+
"error": str(run.get("error") or "")[:160],
|
|
1452
|
+
})
|
|
1453
|
+
try:
|
|
1454
|
+
from core import wxdigest as _wxd # 群摘要喂食:未读数+最新一条(失败兜底零值)
|
|
1455
|
+
dg = _wxd.pet_digest()
|
|
1456
|
+
except Exception:
|
|
1457
|
+
dg = {"unseen": 0, "latest": None}
|
|
1458
|
+
return self._json(200, {
|
|
1459
|
+
"ok": True, "ts": int(time.time()), "port": PORT,
|
|
1460
|
+
"settings": {"pet_enabled": bool(st.get("pet_enabled", True)),
|
|
1461
|
+
"pet_mode": str(st.get("pet_mode") or "always"),
|
|
1462
|
+
"pet_skin": str(st.get("pet_skin") or "plush")},
|
|
1463
|
+
"workers": {"running": n_run, "queued": n_q},
|
|
1464
|
+
"tasks": rows,
|
|
1465
|
+
"digest": dg,
|
|
1466
|
+
})
|
|
1467
|
+
|
|
1276
1468
|
def _api_pick_folder(self):
|
|
1277
1469
|
"""系统原生「选择文件夹」对话框(工作目录「选择…」/点输入框用)。仅限本机:
|
|
1278
1470
|
对话框弹在服务所在机器上,远端触发等于替别人开窗。pick_dialog.ask_directory
|
|
@@ -1293,6 +1485,24 @@ class Handler(BaseHTTPRequestHandler):
|
|
|
1293
1485
|
return self._json(200, {"path": "", "fallback": fb, "error": err})
|
|
1294
1486
|
return self._json(200, {"path": path or ""})
|
|
1295
1487
|
|
|
1488
|
+
def _api_pick_file(self):
|
|
1489
|
+
"""系统原生「选择文件」对话框(导入备份包用)。仅限本机,同 _api_pick_folder;
|
|
1490
|
+
没有 tkinter 时 fallback=true(本场景没有网页回落,前端提示手输路径)。"""
|
|
1491
|
+
ip, fw = self._forwarded_ip()
|
|
1492
|
+
if ip not in ("127.0.0.1", "::1") or fw:
|
|
1493
|
+
return self._json(403, {"error": "文件选择仅限本机使用,请手动输入路径"})
|
|
1494
|
+
body = self._body()
|
|
1495
|
+
if not _PICK_LOCK.acquire(blocking=False):
|
|
1496
|
+
return self._json(200, {"path": "", "busy": True})
|
|
1497
|
+
try:
|
|
1498
|
+
path, err, fb = pick_dialog.ask_file(str(body.get("initial") or ""),
|
|
1499
|
+
str(body.get("title") or "选择文件"))
|
|
1500
|
+
finally:
|
|
1501
|
+
_PICK_LOCK.release()
|
|
1502
|
+
if err:
|
|
1503
|
+
return self._json(200, {"path": "", "fallback": fb, "error": err})
|
|
1504
|
+
return self._json(200, {"path": path or ""})
|
|
1505
|
+
|
|
1296
1506
|
def _api_dir_scan(self):
|
|
1297
1507
|
"""侧栏文件夹「查看文件」:递归列出该工作目录下的全部文件(左侧文件页树形展示)。
|
|
1298
1508
|
|
|
@@ -1970,6 +2180,56 @@ class ThreadedServer(ThreadingHTTPServer):
|
|
|
1970
2180
|
super().server_bind()
|
|
1971
2181
|
|
|
1972
2182
|
|
|
2183
|
+
# ---------------------------------------------------------------- 桌面蜜蜂
|
|
2184
|
+
|
|
2185
|
+
_PET_PROC = None # 蜜蜂子进程句柄(看护线程专用,别的线程别碰)
|
|
2186
|
+
|
|
2187
|
+
|
|
2188
|
+
def _spawn_pet(port):
|
|
2189
|
+
"""拉起桌面蜜蜂子进程(app/pet.py:tkinter 自绘,死活不影响服务)。"""
|
|
2190
|
+
global _PET_PROC
|
|
2191
|
+
pet_py = Path(__file__).resolve().parent / "pet.py"
|
|
2192
|
+
if not pet_py.exists():
|
|
2193
|
+
return
|
|
2194
|
+
env = dict(os.environ, PYTHONIOENCODING="utf-8")
|
|
2195
|
+
# CREATE_NO_WINDOW 同 pick_dialog:控制台宿主下不给蜜蜂弹黑框
|
|
2196
|
+
kw = {"creationflags": 0x08000000} if os.name == "nt" else {}
|
|
2197
|
+
try:
|
|
2198
|
+
_PET_PROC = subprocess.Popen(
|
|
2199
|
+
[sys.executable, str(pet_py), "--port", str(port),
|
|
2200
|
+
"--data-dir", str(paths.DATA_DIR)], env=env, **kw)
|
|
2201
|
+
print("[CodeBee] 桌面蜜蜂已起飞(右键蜜蜂可设置;设置页可关闭)", flush=True)
|
|
2202
|
+
except Exception as e:
|
|
2203
|
+
print("[CodeBee] 桌面蜜蜂未能起飞(不影响其他功能):%s" % e, flush=True)
|
|
2204
|
+
|
|
2205
|
+
|
|
2206
|
+
def _start_pet_keeper(port):
|
|
2207
|
+
"""蜜蜂看护线程:按设置自愈拉起(崩溃/被杀 5s 内补位)。
|
|
2208
|
+
|
|
2209
|
+
TUTTI_PET_DISABLED=1 完全静默(测试/无桌面环境);关闭走设置
|
|
2210
|
+
pet_enabled=false——蜜蜂轮询到后自离,看护线程也不再拉起,不存在
|
|
2211
|
+
「关了又复活」。子进程与服务的生命周期解耦:服务被杀蜜蜂 1 分钟内
|
|
2212
|
+
自灭(MAX_MISS),不留孤儿窗口。
|
|
2213
|
+
"""
|
|
2214
|
+
if os.environ.get("TUTTI_PET_DISABLED") == "1":
|
|
2215
|
+
return
|
|
2216
|
+
|
|
2217
|
+
def _loop():
|
|
2218
|
+
while True:
|
|
2219
|
+
try:
|
|
2220
|
+
if settings.load().get("pet_enabled", True):
|
|
2221
|
+
if _PET_PROC is None or _PET_PROC.poll() is not None:
|
|
2222
|
+
# 全机唯一蜜蜂:别的服务实例(不同数据目录)已养蜂时
|
|
2223
|
+
# 不再拉起,否则桌面会出现多只(用户实测踩坑)
|
|
2224
|
+
if not pet.global_lock_held():
|
|
2225
|
+
_spawn_pet(port)
|
|
2226
|
+
except Exception:
|
|
2227
|
+
pass
|
|
2228
|
+
time.sleep(5)
|
|
2229
|
+
|
|
2230
|
+
threading.Thread(target=_loop, name="pet-keeper", daemon=True).start()
|
|
2231
|
+
|
|
2232
|
+
|
|
1973
2233
|
def main():
|
|
1974
2234
|
parser = argparse.ArgumentParser(description="CodeBee 多智能体编排台")
|
|
1975
2235
|
parser.add_argument("--host", default="0.0.0.0",
|
|
@@ -2034,6 +2294,13 @@ def main():
|
|
|
2034
2294
|
n_lc = skills.migrate_lesson_categories() # 分类字段上线前的教训按关键词回填(幂等,带备份)
|
|
2035
2295
|
if n_lc:
|
|
2036
2296
|
print("[CodeBee] 经验库:%d 条历史教训已自动归类" % n_lc)
|
|
2297
|
+
try:
|
|
2298
|
+
from core import knowledge
|
|
2299
|
+
n_kd = knowledge.migrate_drafts_approved() # 草稿闸门退役:历史草稿一次性转正(幂等)
|
|
2300
|
+
if n_kd:
|
|
2301
|
+
print("[CodeBee] 知识库:%d 条历史草稿已转正(默认直接参与注入)" % n_kd)
|
|
2302
|
+
except Exception:
|
|
2303
|
+
pass
|
|
2037
2304
|
from core import usage
|
|
2038
2305
|
_step("正在回填用量台账…")
|
|
2039
2306
|
n_bf = usage.backfill_from_runs() # 历史运行 token 回填台账(幂等,仅补缺失步骤)
|
|
@@ -2107,6 +2374,13 @@ def main():
|
|
|
2107
2374
|
print("[CodeBee] 禅道集成:%d 条认领记录已加载" % n_zt)
|
|
2108
2375
|
except Exception:
|
|
2109
2376
|
pass
|
|
2377
|
+
try:
|
|
2378
|
+
from core import wxdigest
|
|
2379
|
+
n_wx = wxdigest.start() # 群摘要:加载监控配置与游标(调度挂在 automation tick)
|
|
2380
|
+
if n_wx:
|
|
2381
|
+
print("[CodeBee] 群摘要:%d 个群游标已加载" % n_wx)
|
|
2382
|
+
except Exception:
|
|
2383
|
+
pass
|
|
2110
2384
|
tok = remote.token()
|
|
2111
2385
|
import atexit
|
|
2112
2386
|
import os as _os
|
|
@@ -2147,6 +2421,7 @@ def main():
|
|
|
2147
2421
|
faulthandler.cancel_dump_traceback_later() # 启动完成,看门狗退役(否则运行期每 20s 误报堆栈)
|
|
2148
2422
|
except Exception:
|
|
2149
2423
|
pass
|
|
2424
|
+
_start_pet_keeper(args.port) # 桌面蜜蜂:端口就绪后放出(设置可关;测试可禁)
|
|
2150
2425
|
if remote.PUBLIC_URL:
|
|
2151
2426
|
print("[CodeBee] 公网 %s/?token=%s ← 任何网络可访问(反代回源已强制校验令牌)"
|
|
2152
2427
|
% (remote.PUBLIC_URL, tok))
|