151 lines
8.6 KiB
Python
151 lines
8.6 KiB
Python
#!/usr/bin/env python
|
||||
|
|
"""🔴 **新建协作工作区**(技能内脚本,用户 2026-10-03 要求"每次使用协作会话机制都可以有对应的目标文件夹")。
|
|||
|
|
|
|||
|
|
用法:
|
|||
|
|
python init_workspace.py <工作区绝对路径> [--title 目标标题] [--short 目标简称]
|
|||
|
|
[--port 单例端口] [--no-main-session]
|
|||
|
|
|
|||
|
|
做四件事(⛔ 顺序固定,缺一件机制就跑不起来):
|
|||
|
|
① 建工作区骨架(`.workbuddy/collab/`、`tmp/supervise-inbox/`、`交付物/`)
|
|||
|
|
② 写**部署配置** `collabd.config.json`(`COLLABD_CONFIG` 指向它 ⇒ `collabd.py` 才认这个工作区)
|
|||
|
|
③ 写**目标** `goal.json`(title/short/topics/lifecycle=进行中/taskgraph)
|
|||
|
|
③b 写**最小任务图**(⛔ 漏了它 ⇒ 常驻每轮报「读任务图失败」,且目标判据缺一条腿)
|
|||
|
|
④ 跑 `collabd.py --ensure-goal-dir` ⇒ **建目标文件夹 + 落《目标执行状态.md》骨架**(幂等)
|
|||
|
|
⑤ 建一条**主会话排期**(`automation_update` 由调用方做 ⇒ 本脚本只**打印**它该用的参数,
|
|||
|
|
⛔ 不自己写宿主库 —— 创建新会话**只能走自动化入口**,这是宿主规矩)
|
|||
|
|
|
|||
|
|
🔴 关键约束(都是踩过的):
|
|||
|
|
· `workspace` 必须**正斜杠**(⛔ 反斜杠会让 `cwds` 裂组)
|
|||
|
|
· `singleton_port` **每个工作区一个**(⛔ 复用 ⇒ 常驻单例互抢、静默只有一个活着)
|
|||
|
|
· `board_ext` 指 `.RETIRED-*` ⇒ 新工作区**不带**使用方扩展(技能侧零项目串)
|
|||
|
|
· 排期名必须是两级前缀 `[主]-<类别>-<具体>`(⛔ 光杆名 ⇒ 角色判不出,看板认不出)
|
|||
|
|
"""
|
|||
|
|
import argparse
|
|||
|
|
import json
|
|||
|
|
import os
|
|||
|
|
import subprocess
|
|||
|
|
import sys
|
|||
|
|
from pathlib import Path
|
|||
|
|
|
|||
|
|
sys.stdout.reconfigure(encoding="utf-8", errors="replace")
|
|||
|
|
SK = Path(__file__).resolve().parent
|
|||
|
|
DEFAULT_TITLE = "检查会话协作是否运行正常 + 协作机制问题排查与修复"
|
|||
|
|
|
|||
|
|
|
|||
|
|
def main() -> int:
|
|||
|
|
ap = argparse.ArgumentParser()
|
|||
|
|
ap.add_argument("workspace")
|
|||
|
|
ap.add_argument("--title", default=DEFAULT_TITLE)
|
|||
|
|
ap.add_argument("--short", default="")
|
|||
|
|
ap.add_argument("--topics", default="会话协作自检,机制排查与修复")
|
|||
|
|
ap.add_argument("--port", type=int, default=0)
|
|||
|
|
ap.add_argument("--python", default=sys.executable)
|
|||
|
|
a = ap.parse_args()
|
|||
|
|
|
|||
|
|
ws = Path(a.workspace).resolve()
|
|||
|
|
ws_s = str(ws).replace("\\", "/") # 🔴 正斜杠(红线)
|
|||
|
|
short = a.short or ws.name
|
|||
|
|
topics = [t.strip() for t in a.topics.split(",") if t.strip()]
|
|||
|
|
port = a.port or (20000 + (abs(hash(ws_s)) % 5000)) # ⛔ 每区唯一
|
|||
|
|
|
|||
|
|
print("=== 新建协作工作区:%s ===" % ws_s)
|
|||
|
|
# ① 骨架
|
|||
|
|
for d in (".workbuddy/collab", "tmp/supervise-inbox", "交付物", ".workbuddy/collab/logs"):
|
|||
|
|
(ws / d).mkdir(parents=True, exist_ok=True)
|
|||
|
|
print("① 骨架已建(.workbuddy/collab / tmp/supervise-inbox / 交付物 / logs)")
|
|||
|
|
|
|||
|
|
# ② 部署配置
|
|||
|
|
cfg = {
|
|||
|
|
"_说明": "本文件=**本工作区**的协作机制部署配置(⛔ 不属于技能)。由 `init_workspace.py` 生成;"
|
|||
|
|
"`COLLABD_CONFIG` 必须指向它,否则 `collabd.py` 不认这个工作区。",
|
|||
|
|
"workspace": ws_s,
|
|||
|
|
"inbox": "tmp/supervise-inbox",
|
|||
|
|
"live": "交付物/本机协作-实时状态.md",
|
|||
|
|
"taskgraph": "交付物/任务图-会话协作自检.json",
|
|||
|
|
"targets": {},
|
|||
|
|
"singleton_port": port,
|
|||
|
|
"interval": 10, "idle_min": 12, "stuck_min": 30, "vacuum_min": 5, "health_every": 300,
|
|||
|
|
"supervise_interval": 10,
|
|||
|
|
"wake_enable": False, # 🔴 唤醒/投递整套已退役
|
|||
|
|
"board_ext": ".workbuddy/collab/board_ext.RETIRED.py", # ⛔ 不带使用方扩展
|
|||
|
|
"log": ".workbuddy/collab/logs/_collabd.log",
|
|||
|
|
}
|
|||
|
|
cfgp = ws / ".workbuddy/collab/collabd.config.json"
|
|||
|
|
cfgp.write_text(json.dumps(cfg, ensure_ascii=False, indent=1), encoding="utf-8", newline="")
|
|||
|
|
print("② 部署配置已写:%s(单例端口 %d)" % (cfgp.relative_to(ws), port))
|
|||
|
|
|
|||
|
|
# ③ 目标
|
|||
|
|
goal = {
|
|||
|
|
"title": a.title, "short": short, "topics": topics,
|
|||
|
|
"lifecycle": "进行中",
|
|||
|
|
"lifecycle_at": "", "lifecycle_by": "", "lifecycle_why": "init_workspace.py 新建",
|
|||
|
|
"taskgraph": "交付物/任务图-会话协作自检.json",
|
|||
|
|
"taskgraph_set_at": "", "taskgraph_set_by": "",
|
|||
|
|
"declared_at": "", "declared_by": "",
|
|||
|
|
"acceptance_state": {},
|
|||
|
|
"_唯一来源": "本文件 title/short/topics 是真源;`execution_doc` 指向目标文件夹里的《目标执行状态.md》。",
|
|||
|
|
}
|
|||
|
|
gp = ws / "tmp/supervise-inbox/goal.json"
|
|||
|
|
gp.write_text(json.dumps(goal, ensure_ascii=False, indent=1), encoding="utf-8", newline="")
|
|||
|
|
print("③ 目标已写:title=%r short=%r lifecycle=进行中" % (a.title, short))
|
|||
|
|
|
|||
|
|
# ③b 任务图(🔴 2026-10-03 14:35x 补):配置里 `taskgraph` 指向它,
|
|||
|
|
# 但初始化时**没建这个文件** ⇒ 常驻每 10 秒报「goal_state 读任务图失败」
|
|||
|
|
# ⇒ 更要命的是**判据第二路(任务图 nodes)永远取不到** ⇒ 目标状态判据缺一条腿。
|
|||
|
|
# ⇒ 写一份最小任务图:节点全 `pending`,由协作会话逐个转 `done`。
|
|||
|
|
tg_rel = "交付物/任务图-会话协作自检.json"
|
|||
|
|
tg = {"title": a.title, "short": short, "topics": topics,
|
|||
|
|
"nodes": [{"id": "N1", "title": "核对目标并完成首轮协作", "status": "pending",
|
|||
|
|
"line": topics[0] if topics else "", "note": "init_workspace.py 生成的最小任务图"}]}
|
|||
|
|
(ws / tg_rel).write_text(json.dumps(tg, ensure_ascii=False, indent=1),
|
|||
|
|
encoding="utf-8", newline="")
|
|||
|
|
print("③b 任务图已写:%s(%d 个节点,全 pending)" % (tg_rel, len(tg["nodes"])))
|
|||
|
|
|
|||
|
|
# ④ 目标文件夹(走产品入口,⛔ 不自己 mkdir 文档)
|
|||
|
|
# 🔴🔴 2026-10-03 **改用「本工作区的副本」,⛔ 不再用技能目录那份**:
|
|||
|
|
# 定案口径=**各工作区的程序各自独立**(技能目录=源,`.workbuddy/collab/`=各区副本,
|
|||
|
|
# `deploy_code.py` 分发)⇒ 提示用户去跑技能目录那份,等于**又拉起第二个常驻**,
|
|||
|
|
# 它会写**技能目录的 tmp/**(⛔ 那不是任何工作区的 inbox)⇒ 状态写到别处,
|
|||
|
|
# 看板读不到、故障排查也被带偏(本轮 `SESSION_END`-style 误判的同族)。
|
|||
|
|
# ⚠️ 本副本若还不存在(首次初始化、还没分发)⇒ 回落用技能目录那份,
|
|||
|
|
# 但**打印时必须说清"这次是回落"**(⛔ 静默回落=用户以为跑的是副本)。
|
|||
|
|
_own = ws / ".workbuddy" / "collab" / "collabd.py"
|
|||
|
|
_own_ba = ws / ".workbuddy" / "collab" / "board.py"
|
|||
|
|
_fallback = not _own.is_file()
|
|||
|
|
_cd = SK / "collabd.py" if _fallback else _own
|
|||
|
|
if _fallback:
|
|||
|
|
print(" ⚠️ 本工作区还没有 collabd.py 副本 ⇒ 本次**回落用技能目录那份**;"
|
|||
|
|
"请随后跑 deploy_code.py 分发,否则各区程序不独立。")
|
|||
|
|
env = dict(os.environ)
|
|||
|
|
env["COLLABD_CONFIG"] = str(cfgp).replace("\\", "/")
|
|||
|
|
env["COLLABD_NO_ENSURE"] = "1"
|
|||
|
|
r = subprocess.run([a.python, str(_cd), "--ensure-goal-dir"],
|
|||
|
|
env=env, cwd=str(ws), capture_output=True, timeout=120)
|
|||
|
|
out = r.stdout.decode("utf-8", "replace").strip()
|
|||
|
|
print("④ --ensure-goal-dir rc=%s" % r.returncode)
|
|||
|
|
for ln in out.splitlines():
|
|||
|
|
print(" " + ln)
|
|||
|
|
gd = [ln for ln in out.splitlines() if ln.startswith("目标目录:")]
|
|||
|
|
gdir = gd[0].split(":", 1)[1].strip() if gd else ""
|
|||
|
|
|
|||
|
|
# ⑤ 主会话排期参数(⛔ 本脚本不建 —— 创建新会话只能走自动化入口)
|
|||
|
|
print()
|
|||
|
|
print("⑤ 主会话排期(请由调用方用 automation_update 建):")
|
|||
|
|
print(" name = [主]-%s-主会话" % short)
|
|||
|
|
print(" cwds = %s" % ws_s)
|
|||
|
|
print(" 提示:主会话标题必须是**两级前缀**(`[主]-<类别>-<具体>`),⛔ 光杆名看板认不出。")
|
|||
|
|
print()
|
|||
|
|
print("✅ 完成。后续协作(⛔ **都跑本工作区的副本**,别跑技能目录那份 —— "
|
|||
|
|
"各区程序各自独立,跑了目录那份会写错地方):")
|
|||
|
|
print(" export COLLABD_CONFIG=%s" % str(cfgp).replace("\\", "/"))
|
|||
|
|
_cd_show = _cd
|
|||
|
|
_bd_show = (SK / "board.py") if not (ws / ".workbuddy" / "collab" / "board.py").is_file() else _own_ba
|
|||
|
|
print(" python %s --supervise" % _cd_show)
|
|||
|
|
print(" python %s --serve 8789 --takeover" % _bd_show)
|
|||
|
|
if _fallback:
|
|||
|
|
print(" ⚠️ 上面是**技能目录那份**(副本还没分发)⇒ 先跑 deploy_code.py --ws %s" % ws_s)
|
|||
|
|
return 0
|
|||
|
|
|
|||
|
|
|
|||
|
|
if __name__ == "__main__":
|
|||
|
|
sys.exit(main())
|