2026-10-05 14:13:24 +08:00
|
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
|
|
"""常驻启动器(无控制台)—— 🔴 计划任务的**常驻载体**指向本文件,⛔ 不许直起 `collabd.py`。
|
|
|
|
|
|
|
|
|
|
|
|
🔴🔴 2026-10-05 **为什么必须要有这个启动器**(本轮实测踩到的坑,P0-73):
|
|
|
|
|
|
新形态(计划任务 → `pythonw.exe` → `collabd.py --supervise`)**直起**时,
|
|
|
|
|
|
任务环境里**没有 `CODEBUDDY_CONFIG_DIR`** —— 旧看守 `start-supervise.ps1` 里那句
|
2026-10-14 09:48:01 +08:00
|
|
|
|
`$env:CODEBUDDY_CONFIG_DIR = "E:/ProgramData/.workbuddy"`
|
2026-10-05 14:13:24 +08:00
|
|
|
|
在我改成两层形态时**被丢掉了** ⇒ 后果:
|
|
|
|
|
|
· `_wb_db()` 落到 `~/.workbuddy/workbuddy.db`(**0 字节空库**)⇒
|
|
|
|
|
|
· `_all_sessions_idle()` 每轮报 **`no such table: sessions`** ⇒ 闸② 恒判"有会话在跑"
|
|
|
|
|
|
· ⇒ **检查会话再也不建**(实测日志 36 次读库失败,最后一次成功建排期停在 11:54)。
|
|
|
|
|
|
⚠️ 为什么有的区没炸:那区的 `collabd.config.json` 里 `host_db` **写死了绝对路径**(兜住了)。
|
|
|
|
|
|
⇒ **两种兜底都要有**:启动器设 env(通用)+ 配置可写 `host_db`(单区保险)。
|
|
|
|
|
|
|
|
|
|
|
|
🔴 用 `runpy.run_path`(⛔ 不用 `exec`):`runpy` 会把 `__name__`/`__file__`/`sys.path[0]`
|
|
|
|
|
|
都按"真在跑那个脚本"设好 ⇒ `collabd.py` 顶部的 `_sm_load_roots()` 靠 `__file__` 找 `roots.env`
|
|
|
|
|
|
与命令行分发(它自己读 `sys.argv`)全对。
|
2026-10-14 09:48:01 +08:00
|
|
|
|
|
|
|
|
|
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
|
|
|
|
🔴🔴 2026-10-14 **宿主库路径须「相对/外置」,且必须两处兜底**(用户令:技能里的文件路径用相对路径)
|
|
|
|
|
|
本文件**只允许**用 `HERE`(=自身所在目录)拼相对路径,⛔ 不写死任何本机盘符。
|
|
|
|
|
|
宿主库路径按**下面三级**取,命中即止:
|
|
|
|
|
|
|
|
|
|
|
|
① 宿主 env `CODEBUDDY_CONFIG_DIR`(宿主进程给了就用它,⛔ 不覆盖);
|
|
|
|
|
|
② 本区 `collabd.config.json` 的 `host_db`(单区兜底,**部署时由 init_workspace 按本机根目录生成**);
|
|
|
|
|
|
③ 包内 `env/roots.env` 的 `CODEBUDDY_CONFIG_DIR`(本包全局单例,跨区共享)。
|
|
|
|
|
|
|
|
|
|
|
|
⛔⛔ **禁止回落 `os.path.expanduser("~/.workbuddy")`** —— 本机它是 **0 字节空库**
|
|
|
|
|
|
(实测 `C:/Users/Administrator/.workbuddy/workbuddy.db` = 0 字节、零张表;真库在
|
|
|
|
|
|
`E:/ProgramData/.workbuddy/workbuddy.db`,124 MB)⇒ 回落即 `no such table: sessions`
|
|
|
|
|
|
⇒ 闸② 恒判"有会话在跑" ⇒ **检查会话再也不建**(2026-10-14 mcn-aigc-ontology 实测复现)。
|
|
|
|
|
|
⚠️ 10-11 那版写 `expanduser("~/.workbuddy")` 的**动机是对的**(不写死盘符、跟着用户走),
|
|
|
|
|
|
但**兜底选错了目录** —— 「不写死盘符」要靠**外置配置**实现,⛔ 不是靠猜 `~`。
|
|
|
|
|
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
2026-10-05 14:13:24 +08:00
|
|
|
|
"""
|
2026-10-14 09:48:01 +08:00
|
|
|
|
import json
|
2026-10-05 14:13:24 +08:00
|
|
|
|
import os
|
|
|
|
|
|
import sys
|
|
|
|
|
|
|
|
|
|
|
|
# 🔴 启动器所在目录即本区 `.workbuddy/collab`(由 init_workspace 分发到各区)
|
2026-10-14 09:48:01 +08:00
|
|
|
|
# 本文件内的所有路径一律以 `HERE` 为基准(相对),⛔ 无任何本机盘符字面量。
|
2026-10-05 14:13:24 +08:00
|
|
|
|
HERE = os.path.dirname(os.path.abspath(__file__))
|
|
|
|
|
|
CB = os.path.join(HERE, "collabd.py")
|
|
|
|
|
|
CFG = os.path.join(HERE, "collabd.config.json")
|
|
|
|
|
|
|
2026-10-14 09:48:01 +08:00
|
|
|
|
|
|
|
|
|
|
def _resolve_config_dir() -> str:
|
|
|
|
|
|
"""宿主库所在目录(`CODEBUDDY_CONFIG_DIR`)。三级取值,⛔ 不写死盘符、⛔ 不猜 `~`。"""
|
|
|
|
|
|
# ① 宿主 env 已给 ⇒ 直接用,⛔ 不动
|
|
|
|
|
|
given = (os.environ.get("CODEBUDDY_CONFIG_DIR") or "").strip()
|
|
|
|
|
|
if given:
|
|
|
|
|
|
return given
|
|
|
|
|
|
|
|
|
|
|
|
# ② 本区配置的 host_db(取它的父目录)
|
|
|
|
|
|
try:
|
|
|
|
|
|
with open(CFG, encoding="utf-8") as f:
|
|
|
|
|
|
_host_db = (json.load(f).get("host_db") or "").strip()
|
|
|
|
|
|
if _host_db:
|
|
|
|
|
|
return os.path.dirname(os.path.normpath(_host_db))
|
|
|
|
|
|
except Exception:
|
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
# ③ 包内 env/roots.env(HERE 往上找包根:collab → .workbuddy → 工作区根;再试技能包位置)
|
|
|
|
|
|
for _up in range(1, 8):
|
|
|
|
|
|
_pkg = os.path.dirname(HERE)
|
|
|
|
|
|
for _ in range(_up - 1):
|
|
|
|
|
|
_pkg = os.path.dirname(_pkg)
|
|
|
|
|
|
_roots = os.path.join(_pkg, "env", "roots.env")
|
|
|
|
|
|
if os.path.isfile(_roots):
|
|
|
|
|
|
try:
|
|
|
|
|
|
with open(_roots, encoding="utf-8", errors="replace") as f:
|
|
|
|
|
|
for ln in f:
|
|
|
|
|
|
ln = ln.strip()
|
|
|
|
|
|
if ln.startswith("CODEBUDDY_CONFIG_DIR="):
|
|
|
|
|
|
val = ln.split("=", 1)[1].strip()
|
|
|
|
|
|
if val:
|
|
|
|
|
|
return val
|
|
|
|
|
|
except Exception:
|
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
return ""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
_cfgdir = _resolve_config_dir()
|
|
|
|
|
|
if _cfgdir:
|
|
|
|
|
|
os.environ["CODEBUDDY_CONFIG_DIR"] = _cfgdir
|
|
|
|
|
|
else:
|
|
|
|
|
|
# ⛔ 取不到 ⇒ 宁可留空让下游自己报错,⛔ 也不回落 `~/.workbuddy`(那是 0 字节空库)。
|
|
|
|
|
|
try:
|
|
|
|
|
|
sys.stderr.write(
|
|
|
|
|
|
"[supervise-launch] ⚠️ 取不到 CODEBUDDY_CONFIG_DIR"
|
|
|
|
|
|
"(宿主 env / 本区 host_db / 包内 roots.env 三处都空)"
|
|
|
|
|
|
"⇒ 宿主库不可读,检查程序会静默失效\n"
|
|
|
|
|
|
)
|
|
|
|
|
|
except Exception:
|
|
|
|
|
|
pass
|
|
|
|
|
|
|
2026-10-05 14:13:24 +08:00
|
|
|
|
os.environ["COLLABD_CONFIG"] = CFG
|
|
|
|
|
|
os.environ["PYTHONIOENCODING"] = "utf-8"
|
|
|
|
|
|
os.environ["PYTHONUNBUFFERED"] = "1"
|
|
|
|
|
|
|
|
|
|
|
|
# 🔴 `pythonw.exe` 下 `sys.stdout/stderr` 可能是 `None` ⇒ 任何 `print` 都会 `AttributeError` 秒退。
|
|
|
|
|
|
LOG = os.path.join(HERE, "logs", "_supervise-launch.log")
|
|
|
|
|
|
try:
|
|
|
|
|
|
os.makedirs(os.path.dirname(LOG), exist_ok=True)
|
|
|
|
|
|
_f = open(LOG, "a", encoding="utf-8", errors="replace")
|
|
|
|
|
|
except Exception:
|
|
|
|
|
|
_f = None
|
|
|
|
|
|
if _f is not None:
|
|
|
|
|
|
if sys.stdout is None:
|
|
|
|
|
|
sys.stdout = _f
|
|
|
|
|
|
if sys.stderr is None:
|
|
|
|
|
|
sys.stderr = _f
|
|
|
|
|
|
|
|
|
|
|
|
import runpy
|
|
|
|
|
|
|
|
|
|
|
|
sys.argv = [CB, "--supervise"]
|
|
|
|
|
|
runpy.run_path(CB, run_name="__main__")
|