Files
workbuddy_skills/session-mechanism/scripts/supervise-launch.py
T
admin 053a6a6723 session-mechanism:主会话只能由用户开(撤掉"排期建主会话"那条路)
用户报障:「技能中没有任何创建主会话的机制,除非是创建接续会话」。

病灶:supervise-persistence.md §〇 早期把「定时任务创建主会话」与「用户手动
创建主会话」并列成两种合法场景,SKILL.md §⑥ 顺着抄了一遍 ⇒ 读到的人会理解成
"这条路是允许的",于是照做。

实测事故(mcn-aigc-ontology 2026-10-14 07:31):init_workspace 装机制后建出
recurring 排期 [主]-super-i卡片采集-主会话(FREQ=DAILY;BYHOUR=09),prompt 里
写着「你是本工作区的主会话……要推进就直接建 [执行] 排期拉起任务会话」——
主会话的口径被套到了一条排期身上。排期开出来的会话恒是任务会话那一档
(is_background_automation=1)⇒ 台账里多一条"主会话",用户那边没多窗口。

改动:
- SKILL.md §⑥:改成「形态只有一种:主会话由用户手动开」,⛔ 排期拉不出主会话
- supervise-persistence.md §〇:表格两列改一列 + 新增事故回放小节
- supervise-persistence.md §八:删掉「B · 定时任务创建主会话」整行
- architecture.md §1:新增《主会话怎么来:只能由用户手动开》段
- session-rules-check.py:新增判据 ⑬ main_sched(扫排期名以 [主] 开头或含
  "主会话" ⇒ ACTIVE 报 fail、非 ACTIVE 报 warn);扫描面 recurring 与 once 全含
- SKILL.md 体检清单:十二项 → 十三项,补 ⑬ 变异对照记录

判据验收(非恒绿):喂 mcn 区真数据三种输入 —— 原样 PAUSED ⇒ warn;
伪造成 ACTIVE ⇒ fail;无此类排期的区 ⇒ ok。

同批带上的既有改动:supervise-launch.py 路径相对化 + 三级取值(禁回落 ~)。
2026-10-14 09:48:01 +08:00

119 lines
6.0 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- coding: utf-8 -*-
"""常驻启动器(无控制台)—— 🔴 计划任务的**常驻载体**指向本文件,⛔ 不许直起 `collabd.py`。
🔴🔴 2026-10-05 **为什么必须要有这个启动器**(本轮实测踩到的坑,P0-73):
新形态(计划任务 → `pythonw.exe` → `collabd.py --supervise`)**直起**时,
任务环境里**没有 `CODEBUDDY_CONFIG_DIR`** —— 旧看守 `start-supervise.ps1` 里那句
`$env:CODEBUDDY_CONFIG_DIR = "E:/ProgramData/.workbuddy"`
在我改成两层形态时**被丢掉了** ⇒ 后果:
· `_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 **宿主库路径须「相对/外置」,且必须两处兜底**(用户令:技能里的文件路径用相对路径)
本文件**只允许**用 `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")` 的**动机是对的**(不写死盘符、跟着用户走),
但**兜底选错了目录** —— 「不写死盘符」要靠**外置配置**实现,⛔ 不是靠猜 `~`。
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
"""
import json
import os
import sys
# 🔴 启动器所在目录即本区 `.workbuddy/collab`(由 init_workspace 分发到各区)
# 本文件内的所有路径一律以 `HERE` 为基准(相对),⛔ 无任何本机盘符字面量。
HERE = os.path.dirname(os.path.abspath(__file__))
CB = os.path.join(HERE, "collabd.py")
CFG = os.path.join(HERE, "collabd.config.json")
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
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__")