fix(session-mechanism): 常驻读不到宿主库 ⇒ 检查会话永不建
- init_workspace.py 生成配置时写 host_db(宿主 env → 本包 env/roots.env);旧配置留空则补,已写值不动 - collabd._wb_db() 去掉 ~/.workbuddy 回落(本机无此文件),取不到改为占位路径 + 限流留痕 - _sm_load_roots() 先读 COLLABD_CONFIG 所指配置的 host_db,再试 <CODEBUDDY_CONFIG_DIR>/skills/session-mechanism/env/roots.env,最后才上跳 - pitfalls 新增 P0-99(含「新工作区验收跑一次脱环境探针」通则) 实测:deepwrite-harness 新建区 2091 条「读库失败」、11.5 小时零检查会话;修复后五区重启读库失败归零,检查会话建棒并把目标置「已完成」
This commit is contained in:
1 parent
8160f91559
commit
310a964a94
3 files changed
+111
-3
No files matched your search
@@ -3823,3 +3823,26 @@ var r = el.getBoundingClientRect();
|
|||||||
- ⚠️ 升级后必做的回归:核对**下游按名引用的具体文件还在不在**
|
- ⚠️ 升级后必做的回归:核对**下游按名引用的具体文件还在不在**
|
||||||
(本次 `product-planning` 引用的 `visual-review.md`/`tools.md`/`design-direction.md`/`style-explorer.md` 全部健在;
|
(本次 `product-planning` 引用的 `visual-review.md`/`tools.md`/`design-direction.md`/`style-explorer.md` 全部健在;
|
||||||
新版删了 `imagery.md`、新增 16 份 —— 删除项要逐条确认无下游引用)。
|
新版删了 `imagery.md`、新增 16 份 —— 删除项要逐条确认无下游引用)。
|
||||||
|
|
||||||
|
## P0-99 新建工作区的常驻读不到宿主库 ⇒ 检查会话永不建(2026-10-17 实测)
|
||||||
|
|
||||||
|
- **症状**:新开的工作区(`deepwrite-harness`)整夜零检查会话;`_collabd.log` 里
|
||||||
|
`all_sessions_idle 读库失败 unable to open database file` 每 20 秒一条,**2091 条**。
|
||||||
|
`_all_sessions_idle()` 是 fail-safe:读不出来 ⇒ 判「有会话在跑」⇒ 闸② 恒挡 ⇒ 检查线整条瘫。
|
||||||
|
- **根因链(三道兜底全空)**:① 计划任务起 `pythonw supervise-launch.py`,环境里没有
|
||||||
|
`CODEBUDDY_CONFIG_DIR`;② 本区 `collabd.config.json` 的 `host_db` 被 `init_workspace.py` 留空;
|
||||||
|
③ 启动器第③级与 `collabd._sm_load_roots()` 都靠「从自身位置往上跳找 `env/roots.env`」,
|
||||||
|
而副本在 `<工作区>/.workbuddy/collab/`、与技能包没有相对关系 ⇒ **恒找不到**。
|
||||||
|
⇒ `_wb_db()` 回落 `~/.workbuddy/workbuddy.db`(本机无此文件)⇒ 打不开。
|
||||||
|
- **旁证**:本机六个区里写过 `host_db` 绝对路径的三个(`aigc`/`contentm`/`mcn`)没炸;
|
||||||
|
`ai1net-dsh-server` 同样留空 ⇒ 日志 41 次 `no such table: sessions` —— 同一个病两个报错。
|
||||||
|
- **✅ 正解(三处一起改)**:
|
||||||
|
① `init_workspace.py` 新增 `_resolve_host_db()`,生成配置时写 `host_db`(宿主 env → 本包 `env/roots.env`);
|
||||||
|
旧配置留空 ⇒ 补上,已写值保持不动。
|
||||||
|
② `collabd._wb_db()` 去掉 `Path.home()/".workbuddy"` 回落,取不到 ⇒ 返回占位路径 + `log_throttled` 留痕。
|
||||||
|
③ `_sm_load_roots()` **先读 `COLLABD_CONFIG` 所指配置里的 `host_db`**(最接近真相,启动器恒设这个 env),
|
||||||
|
再补一条 `<CODEBUDDY_CONFIG_DIR>/skills/session-mechanism/env/roots.env`;仍找不到才上跳。
|
||||||
|
- **验收**:清空 `CODEBUDDY_CONFIG_DIR` 起探针 ⇒ 解析出 `E:\ProgramData\.workbuddy`;
|
||||||
|
五个区重启后 `11:34 之后读库失败 = 0`;本区检查会话 11:26:49 起、11:28:39 把目标置「已完成」。
|
||||||
|
- **🔴 通则**:**新工作区验收要跑一次「脱环境」探针**(`env -u CODEBUDDY_CONFIG_DIR` 起 collabd),
|
||||||
|
能解析出宿主库才算装好。⛔ 别拿「其它区都在跑」当证据 —— 那是早年的 `host_db` 绝对路径在兜底。
|
||||||
@@ -70,6 +70,21 @@ from __future__ import annotations
|
|||||||
def _sm_load_roots():
|
def _sm_load_roots():
|
||||||
import os as _os
|
import os as _os
|
||||||
_here = _os.path.dirname(_os.path.abspath(__file__))
|
_here = _os.path.dirname(_os.path.abspath(__file__))
|
||||||
|
# 🔴🔴 2026-10-17 补:**先读本区部署配置的 `host_db`**(最接近真相的一条)
|
||||||
|
# 启动器恒会设 `COLLABD_CONFIG`;配置里那项是 `init_workspace` 按本机写下的绝对路径。
|
||||||
|
# 为什么排第一:工作区副本与技能包没有相对关系 ⇒ 上跳找 `env/roots.env` 恒落空
|
||||||
|
# ⇒ 只靠上跳,常驻的 `CODEBUDDY_CONFIG_DIR` 永远为空 ⇒ 宿主库路径解析失败
|
||||||
|
# (deepwrite-harness 实测 2091 条读库失败、检查会话整夜不建)。
|
||||||
|
_cfgp = _os.environ.get("COLLABD_CONFIG") or ""
|
||||||
|
if _cfgp and _os.path.isfile(_cfgp):
|
||||||
|
try:
|
||||||
|
import json as _json
|
||||||
|
_hd = (_json.load(open(_cfgp, encoding="utf-8")).get("host_db") or "").strip()
|
||||||
|
if _hd:
|
||||||
|
_os.environ.setdefault(
|
||||||
|
"CODEBUDDY_CONFIG_DIR", _os.path.dirname(_os.path.normpath(_hd)))
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
# 🔴 2026-10-11:「脚本是脚本 配置是配置」⇒ 配置落本包 `env/`。
|
# 🔴 2026-10-11:「脚本是脚本 配置是配置」⇒ 配置落本包 `env/`。
|
||||||
# 首选 `env/roots.env`;并兼容旧的包根 `roots.env`(拷贝未同步时照旧能跑)。
|
# 首选 `env/roots.env`;并兼容旧的包根 `roots.env`(拷贝未同步时照旧能跑)。
|
||||||
for _up in range(4):
|
for _up in range(4):
|
||||||
@@ -87,6 +102,30 @@ def _sm_load_roots():
|
|||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
return
|
return
|
||||||
|
# 🔴🔴 2026-10-17 补第三路(实测:工作区副本这条路恒落空)
|
||||||
|
# 副本住在 `<工作区>/.workbuddy/collab/`,与技能包没有相对关系 ⇒
|
||||||
|
# 「从自身位置往上跳」永远跳不到 `<技能根>/session-mechanism/env/roots.env`
|
||||||
|
# ⇒ `CODEBUDDY_CONFIG_DIR` 一直为空 ⇒ 宿主库路径解析失败
|
||||||
|
# (deepwrite-harness 实测 2091 条读库失败,检查会话整夜不建)。
|
||||||
|
# 技能根的位置只能由配置目录推出来:`<CODEBUDDY_CONFIG_DIR>/skills/session-mechanism/`。
|
||||||
|
_cfgdir = (_os.environ.get("CODEBUDDY_CONFIG_DIR")
|
||||||
|
or _os.path.expanduser("~/.workbuddy")).strip()
|
||||||
|
for _cand in (
|
||||||
|
_os.path.join(_cfgdir, "skills", "session-mechanism", "env", "roots.env"),
|
||||||
|
_os.path.join(_cfgdir, "skills", "session-mechanism", "roots.env"),
|
||||||
|
):
|
||||||
|
if not _os.path.isfile(_cand):
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
with open(_cand, encoding="utf-8", errors="replace") as _f:
|
||||||
|
for _ln in _f:
|
||||||
|
_ln = _ln.strip()
|
||||||
|
if _ln and not _ln.startswith("#") and "=" in _ln:
|
||||||
|
_k, _v = _ln.split("=", 1)
|
||||||
|
_os.environ.setdefault(_k.strip(), _v.strip())
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return
|
||||||
_sm_load_roots()
|
_sm_load_roots()
|
||||||
# 🔴 2026-10-01 加 · **输出编码兜底**:脚本一旦被重定向(钩子/常驻/后台任务都会这么干),
|
# 🔴 2026-10-01 加 · **输出编码兜底**:脚本一旦被重定向(钩子/常驻/后台任务都会这么干),
|
||||||
# Windows 本地编码(GBK)编不出 ⛔/✅/🔴 这类字符 ⇒ `print` 抛 UnicodeEncodeError
|
# Windows 本地编码(GBK)编不出 ⛔/✅/🔴 这类字符 ⇒ `print` 抛 UnicodeEncodeError
|
||||||
@@ -3761,9 +3800,24 @@ _LIFE_ALIAS_WAIT_NOTE = "等待" # 旧值显示用的兜底文案
|
|||||||
# v3 补 runtime_state → 仍未拾取(证明那行没用)
|
# v3 补 runtime_state → 仍未拾取(证明那行没用)
|
||||||
# v4 两项都对 → IN_PROGRESS + 真实 conversationId
|
# v4 两项都对 → IN_PROGRESS + 真实 conversationId
|
||||||
def _wb_db() -> Path:
|
def _wb_db() -> Path:
|
||||||
"""宿主活动库(⛔ **不是** `~/.workbuddy/workbuddy.db` —— 那是旧库且可能是 0 字节空文件)。"""
|
"""宿主活动库(⛔ **不是** `~/.workbuddy/workbuddy.db` —— 那是旧库且可能是 0 字节空文件)。
|
||||||
cfg = os.environ.get("CODEBUDDY_CONFIG_DIR") or ""
|
|
||||||
return Path(cfg) / "workbuddy.db" if cfg else Path.home() / ".workbuddy" / "workbuddy.db"
|
🔴 2026-10-17 改:**取不到配置目录 ⇒ 返回一个不存在的占位路径 + 留痕**。
|
||||||
|
原先是回落 `Path.home()/".workbuddy"`,本机该文件不存在(或 0 字节空库)⇒
|
||||||
|
报出来是 `unable to open database file`,与「真的没有会话」在日志里分不开
|
||||||
|
(deepwrite-harness 实测 2091 条,整夜锁死闸② ⇒ 检查会话一个都不建)。
|
||||||
|
留痕用 `log_throttled`:同一原因 10 分钟一条,⛔ 不刷屏。
|
||||||
|
"""
|
||||||
|
cfg = (os.environ.get("CODEBUDDY_CONFIG_DIR") or "").strip()
|
||||||
|
if cfg:
|
||||||
|
return Path(cfg) / "workbuddy.db"
|
||||||
|
try:
|
||||||
|
log_throttled("wb-db-unresolved", 600,
|
||||||
|
"宿主库路径未解析:CODEBUDDY_CONFIG_DIR 为空、本区配置也没给 host_db "
|
||||||
|
"⇒ 检查会话建不出来(查启动器三级兜底)")
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return Path("__unresolved_config_dir__") / "workbuddy.db"
|
||||||
|
|
||||||
|
|
||||||
def _ro_conn():
|
def _ro_conn():
|
||||||
|
|||||||
@@ -72,6 +72,33 @@ SK = _resolve_skill_dir(SK)
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def _resolve_host_db() -> str:
|
||||||
|
"""宿主库绝对路径(写进本区配置的 `host_db`)。
|
||||||
|
|
||||||
|
为什么必须写进配置(2026-10-17 deepwrite-harness 实测,2091 条读库失败):
|
||||||
|
计划任务起的常驻拿不到 `CODEBUDDY_CONFIG_DIR`,而本区副本住在
|
||||||
|
`<工作区>/.workbuddy/collab/`、与技能包**没有相对关系** ⇒ 启动器第③级
|
||||||
|
「上跳找 `env/roots.env`」恒落空 ⇒ `collabd._wb_db()` 回落
|
||||||
|
`~/.workbuddy/workbuddy.db`(本机无此文件)⇒ 闸② 恒判「有会话在跑」
|
||||||
|
⇒ 检查会话永不建。配置里写死这一项是第②级兜底,单区生效、不依赖环境。
|
||||||
|
|
||||||
|
取值顺序:宿主 env `CODEBUDDY_CONFIG_DIR` → 本包 `env/roots.env`。
|
||||||
|
两处都取不到 ⇒ 返回空串(交启动器报错留痕,⛔ 不猜 `~/.workbuddy`)。
|
||||||
|
"""
|
||||||
|
cfg = (os.environ.get("CODEBUDDY_CONFIG_DIR") or "").strip()
|
||||||
|
if not cfg:
|
||||||
|
_roots = SK.parent / "env" / "roots.env"
|
||||||
|
try:
|
||||||
|
for _ln in _roots.read_text(encoding="utf-8", errors="replace").splitlines():
|
||||||
|
_ln = _ln.strip()
|
||||||
|
if _ln.startswith("CODEBUDDY_CONFIG_DIR="):
|
||||||
|
cfg = _ln.split("=", 1)[1].strip()
|
||||||
|
break
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return str(Path(cfg) / "workbuddy.db") if cfg else ""
|
||||||
|
|
||||||
|
|
||||||
def main() -> int:
|
def main() -> int:
|
||||||
ap = argparse.ArgumentParser()
|
ap = argparse.ArgumentParser()
|
||||||
ap.add_argument("workspace")
|
ap.add_argument("workspace")
|
||||||
@@ -196,6 +223,7 @@ def main() -> int:
|
|||||||
% ("有" if _old_cfg else "⛔无", "有" if _old_goal else "⛔无"))
|
% ("有" if _old_cfg else "⛔无", "有" if _old_goal else "⛔无"))
|
||||||
|
|
||||||
# ② 部署配置(**补缺式**:以旧配置为底,只加现行键;⛔ 不动用户改过的值)
|
# ② 部署配置(**补缺式**:以旧配置为底,只加现行键;⛔ 不动用户改过的值)
|
||||||
|
_host_db = _resolve_host_db()
|
||||||
cfg = {
|
cfg = {
|
||||||
"_说明": "本文件=**本工作区**的协作机制部署配置(⛔ 不属于技能)。由 `init_workspace.py` 生成;"
|
"_说明": "本文件=**本工作区**的协作机制部署配置(⛔ 不属于技能)。由 `init_workspace.py` 生成;"
|
||||||
"`COLLABD_CONFIG` 必须指向它,否则 `collabd.py` 不认这个工作区。",
|
"`COLLABD_CONFIG` 必须指向它,否则 `collabd.py` 不认这个工作区。",
|
||||||
@@ -205,6 +233,7 @@ def main() -> int:
|
|||||||
"taskgraph": "交付物/任务图-会话协作自检.json",
|
"taskgraph": "交付物/任务图-会话协作自检.json",
|
||||||
"targets": {},
|
"targets": {},
|
||||||
"singleton_port": port,
|
"singleton_port": port,
|
||||||
|
"host_db": _host_db, # 🔴 二级兜底:计划任务环境没有 CODEBUDDY_CONFIG_DIR
|
||||||
"interval": 10, "idle_min": 12, "stuck_min": 30, "vacuum_min": 5, "health_every": 300,
|
"interval": 10, "idle_min": 12, "stuck_min": 30, "vacuum_min": 5, "health_every": 300,
|
||||||
"supervise_interval": 10,
|
"supervise_interval": 10,
|
||||||
"wake_enable": False, # 🔴 唤醒/投递整套已退役
|
"wake_enable": False, # 🔴 唤醒/投递整套已退役
|
||||||
@@ -218,6 +247,8 @@ def main() -> int:
|
|||||||
_added = [k for k in cfg if k not in _old_cfg]
|
_added = [k for k in cfg if k not in _old_cfg]
|
||||||
cfg["workspace"] = ws_s # 这两项必须以本次为准(工作区可能搬过家)
|
cfg["workspace"] = ws_s # 这两项必须以本次为准(工作区可能搬过家)
|
||||||
cfg["singleton_port"] = port # ⚠️ 纠偏时**必须以本次为准**(旧值可能在 `_old_cfg` 里,会被 update 盖回去)
|
cfg["singleton_port"] = port # ⚠️ 纠偏时**必须以本次为准**(旧值可能在 `_old_cfg` 里,会被 update 盖回去)
|
||||||
|
if not (cfg.get("host_db") or "").strip():
|
||||||
|
cfg["host_db"] = _host_db # 旧配置留空 ⇒ 补上(空串会让常驻读不到宿主库)
|
||||||
cfgp.write_text(json.dumps(cfg, ensure_ascii=False, indent=1), encoding="utf-8", newline="")
|
cfgp.write_text(json.dumps(cfg, ensure_ascii=False, indent=1), encoding="utf-8", newline="")
|
||||||
print("② 部署配置:%s%s(端口 %d,%s)"
|
print("② 部署配置:%s%s(端口 %d,%s)"
|
||||||
% (cfgp.relative_to(ws), (" 补了 %s" % _added) if _added else " 已最新",
|
% (cfgp.relative_to(ws), (" 补了 %s" % _added) if _added else " 已最新",
|
||||||
|
|||||||
Reference in new issue
Block a user