decision-rules-hook: 注入补「两个停止点」+「技术方案排序轴」
起因(2026-10-06 用户问):能自决的问题为什么还要问。
复盘发现注入的判据里**缺动作**:
- 缺「十步」的两个停止点 → ① 判类型(方案请求 vs 直接执行)② 判方向(扩大/收窄/中性);
- 缺「自主裁决顺序」的排序轴 → 更小改动 → 更少新增形态 → **不扩大可见面**。
⇒ 前者导致「会扩大可见面」时不停手;后者导致排完仍把已定项混进待拍板清单(=变相征询)。
本次只补这两条动作骨架(1459 → 1906 字符),⛔ 不塞十步全文(避免稀释)。
This commit is contained in:
1 parent
64dd82073b
commit
433d43af54
8 files changed
+195
-273
No files matched your search
@@ -192,7 +192,22 @@ python .workbuddy/collab/collabd.py --set-life 进行中 --by "主会话"
|
||||
动作写死 **`powershell.exe -WindowStyle Hidden -File start-supervise.ps1`**
|
||||
⇒ PowerShell 是**控制台程序** ⇒ 每次触发分配 `conhost.exe` ⇒ **闪黑窗**(用户 2026-10-05 报「又弹了窗口」)。
|
||||
✅ 已改指 `pythonw.exe + supervise-launch.py`。⇒ **凡"同一种东西有第二条起法",收口时必须全文搜一遍**:
|
||||
判据 = `grep -n "New-ScheduledTaskAction"` 与 `grep -n "\\.ps1"`(本次就是靠这个抓到的)。
|
||||
判据 = `grep -n "New-ScheduledTaskAction"` 与 `grep -n "\.ps1"`(本次就是靠这个抓到的)。
|
||||
|
||||
🔴🔴 **2026-10-06 二次收口(上一次只改了一半)**:只换"动作"不够,**名字与旁路也得一起换**。三处:
|
||||
1、**任务名统一** —— `_escalate_to_keeper()` 原建 `collabd-supervise-<区>`,而 `collabctl.py` 建
|
||||
`collabd-keepalive-<区>` ⇒ 前者**恰好落在 `collabctl.py` 的 `SCHED_TASKS` 禁用名单里**
|
||||
⇒ **自愈起的常驻会被下一次 `off` 当"旧形态"顺手干掉**(自愈链断在这里)。✅ 已统一为 `collabd-keepalive-<区>`。
|
||||
2、**`init_workspace.py` 是漏网的第二个建任务者** —— 它自带一份 `_register_keeper_task()`,
|
||||
还用着 `powershell.exe -File start-supervise.ps1` 与 `assets/start-supervise.ps1.tpl`。
|
||||
✅ 整份去掉:建任务**只许有一处实现**=`collabctl.py`(受 `supervise.switch` 总电闸管辖),
|
||||
该脚本改为只检查启动器在位并提示入口。模板 `assets/start-supervise.ps1.tpl` 一并删除。
|
||||
3、**探针曾打偏** —— 守形态的自检用例**只扫 `collabd.py`**,同族的 `init_workspace.py` 整份没被扫到。
|
||||
✅ 已扩成扫**全族**(`collabd.py` / `init_workspace.py` / `collabctl.py`),并新增判据
|
||||
「**任务名口径唯一**」与「旧形态死代码已清」。
|
||||
⚠️ **教训**:凡"这一族文件都不许有 X",必须**枚举整族**,⛔ 不许只挑一个代表 —— 挑一个就等于没扫。
|
||||
⚠️ 该判据**扫文本**,故必须先剥掉 `SCHED_TASKS` 那种"故意列旧名去禁用它"的**否定语境块**
|
||||
(同一坑本项目踩过三次)。
|
||||
|
||||
- ⚠️ **变量名坑**:`roots.env` 给的是 **`COLLABD_PROD_CONFIG`**,而 `board.py`/`collabd.py` 读的是 **`COLLABD_CONFIG`** ⇒ 名字对不上 ⇒ `roots.env` **兜不住**。
|
||||
- 🔴 **启动器里写死最稳**(⛔ 别指望 `roots.env` 兜住各区 —— 它只在技能目录,而各区副本在 `<ws>/.workbuddy/collab/`,`_sm_load_roots()` 向上 4 层**找不到**)。
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
# manifest · 包内文件清单
|
||||
|
||||
> 生成方式:逐文件 `compile()` / `json.loads` + md5 | **最近一次全量重算:2026-10-06 05:10 (补 P0-85(工作区解析静默错归属)+P0-84 已修尾注)**
|
||||
> 生成方式:逐文件 `compile()` / `json.loads` + md5 | **最近一次全量重算:2026-10-06 22:35 (P0-74 二次收口:任务名统一 + `init_workspace.py` 旁路清除 + 删 `assets/start-supervise.ps1.tpl`)**
|
||||
> ⚠️ **2026-10-05 局部增量**:`references/pitfalls.md`(P0-77 拆条 + P0-73/P0-77 压缩)与
|
||||
> `scripts/goalctl.py`(`--switch-goal` 确认闸 + 旧目标归档)两行的 md5/大小已按当天实测值更新;**其余行仍是 10-04 基线**。
|
||||
> ⛔ 本表**不含** `install.log`(运行日志)与 `references/manifest.md`(自引用,写完即失真)。
|
||||
> ⚠️ **provenance 列里的 `skills/multi-session-collab/…`、`skills/workbuddy-session-forensics/…` 已是历史路径**
|
||||
> —— 那两个目录 2026-10-01 已移到 `<工作区>/归档/技能-退役-20261001/`(⛔ 不在技能根了)。
|
||||
|
||||
文件总数:**56** | 语法 / 结构检查失败:**0**
|
||||
文件总数:**55** | 语法 / 结构检查失败:**0**
|
||||
|
||||
## ✅ 已完成 · 2026-10-05 「目标唯一性 + 换目标需确认」(用户口径落地)
|
||||
|
||||
@@ -56,7 +56,6 @@
|
||||
- `references/01-文档索引.md` — 3604 B |md5 `825999a411e2a4f6b740063f81c01b03`
|
||||
- `scripts/judge_audit.py` — 13562 B |md5 `8f14a973fa5709a3b9a60b065dc22d7d`
|
||||
- `scripts/workspace_mirror.py` — 17263 B |md5 `60f104b37df3133785abb13f686eea4f`
|
||||
- `assets/start-supervise.ps1.tpl` — 5360 B |md5 `3805f1a82a1747269212287e9dfde369`
|
||||
- `assets/design-tokens.css` — 6364 B |md5 `0668feb3a4729f1022e579ac0c5d2569`
|
||||
|
||||
## 最近改动(⛔ 只记「什么时候改了什么」,不写流水账)
|
||||
@@ -119,7 +118,6 @@
|
||||
| `assets/board-render-probe.js` | 14398 | `a21bd1905b34537436ce778c6bfbc288` | — |
|
||||
| `assets/board.html` | 151811 | `c597cd7f21543542e9cdbe486c5ccd79` | — |
|
||||
| `assets/design-tokens.css` | 6364 | `0668feb3a4729f1022e579ac0c5d2569` | — |
|
||||
| `assets/start-supervise.ps1.tpl` | 5778 | `4eae189c7c850e4748a0a62024f48127` | — |
|
||||
| `install.py` | 33311 | `ee11283cbcdbb4f821f021e86cff4498` | ok |
|
||||
| `references/00-动手前必过.md` | 7181 | `33bc86e442ccd4fcbb930c79c4ad0641` | — |
|
||||
| `references/01-文档索引.md` | 3604 | `825999a411e2a4f6b740063f81c01b03` | — |
|
||||
@@ -141,7 +139,7 @@
|
||||
| `scripts/board_ext.py` | 45655 | `2319fb9d2d21bf961e03a42da9a1ad4b` | ok |
|
||||
| `scripts/collabctl.py` | 28124 | `827fe07da25b86b773a3f701e86db351` | ok |
|
||||
| `scripts/collabd.config.example.json` | 1158 | `6655c15c411a84e3ca12fd136dbe2c60` | ok |
|
||||
| `scripts/collabd.py` | 431355 | `955bd92009d7cd0cccc53d867f591b49` | ok |
|
||||
| `scripts/collabd.py` | 433099 | `f177baed038b7ef90ae17d0abd175399` | ok |
|
||||
| `scripts/deliver-gateway-token.py` | 5685 | `cbba9648d404ecd2683061e19dbdb24a` | ok |
|
||||
| `scripts/deploy_code.py` | 5325 | `a79cf5e5ffb3e8c60a31b4d0d8845f40` | ok |
|
||||
| `scripts/forensics/proc-parent.py` | 2347 | `8cdfff2dbb88303298752c3777da7b20` | ok |
|
||||
@@ -156,7 +154,7 @@
|
||||
| `scripts/hooks/stop-dialog-guard.py` | 40247 | `4e5f9889717d4ed37a6384c643b4d05e` | ok |
|
||||
| `scripts/hooks/supervise-ensure-hook.py` | 14308 | `dc6f62584a8384baba0a1f2460828013` | ok |
|
||||
| `scripts/hooks/wb-result-hook.py` | 37199 | `2d4f694acd031222bf03b98a859b6e90` | ok |
|
||||
| `scripts/init_workspace.py` | 35817 | `c8b6bbbae01f21939ea2fd8d294d2726` | ok |
|
||||
| `scripts/init_workspace.py` | 24042 | `dda53acd24bcc66d509bbdab3ebc0488` | ok |
|
||||
| `scripts/judge_audit.py` | 13562 | `8f14a973fa5709a3b9a60b065dc22d7d` | ok |
|
||||
| `scripts/keepalive.py` | 12265 | `b84c753917e0a34cf40800bda953e7a4` | ok |
|
||||
| `scripts/lock/handoff-guard.sh` | 34616 | `f38b2ec92fdc1dab9abdaca0e0bb001a` | — |
|
||||
@@ -164,7 +162,7 @@
|
||||
| `scripts/lock/op-lock.sh` | 4690 | `1a31eda3642e23693a65e1519860c744` | — |
|
||||
| `scripts/lock/preflight-lock.sh` | 8673 | `e985ec1cb853fae4c351cd03b171355d` | — |
|
||||
| `scripts/mut_run.py` | 13323 | `3bffc12e1e57d5efc743bac55e584b62` | ok |
|
||||
| `scripts/selftest.py` | 469482 | `dc0f4801b2e4fa76e71f649b03f2ed50` | ok |
|
||||
| `scripts/selftest.py` | 481254 | `eb86a9165b77b63714ce948553088d72` | ok |
|
||||
| `scripts/session-rules-check.py` | 37640 | `552c90b93a051d775299170d5ff49b56` | ok |
|
||||
| `scripts/stop-collab.py` | 10462 | `d8043bedd23b52b6642aaf7ec8b8c980` | ok |
|
||||
| `scripts/supervise-launch.py` | 2503 | `f416a2f197e5524a8859c1d227ba1d05` | ok |
|
||||
|
||||
@@ -2480,9 +2480,44 @@ INBOX = '…\ai1net-dsh-server\tmp\supervise-inbox'
|
||||
(删掉 `_find_keeper_tpl()` 前置门槛 —— 否则缺模板就直接"没建",与启动器形态无关了)。
|
||||
2. 任务动作改指 **`pythonw.exe` + `<区>/.workbuddy/collab/supervise-launch.py`**
|
||||
(`-WorkingDirectory` =**工作区根**;pythonw 是 **GUI 子系统 ⇒ 零控制台 ⇒ 不闪窗**)。
|
||||
3. **任务名保持** `collabd-supervise-<区>`(⛔ 不改名 ⇒ `collabctl` 的名单不用动)。
|
||||
3. ~~**任务名保持** `collabd-supervise-<区>`(⛔ 不改名 ⇒ `collabctl` 的名单不用动)~~。
|
||||
🔴 **2026-10-06 订正 —— 这条结论是错的,已改**:
|
||||
当时以为"不改名就不用动 `collabctl` 的名单",但**正好反过来** ——
|
||||
`collabctl.py` 的 `SCHED_TASKS` 里列的 `collabd-supervise-<区>` 是**"旧形态 ⇒ 一律禁用"**的名单,
|
||||
⇒ 保留旧名的后果是:**`_escalate_to_keeper()` 自愈建起来的常驻,会被下一次 `collabctl off` 当旧形态干掉**。
|
||||
✅ 现行口径:**统一用 `collabd-keepalive-<区>`**(与 `KEEPALIVE_TASKS` 同名)——
|
||||
这样 `off` 认得它、`on` 会重建它,**开关才真正管得住这条自愈路径**。
|
||||
⚠️ **教训**:判"改不改名"时,⛔ 不能只看"改名的成本",要**查这个名字在别处被怎么用**
|
||||
—— 这里它同时是"检查名单"和"禁用名单"的键。
|
||||
4. `_escalate_to_keeper` 里**被删段**用 `~~删除线~~` 在 docstring 里标注(⛔ 别留下与实现不符的说明)。
|
||||
|
||||
### 三·补、🔴 2026-10-06 二次收口(第一次只改了动作,没改名字、也没扫旁路)
|
||||
|
||||
**三个漏网**(本轮实测抓到,`selftest` 探针一度只扫 `collabd.py` ⇒ 全都没报警):
|
||||
|
||||
1、**任务名两套并存** —— 见上面第 3 条的订正。现已统一 `collabd-keepalive-<区>`;
|
||||
触发形态也对齐(`-AtLogOn` → **每 5 分钟重复触发**;去掉 `RestartCount 999/1min` ——
|
||||
每 5 分钟重复本身就提供重拉,每分钟重启是多余抖动)。本区遗留的旧名任务已 `Disable`。
|
||||
2、**`init_workspace.py` 是第二个建任务者** —— 自带 `_register_keeper_task()` + `_keeper_task_state()`,
|
||||
动作写 `powershell.exe -File start-supervise.ps1`、还要铺 `assets/start-supervise.ps1.tpl`、
|
||||
自检里有 `_ps_check()`(验 ps1 语法)。**整份删掉**(含 `--with-keeper` / `--no-task` 两个开关),
|
||||
改为只检查 `supervise-launch.py` 在位并指向唯一入口 `collabctl.py`。
|
||||
⛔ **建任务只许有一处实现** = `collabctl.py`(受 `supervise.switch` 总电闸管辖);
|
||||
自己建就绕过开关,是"第二条起法"。模板文件一并删除。
|
||||
3、**守形态的探针打偏** —— `t_keeper_legacy_ps1_form_gone` 原来只 `read_text("collabd.py")`。
|
||||
✅ 已扩成扫**全族**(`collabd.py` / `init_workspace.py` / `collabctl.py`),并新增两条判据:
|
||||
「**任务名口径唯一**」、「`init_workspace.py` 旧形态死代码已清」。
|
||||
|
||||
⚠️ **探针本身的坑**:新增的"任务名唯一"判据**扫文本**,第一版**误红** ——
|
||||
它命中了 `collabctl.py` 里 `SCHED_TASKS = [...]` 那段(那是**故意**列旧名去禁用的)。
|
||||
⇒ 必须先 `re.sub` 剥掉该块再扫。**这是本项目第三次踩"判据扫到自己的否定句"**
|
||||
(前两次:查"PRD 有没有视觉词"命中免责声明、查"有没有六份旧产出"命中 PRD 里的禁令文本)。
|
||||
|
||||
⚠️ **另一条我自己的操作坑**:用 python 脚本读写 `init_workspace.py` / `collabd.py` 后,
|
||||
行尾从**纯 LF 被转成纯 CRLF**(技能包其余 7 个同族文件都是 LF)—— 已用 `write_bytes` 还原为 LF。
|
||||
⇒ **凡用脚本改技能源码,改完要核行尾**(否则 diff 里整个文件显示为"全改",审不出真改动)。
|
||||
|
||||
|
||||
### 四、✅ 验收(2026-10-05 12:42 实测)
|
||||
|
||||
- 跑一次 `_escalate_to_keeper()` ⇒ 回读任务动作:
|
||||
|
||||
@@ -1,5 +1,16 @@
|
||||
# 常驻程序长期在线(Windows)
|
||||
|
||||
> 🔴🔴 **2026-10-06 过期警告 · 读之前必看**
|
||||
> 本档 §一(包装脚本)、§三(铺脚本步骤)等节描述的 **`start-supervise.ps1` 形态已整套废弃**。
|
||||
> 现行形态见 `SKILL.md`「两个启动器」表 + `pitfalls.md` **P0-74**:
|
||||
> **计划任务 → `pythonw.exe` → `supervise-launch.py`**(GUI 子系统 ⇒ 零控制台 ⇒ **不闪窗**)。
|
||||
> ⛔ 凡本档出现 `start-supervise.ps1`、`assets/start-supervise.ps1.tpl`(**该模板已删除**)、
|
||||
> 或 `New-ScheduledTaskAction ... -File` 的段落,**一律按历史留痕读,⛔ 不许照着做**。
|
||||
> ⚠️ 另:本档所说"载体是可选需求、常态用不着计划任务"的**2026-10-04 口径已被推翻** ——
|
||||
> 2026-10-05 用户拍板「**常驻崩了,拉起来这个事儿,你不能也用计划任务起个程序吗?**」
|
||||
> ⇒ 计划任务现在是**既定形态**(每 5 分钟兜底),开关是 `supervise.switch`(`collabctl.py on|off`)。
|
||||
> 📌 **整份重写待办**(本轮只加了本警告块,未重写正文)。
|
||||
|
||||
> 📌 **本档=「想让目标检查程序长期在线」时的可选做法**(2026-10-03 夜里整理)。
|
||||
> 🔴 **注意:常态用不着它** —— 常态是「调用技能完成目标时起后台任务 + 检查程序」,
|
||||
> 那套一直好好在跑(见下面那段口径)。
|
||||
|
||||
@@ -744,7 +744,7 @@ def _escalate_to_keeper(detail: str = "") -> str:
|
||||
`powershell.exe -WindowStyle Hidden -File start-supervise.ps1`,而 PowerShell 是
|
||||
**控制台程序** ⇒ 每次触发都分配 `conhost.exe` ⇒ **闪黑窗**(用户 2026-10-05 报
|
||||
「又弹了窗口」,实测抓到 `powershell.exe` + `conhost.exe` 这对进程)。
|
||||
② 登记计划任务 `collabd-supervise-<区名>` —— 动作已改指
|
||||
② 登记计划任务 `collabd-keepalive-<区名>` —— 动作已改指
|
||||
**`pythonw.exe` + `<区>/.workbuddy/collab/supervise-launch.py`**(GUI 子系统,
|
||||
**零控制台** ⇒ 不闪窗),立即启动;
|
||||
③ 回读任务状态做**行为级验收**(⛔ 不满足于"命令没报错")。
|
||||
@@ -777,7 +777,19 @@ def _escalate_to_keeper(detail: str = "") -> str:
|
||||
if not _pyw.exists():
|
||||
_cands = sorted(Path(sys.executable).parent.parent.parent.glob("*/pythonw.exe"))
|
||||
_pyw = _cands[-1] if _cands else Path(sys.executable)
|
||||
_task = "collabd-supervise-%s" % (Path(WS).name or "ws")
|
||||
# 🔴🔴 2026-10-06 二次收口:**任务名与触发形态都对齐 `collabctl.py`**。
|
||||
# 原因(第三处「同一种东西有第二条起法」,本轮实测抓到):
|
||||
# 本函数原建 `collabd-supervise-<区>`,而 `collabctl.py` 建 `collabd-keepalive-<区>`
|
||||
# ⇒ 两个名字**各自对应一套形态**,且 `collabctl.py` 把 `collabd-supervise-*`
|
||||
# 列入 `SCHED_TASKS`(**一律禁用**)⇒ **本函数自我供给起的常驻,会被下一次
|
||||
# `collabctl off` 当"旧形态"干掉** ⇒ 自愈链断在这里。
|
||||
# ⇒ 统一到 `collabd-keepalive-<区>`(`KEEPALIVE_TASKS` 的名单口径):
|
||||
# `off` 认得它、`on` 会重建它 ⇒ **开关能真正管住这条自愈路径**。
|
||||
# ⚠️ 触发形态也一并对齐:`-AtLogOn`(只在登录起)→ **每 5 分钟重复触发**
|
||||
# (常驻中途死了要等下次登录 = 不自愈;5 分钟兜底才是既定口径)。
|
||||
# ⚠️ 旧的 `collabd-supervise-<区>` 若已存在 ⇒ **不在这里删**(清理归 `collabctl` 的
|
||||
# `SCHED_TASKS` 禁用名单,那块已覆盖)。
|
||||
_task = "collabd-keepalive-%s" % (Path(WS).name or "ws")
|
||||
_ps = (
|
||||
"$ErrorActionPreference='Stop';"
|
||||
"$n='%s';"
|
||||
@@ -787,12 +799,18 @@ def _escalate_to_keeper(detail: str = "") -> str:
|
||||
"$a=New-ScheduledTaskAction -Execute '%s' "
|
||||
"-Argument '\"%s\"' "
|
||||
"-WorkingDirectory '%s';"
|
||||
"$t=New-ScheduledTaskTrigger -AtLogOn;"
|
||||
# 🔴 每 5 分钟重复触发(与 `collabctl.py` 同口径)
|
||||
"$t=New-ScheduledTaskTrigger -Once -At (Get-Date) "
|
||||
"-RepetitionInterval (New-TimeSpan -Minutes 5);"
|
||||
# 🔴 ExecutionTimeLimit=0(长驻进程 ⛔ 不能被调度器到点掐死)
|
||||
# ⚠️ 去掉 `RestartCount 999 / RestartInterval 1min` —— 每 5 分钟重复触发
|
||||
# 本身就提供重拉;每分钟重启是多余的抖动(P0-74 记过"反复"的成因)。
|
||||
"$s=New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries "
|
||||
"-DontStopIfGoingOnBatteries -StartWhenAvailable "
|
||||
"-ExecutionTimeLimit ([TimeSpan]::Zero) -MultipleInstances IgnoreNew "
|
||||
"-RestartCount 999 -RestartInterval (New-TimeSpan -Minutes 1);"
|
||||
"Register-ScheduledTask -TaskName $n -Action $a -Trigger $t -Settings $s -Force|Out-Null;"
|
||||
"-ExecutionTimeLimit ([TimeSpan]::Zero) -MultipleInstances IgnoreNew -Hidden;"
|
||||
"$pr=New-ScheduledTaskPrincipal -UserId $env:USERNAME -LogonType Interactive -RunLevel Highest;"
|
||||
"Register-ScheduledTask -TaskName $n -Action $a -Trigger $t -Settings $s "
|
||||
"-Principal $pr -Force|Out-Null;"
|
||||
"Start-ScheduledTask -TaskName $n;"
|
||||
"$i=Get-ScheduledTaskInfo -TaskName $n;"
|
||||
"'OK State=' + (Get-ScheduledTask -TaskName $n).State + ' Last=' + $i.LastTaskResult"
|
||||
|
||||
@@ -133,6 +133,19 @@ RULES = (
|
||||
" · 提报时把技术话翻成**功能话**(“影响谁、断多久、花多少钱”),\n"
|
||||
" ⛔ 不写包名/环境变量/文件路径/代码标识符。一轮只问一个问题。\n"
|
||||
"\n"
|
||||
"🔴 **两个停止点:没结论就不要往下走**(十步里的第 1、4 步)\n"
|
||||
" ① **判类型**:这是「方案请求」还是「任务明确·直接执行」?\n"
|
||||
" 方案请求 ⇒ **只出方案**,未经明确授权 ⛔ 不改任何文件。\n"
|
||||
" ② **判方向**:这次改动是 **扩大 / 收窄 / 中性**?\n"
|
||||
" 🔴 **只要会「扩大可见面」⇒ 立即停手**,并把「要不要扩大」**单独**作为红线问题问,\n"
|
||||
" ⛔ **不许**把它和某个技术方案**捆成一条**问(捆包=逼用户在没看到技术选项时先答红线)。\n"
|
||||
"\n"
|
||||
"🔴 **技术方案怎么排序:自己排,⛔ 不问用户**(`dsh-decision` 默认裁决顺序)\n"
|
||||
" 只需比三条轴,**按序**取先满足的那条:\n"
|
||||
" ① **更小改动** → ② **更少新增形态** → ③ **不扩大可见面**。\n"
|
||||
" 排完后:**已定的写成陈述句**(「我选了什么,可推翻」),\n"
|
||||
" **只把真未定的**列进待拍板项 —— ⛔ 已定项混进待拍板清单 = 变相征询。\n"
|
||||
"\n"
|
||||
"🔴 **删/改/迁移先判代价对称性**(A8/A14/A16/A22)\n"
|
||||
" 删除收益 < 潜在破坏 ⇒ **标注废弃保留**,⛔ 不删。改名/迁移先列**伴随物清单**\n"
|
||||
" (只改主体必留隐患)。替换/退役:**先补位,再退役**。\n"
|
||||
|
||||
@@ -21,7 +21,6 @@
|
||||
· 排期名必须是两级前缀 `[主]-<类别>-<具体>`(⛔ 光杆名 ⇒ 角色判不出,看板认不出)
|
||||
"""
|
||||
import argparse
|
||||
import io
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
@@ -33,154 +32,10 @@ SK = Path(__file__).resolve().parent
|
||||
DEFAULT_TITLE = "检查会话协作是否运行正常 + 协作机制问题排查与修复"
|
||||
|
||||
|
||||
def _register_keeper_task(ws: Path, python_exe: Path) -> str:
|
||||
"""🔴🔴 给本工作区**登记一条计划任务**(="各工作区都能自己常驻"的落点)。
|
||||
|
||||
## 为什么必须有这一步(2026-10-04 用户原话)
|
||||
|
||||
> 「我需要的是**各工作区会话 完成目标时 自己能建立 常驻程序**」
|
||||
|
||||
病根链(逐层实测):
|
||||
① `ensure_supervise()` 能起常驻,但起法(`DETACHED_PROCESS`)**不脱离会话作业对象**
|
||||
⇒ 会话一收工就被回收(vibe 会话查出,我已复验);
|
||||
② 唯一合格载体=**计划任务**(父链根在 `svchost -s Schedule`,与会话树无关);
|
||||
③ 而 `init_workspace.py` 原来**只铺脚本、⛔ 不登记任务**(当时口径:"
|
||||
开机自启不是需求")⇒ 于是"只有某个区能常驻"。
|
||||
|
||||
## ⚖️ 边界(⛔ 不许越)
|
||||
|
||||
· **本函数是"登记"的唯一实现** —— ⛔ 别在别处再写一份(那是漂移的源头);
|
||||
· ⛔ **只登记本区**(任务名带区名,各区互不干扰);
|
||||
· ⛔ **不代用户开目标** —— 没目标就没有常驻,那是正常态;
|
||||
· ✅ **幂等**:重复跑=覆盖同一条任务,⛔ 不产生第二条。
|
||||
|
||||
## 返回
|
||||
|
||||
人类可读的一行结论(调用方直接打印)。⛔ 不抛异常(登记失败要能继续跑完别的步骤)。
|
||||
"""
|
||||
_task = "collabd-keepalive-%s" % (ws.name or "ws")
|
||||
# 🔴🔴 2026-10-06 收口(P0-74):动作=**`pythonw.exe` + `supervise-launch.py`**。
|
||||
# 旧形态是 `powershell.exe -File start-supervise.ps1` —— PowerShell 是**控制台程序**
|
||||
# ⇒ 每次触发分配 `conhost.exe` ⇒ **闪一下黑窗**(用户 2026-10-05:「又弹了窗口」)。
|
||||
# ⚠️ 旧注释的理由「keeper 里要 set 那几个 env ⇒ 直接跑解释器没有设环境变量这一步」
|
||||
# **已被 `supervise-launch.py` 推翻** —— 那个启动器存在的**全部目的**就是在进程内设 env。
|
||||
# ⛔ **登记动作仍走 PowerShell**(那是在**登记**,不是在**当看守**,两者别混)。
|
||||
_pyw = Path(python_exe).with_name("pythonw.exe")
|
||||
if not _pyw.is_file(): # ⛔ 没同版本 pythonw ⇒ 找全局那份
|
||||
_cands = sorted(Path(python_exe).parent.parent.parent.glob("*/pythonw.exe"))
|
||||
_pyw = _cands[-1] if _cands else Path(python_exe)
|
||||
_launcher = ws / ".workbuddy" / "collab" / "supervise-launch.py"
|
||||
# 🔴 `-WorkingDirectory` **必须是「工作区根」**(⛔ 不是脚本目录):
|
||||
# `collabd.load_cfg()` 按 `<cwd>/.workbuddy/collab/collabd.config.json` 找配置;
|
||||
# cwd 设成脚本目录 ⇒ 它去找 `<ws>/.workbuddy/collab/.workbuddy/collab/…` ⇒ **找不到**
|
||||
# ⇒ `CFG_MISSING` ⇒ `--supervise` 拒跑(实测 rc=2;任务侧 `LastTaskResult=1`)。
|
||||
_wd = str(ws).replace("/", "\\")
|
||||
_ps = (
|
||||
"$ErrorActionPreference='Stop';"
|
||||
"$n='%s';"
|
||||
# 🔴 幂等:先注销同名(`-Confirm:$false` 免交互;宿主可能 -NonInteractive)
|
||||
"if(Get-ScheduledTask -TaskName $n -ErrorAction SilentlyContinue){"
|
||||
"Unregister-ScheduledTask -TaskName $n -Confirm:$false};"
|
||||
# 🔴 动作=`pythonw.exe` + 启动器(GUI 子系统 ⇒ 零 conhost ⇒ **不闪窗**)
|
||||
"$a=New-ScheduledTaskAction -Execute '%s' -Argument '\"%s\"' -WorkingDirectory '%s';"
|
||||
# 🔴 `RepetitionInterval` 5 分钟(与 `collabctl.py` 的既定口径一致)
|
||||
"$t=New-ScheduledTaskTrigger -Once -At (Get-Date) "
|
||||
"-RepetitionInterval (New-TimeSpan -Minutes 5);"
|
||||
# 🔴 四个设置缺一不可(逐条理由见 references/supervise-persistence.md §二②):
|
||||
# ExecutionTimeLimit=0 ⇒ 默认 72h 会把常驻杀掉;
|
||||
# ⚠️ ⛔ 不许设 2 分钟(那会被调度器到点掐死 ⇒ 每 2 分钟重建一次 = 抖动)
|
||||
# MultipleInstances=IgnoreNew ⇒ 防双写台账
|
||||
# -Hidden ⇒ 计划任务库里不显眼
|
||||
"$s=New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries "
|
||||
"-DontStopIfGoingOnBatteries -StartWhenAvailable "
|
||||
"-ExecutionTimeLimit ([TimeSpan]::Zero) -MultipleInstances IgnoreNew -Hidden;"
|
||||
"$pr=New-ScheduledTaskPrincipal -UserId $env:USERNAME -LogonType Interactive -RunLevel Highest;"
|
||||
"Register-ScheduledTask -TaskName $n -Action $a -Trigger $t -Settings $s -Principal $pr -Force|Out-Null;"
|
||||
# 🔴 立刻起一次(⛔ 别等下次触发 —— 否则"配好了"这个结论当场不可验)
|
||||
"Start-ScheduledTask -TaskName $n;"
|
||||
"'OK ' + $n"
|
||||
) % (_task, str(_pyw), str(_launcher), _wd)
|
||||
try:
|
||||
_r = subprocess.run(["powershell.exe", "-NoProfile", "-ExecutionPolicy", "Bypass",
|
||||
"-Command", _ps],
|
||||
capture_output=True, timeout=180,
|
||||
# 🔴 `CREATE_NO_WINDOW`(⛔ 缺了每次登记都闪一下)
|
||||
creationflags=0x08000000)
|
||||
_o = _r.stdout.decode("utf-8", "replace").strip()
|
||||
_e = _r.stderr.decode("utf-8", "replace").strip()
|
||||
if "OK " in _o:
|
||||
return ("✅ 已登记计划任务 `%s`(pythonw + supervise-launch.py · 每 5 分钟 · "
|
||||
"ExecutionTimeLimit=0 · IgnoreNew)并已启动" % _task)
|
||||
# ⛔ 失败要**报原文**,⛔ 不许编原因
|
||||
_msg = (_e or _o or "无回显").splitlines()
|
||||
return ("⛔ 登记失败:%s\n ⚠️ 若提示权限/策略 ⇒ 走 PowerShell 工具手动建"
|
||||
"(`Register-ScheduledTask`;⛔ 别用 `schtasks.exe` —— 本机黑名单拦它)"
|
||||
% (_msg[0][:160] if _msg else "无"))
|
||||
except Exception as _ex:
|
||||
return "⛔ 登记失败(异常):%r" % (_ex,)
|
||||
|
||||
|
||||
def _keeper_task_state(ws: Path) -> str:
|
||||
"""读本区计划任务状态(⛔ 只读;用来做"到底建成了没"的行为级验收)。"""
|
||||
_task = "collabd-keepalive-%s" % (ws.name or "ws")
|
||||
_ps = ("$n='%s';"
|
||||
"$t=Get-ScheduledTask -TaskName $n -ErrorAction SilentlyContinue;"
|
||||
"if(-not $t){'MISSING'}else{"
|
||||
"$i=Get-ScheduledTaskInfo -TaskName $n;"
|
||||
"'State=' + $t.State + ' Last=' + $i.LastTaskResult}"
|
||||
) % _task
|
||||
try:
|
||||
_r = subprocess.run(["powershell.exe", "-NoProfile", "-Command", _ps],
|
||||
capture_output=True, timeout=60)
|
||||
return _r.stdout.decode("utf-8", "replace").strip() or "(无回显)"
|
||||
except Exception as _ex:
|
||||
return "(读不到:%r)" % (_ex,)
|
||||
|
||||
|
||||
def _ps_check(ps1: Path) -> str:
|
||||
"""🔴🔴 验收一份 `.ps1` **确实是合法 PowerShell**(返回''=没问题,否则返回提示串)。
|
||||
|
||||
病根(2026-10-04 事故,P0-56):模板首行被误打成「三连双引号 + #」开头,
|
||||
生成物被 PowerShell 判 9 处语法错、**一秒内秒退** ⇒ 常驻零续命。
|
||||
⚠️ 而当时自检只查 ①UTF-8 BOM ②模板变量是否填完 —— **两个表层特征全绿** ⇒ 静默通过。
|
||||
|
||||
⇒ 判据必须**行为级**:真调 PowerShell 自己的解析器(`Parser::ParseFile`),
|
||||
⛔ 不查关键字、⛔ 不用正则猜。拿不到解析器时**返回"没验"**(⛔ 不当通过)。
|
||||
"""
|
||||
_exe = None
|
||||
for _c in ("powershell.exe", "pwsh.exe"):
|
||||
try:
|
||||
subprocess.run([_c, "-NoProfile", "-Command", "exit 0"],
|
||||
capture_output=True, timeout=20)
|
||||
_exe = _c
|
||||
break
|
||||
except Exception:
|
||||
continue
|
||||
if not _exe:
|
||||
return " ⚠️ 没法验语法(本机没有 PowerShell)"
|
||||
_cmd = (
|
||||
"[Console]::OutputEncoding=[Text.Encoding]::UTF8;"
|
||||
"$e=$null;$t=$null;"
|
||||
"[System.Management.Automation.Language.Parser]::ParseFile('%s',[ref]$t,[ref]$e)|Out-Null;"
|
||||
"if($e -and $e.Count -gt 0){'ERR '+$e.Count+' @L'+$e[0].Extent.StartLineNumber+': '+$e[0].Message}"
|
||||
"else{'OK'}"
|
||||
) % str(ps1).replace("'", "''")
|
||||
try:
|
||||
_r = subprocess.run([_exe, "-NoProfile", "-Command", _cmd],
|
||||
capture_output=True, timeout=60)
|
||||
_o = _r.stdout.decode("utf-8", "replace").strip()
|
||||
if _o.startswith("OK"):
|
||||
return ""
|
||||
if _o.startswith("ERR"):
|
||||
# ⚠️ PowerShell 的报错文案**含换行与中文** ⇒ 只取**第一行**、截断到 60 字,
|
||||
# ⛔ 否则整个自检输出被它冲垮(实测回显里混着源码行)。
|
||||
_msg = _o[4:].splitlines()[0].strip() if _o[4:].strip() else ""
|
||||
if len(_msg) > 60:
|
||||
_msg = _msg[:60] + "…"
|
||||
return " ⛔ **语法不过**(%s)⇒ 计划任务会秒退 ⇒ 常驻没人续命" % _msg
|
||||
return " ⚠️ 语法没验成(回显异常:%s)" % ((_o.splitlines() or [""])[0][:60] or "(空)")
|
||||
except Exception as _e:
|
||||
return " ⚠️ 语法没验成(%r)" % (_e,)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
@@ -191,11 +46,9 @@ def main() -> int:
|
||||
ap.add_argument("--topics", default="会话协作自检,机制排查与修复")
|
||||
ap.add_argument("--port", type=int, default=0)
|
||||
ap.add_argument("--python", default=sys.executable)
|
||||
ap.add_argument("--no-task", action="store_true",
|
||||
help="只铺 start-supervise.ps1,⛔ 不登记计划任务(要手动登记时用)")
|
||||
# 🔴 2026-10-04 口径订正:**默认不铺载体**(常态用不着;见 SKILL.md R 红线口径段)
|
||||
ap.add_argument("--with-keeper", action="store_true",
|
||||
help="铺 start-supervise.ps1(目标做完还要让程序继续跑时才用)")
|
||||
# ⛔ 2026-10-06 删掉 `--no-task` / `--with-keeper` 两个开关:
|
||||
# 它们的存在前提是"本脚本会铺 ps1 并登记计划任务",而那条形态**已整套废弃**(P0-74)。
|
||||
# ⇒ 常驻的开/关/自愈统一走 `collabctl.py on|off|ensure`,⛔ 本脚本不再碰任务。
|
||||
a = ap.parse_args()
|
||||
|
||||
ws = Path(a.workspace).resolve()
|
||||
@@ -454,73 +307,27 @@ def main() -> int:
|
||||
print(" 要并看本区 ⇒ 把 `%s` 加进**主工作区**配置的 `peer_workspaces`(只读),重启主看板。" % ws_s)
|
||||
if _fallback:
|
||||
print(" ⚠️ 上面是**技能目录那份**(副本还没分发)⇒ 先跑 deploy_code.py --ws %s" % ws_s)
|
||||
# ══ ⑥ 🔴🔴 **铺常驻载体**(2026-10-04:用户「任何工作区都要能自己配好」)
|
||||
# 病根(实测):全机 262 个计划任务里**只有 `collabd-supervise-ws3` 一个**是本机制的
|
||||
# ⇒ 5 个部署区**只有测试3 有载体** ⇒ 其余区的常驻**没人续命**(实测停摆 15~20 小时)。
|
||||
# 根因再深一层:那份 `start-supervise.ps1` 里**工作区路径硬编码了 11 处**
|
||||
# ⇒ 别的区**没法直接用**,于是就都没建。
|
||||
# ✅ 做法=**模板 + 按区填变量**:模板在 `assets/start-supervise.ps1.tpl`。
|
||||
_tpl = SK.parent / "assets" / "start-supervise.ps1.tpl" # ⛔ `SK` 指向 **scripts/**,包根要上一层
|
||||
_keeper = ws / ".workbuddy" / "collab" / "start-supervise.ps1"
|
||||
# ══ ⑥ 🔴🔴 **常驻载体**(2026-10-04:用户「任何工作区都要能自己配好」)
|
||||
# 🔴🔴 2026-10-06 收敛(依据:pitfalls §P0-74 的定案三条 + SKILL.md「两条起法必须收敛」):
|
||||
# ⛔ **不再铺 `start-supervise.ps1`**、⛔ **不再做 ps1 语法校验**、
|
||||
# ⛔ **不再依赖 `assets/start-supervise.ps1.tpl`**。
|
||||
# 旧形态=铺 ps1 + 任务动作 `powershell.exe -File start-supervise.ps1`
|
||||
# ⇒ PowerShell 是**控制台程序** ⇒ 每次触发分配 `conhost.exe` ⇒ **闪黑窗**
|
||||
# (用户 2026-10-05「又弹了窗口」;实测抓到 `powershell.exe` + `conhost.exe` 这对进程)。
|
||||
# 新形态=**计划任务 → `pythonw.exe` → `supervise-launch.py`**(GUI 子系统 ⇒ 零控制台)。
|
||||
# ⚠️ env(`CODEBUDDY_CONFIG_DIR` / `COLLABD_CONFIG`)由**启动器在进程内设** ——
|
||||
# 这正是 `supervise-launch.py` 存在的全部理由(旧注释"直接跑解释器没法设 env"已被它推翻)。
|
||||
# ⛔ **建任务这件事只许有一处实现** = `collabctl.py`(常驻唯一管理入口,受 `supervise.switch`
|
||||
# 总电闸管辖)。本脚本**不建任务** —— 自己建就绕过了开关,是"第二条起法"。
|
||||
_cdir = ws / ".workbuddy" / "collab"
|
||||
_logs = _cdir / "logs"
|
||||
_tmp = ws / "tmp"
|
||||
for _d in (_logs, _tmp):
|
||||
_d.mkdir(parents=True, exist_ok=True)
|
||||
# 🔴🔴 2026-10-04 **口径订正**(用户原话:「从来没说过什么开机自启,
|
||||
# 只有调用技能完成目标时启动 后台任务和检查程序」):
|
||||
# **常态不需要载体脚本,也不需要计划任务。**
|
||||
# ⛔ 本步改成**默认不铺**,只有显式 `--with-keeper` 才铺
|
||||
# (⛔ 原来无条件铺 = 把"我以为你要长期在线"当需求做掉了,
|
||||
# 还在输出里说「还差一步:登记计划任务」⇒ 凭空造出一个欠项)。
|
||||
if not a.with_keeper:
|
||||
print("⑥ 常驻载体:**跳过**(⛔ 默认不铺)")
|
||||
print(" 📌 常态=调用技能完成目标时起后台任务 + 检查程序,⛔ 用不着载体。")
|
||||
print(" 📌 若确实要「目标做完程序还继续跑」⇒ 加 `--with-keeper` 重跑本脚本,")
|
||||
print(" 它会铺 `start-supervise.ps1` 并**打印**计划任务参数(⛔ 仍不代建)。")
|
||||
elif _tpl.is_file():
|
||||
# pythonw 必须**同版本**(⛔ 用 sys.executable 推不出同目录 pythonw)
|
||||
_pyw_exe = Path(a.python)
|
||||
_pyw = _pyw_exe.with_name("pythonw.exe")
|
||||
if not _pyw.exists(): # ⛔ 没同版本 pythonw ⇒ 找全局那份
|
||||
_cands = sorted(_pyw_exe.parent.parent.parent.glob("*/pythonw.exe"))
|
||||
_pyw = _cands[-1] if _cands else _pyw_exe
|
||||
_ws_native = str(ws) # ps1 里要**原生反斜杠**路径
|
||||
_vals = {
|
||||
"__WS__": _ws_native,
|
||||
"__CFGDIR__": os.environ.get("CODEBUDDY_CONFIG_DIR") or str(Path.home() / ".workbuddy"),
|
||||
"__COLLABDCFG__": str(cfgp),
|
||||
"__PYW__": str(_pyw),
|
||||
"__SCRIPT__": str(_own if _own.is_file() else (SK / "collabd.py")),
|
||||
"__HBPATH__": str(_logs / "supervise-heartbeat.json"),
|
||||
"__LOG__": str(_tmp / "keeper.log"),
|
||||
"__STAMP__": str(_tmp / "keeper-last-restart.txt"),
|
||||
"__STOPFLAG__": str(ws / "tmp" / "supervise-inbox" / "guard.stop"),
|
||||
"__OUTLOG__": str(_logs / "supervise.out.log"),
|
||||
"__ERRLOG__": str(_logs / "supervise.err.log"),
|
||||
}
|
||||
_t = io.open(_tpl, encoding="utf-8").read()
|
||||
for _k, _v in _vals.items():
|
||||
_t = _t.replace(_k, _v)
|
||||
# 🔴 必须 **UTF-8 带 BOM**(⛔ 不带 ⇒ PowerShell 5.1 按 GBK 解码 ⇒ CJK 路径被毁
|
||||
# ⇒ 任务一秒内 Result=1 退出 —— 这是实测踩过的)
|
||||
io.open(_keeper, "w", encoding="utf-8-sig", newline="\r\n").write(_t)
|
||||
print("⑥ 常驻载体已铺:%s" % _keeper.relative_to(ws))
|
||||
# 🔴🔴 2026-10-04 用户口径:「我需要的是**各工作区会话 完成目标时 自己能建立 常驻程序**」
|
||||
# ⇒ **登记这一步由本脚本自己做掉**(⛔ 不再只是打印参数让人去手工建 ——
|
||||
# 实测结果就是"谁也没建",全机只剩 1 条指向已删区的任务)。
|
||||
# ⚠️ 幂等:重复跑=覆盖同一条任务名(`_register_keeper_task` 内先 Unregister)。
|
||||
# ⚠️ 只登记**本区**;⛔ 不动别的区、⛔ 不动用户的其它任务。
|
||||
if a.no_task:
|
||||
print(" ⏭ `--no-task` ⇒ **跳过登记计划任务**(只铺了脚本)")
|
||||
_launcher = _cdir / "supervise-launch.py"
|
||||
if _launcher.is_file():
|
||||
print("⑥ 常驻载体:✅ 启动器在位(`%s`)" % _launcher.relative_to(ws))
|
||||
else:
|
||||
print(" " + _register_keeper_task(ws, _keeper))
|
||||
print(" 🔎 复核:%s" % _keeper_task_state(ws))
|
||||
else:
|
||||
print("⑥ ⚠️ 找不到载体模板 `assets/start-supervise.ps1.tpl` ⇒ **未铺**"
|
||||
"(⚠️ 只有传了 `--with-keeper` 才需要它)")
|
||||
print(" 🔴 **登记范围**:⛔ 本脚本只登记**本工作区**那一条任务(任务名带区名)——")
|
||||
print(" ⛔ 不碰别的区、⛔ 不动机器上其它任务;重复跑=覆盖同一条(幂等)。")
|
||||
print("⑥ 常驻载体:⛔ 缺启动器 `supervise-launch.py`")
|
||||
print(" 📌 先跑 `deploy_code.py --ws %s` 分发副本。" % ws_s)
|
||||
print(" 📌 常驻的开/关/自愈**一律走唯一入口**:`collabctl.py on|off|ensure`")
|
||||
print(" (⛔ 本脚本不建计划任务 —— 那会绕过 `supervise.switch` 总电闸,是「第二条起法」)")
|
||||
|
||||
# ⑤ 自检收口放在⑥ 之后(⛔ 顺序:先铺载体再体检,否则体检会漏掉"载体有没有")
|
||||
print()
|
||||
@@ -544,32 +351,19 @@ def main() -> int:
|
||||
_o2 = _r2.stdout.decode("utf-8", "replace")
|
||||
_ws_ok = ("COLLABD_CONFIG" in _o2) or (ws_s in _o2)
|
||||
print(" `collabd --where` 认这个工作区 = %s(rc=%s)" % ("✅" if _ws_ok else "⛔", _r2.returncode))
|
||||
# 🔴🔴 2026-10-04 补:**载体**(实测全机只有 1 个区有 ⇒ 其余区常驻没人续命)
|
||||
_kb = ws / ".workbuddy" / "collab" / "start-supervise.ps1"
|
||||
if _kb.is_file():
|
||||
_raw = _kb.read_bytes()
|
||||
_bom = _raw[:3] == b"\xef\xbb\xbf"
|
||||
_no_var = "__" in _raw.decode("utf-8-sig", "ignore")
|
||||
# 🔴🔴 2026-10-04(P0-56):**必须验「这是不是合法 PowerShell」**
|
||||
# 病根:原自检只查 ①BOM ②模板变量填完没 —— 两个都是**表层特征**。
|
||||
# 实测事故:模板首行被我误打成「三连双引号 + #」⇒ 生成的 `.ps1`
|
||||
# 被 PowerShell 判 **9 处语法错**、一秒内秒退 ⇒ 常驻 0 续命。
|
||||
# ⚠️ 而 BOM 在、变量填完了、文件也在 ⇒ **原自检全绿** ⇒ 静默通过。
|
||||
# ⇒ 判据必须**行为级**:真拿 PowerShell 的解析器解析一遍,⛔ 不查关键字。
|
||||
_syn = _ps_check(_kb)
|
||||
print(" 载体脚本 = %s%s%s%s"
|
||||
% ("✅ 在位", "" if _bom else "(⛔ **缺 UTF-8 BOM** ⇒ PowerShell 5.1 会按 GBK 解码"
|
||||
"⇒ CJK 路径被毁 ⇒ 任务秒退)",
|
||||
" ⛔ 模板变量没填完" if _no_var else "",
|
||||
_syn))
|
||||
# 🔴🔴 2026-10-06 收敛(P0-74):载体从 `start-supervise.ps1` 换成 **`supervise-launch.py`**。
|
||||
# ⇒ 自检一并改:**不再验 ps1、不再验 UTF-8 BOM、不再验模板变量填完没** ——
|
||||
# 那三个判据的**被测对象已经不存在了**(留着就是过期的探照灯)。
|
||||
# 判据换成**行为级可核的一条**:启动器在位。
|
||||
# ⚠️ 任务本身**不在这里验** —— 建任务归 `collabctl.py`(唯一实现),
|
||||
# 本脚本自己建/自己验就绕过了 `supervise.switch` 总电闸。
|
||||
_lc = ws / ".workbuddy" / "collab" / "supervise-launch.py"
|
||||
if _lc.is_file():
|
||||
print(" 常驻启动器 = ✅ 在位(`supervise-launch.py`)")
|
||||
else:
|
||||
# 🔴 2026-10-04 口径订正:⛔ **不许**把「没有载体」说成缺陷。
|
||||
# 常态=调用技能完成目标时起后台任务 + 检查程序,⛔ 用不着载体;
|
||||
# 「目标做完程序还继续跑」是**可选需求** ⇒ 没铺只是「没选它」,⛔ 不是「坏了」。
|
||||
# ⛔ 旧文案「常驻跨会话必被带走」是**编造的后果**(实测常驻是孤儿进程,
|
||||
# 独立存活 24.7 h)⇒ 本轮订正。
|
||||
print(" 载体脚本 = ⛔ 没铺(📌 **这不是问题**:常态用不着它;"
|
||||
"确实要「目标做完程序还继续跑」⇒ 加 `--with-keeper` 重跑)")
|
||||
print(" 常驻启动器 = ⛔ **缺**(先跑 `deploy_code.py --ws %s` 分发副本)" % ws_s)
|
||||
print(" 📌 常驻开/关/自愈走唯一入口:`collabctl.py on|off|ensure`"
|
||||
"(本区任务名 `collabd-keepalive-%s`)" % (ws.name or "ws"))
|
||||
if _dp.get("deployed") and _ws_ok and not (_dp.get("missing") or []) :
|
||||
print(" ✅ **本工作区可以直接用**(配置/目标/任务图齐了,程序也认)")
|
||||
else:
|
||||
|
||||
@@ -6606,25 +6606,63 @@ def t_keeper_legacy_ps1_form_gone():
|
||||
④ `CREATE_NO_WINDOW` 在位;
|
||||
⑤ `_find_keeper_tpl` 死代码已清。
|
||||
"""
|
||||
src = (HERE / "collabd.py").read_text(encoding="utf-8")
|
||||
# 🔴🔴 2026-10-05 **必须剔注释 _和 docstring_ 再判**(同一课第 N 遍,这次真栽了):
|
||||
# 考古记录**不止在 `#` 注释里** —— 还大量写在 **docstring**(`"""…"""`)里。
|
||||
# 第一版只剔 `#` 行 ⇒ 744 行那句(在 `_escalate_to_keeper` 的 docstring 里)
|
||||
# **照样命中** ⇒ 误红。⚠️ 「剔注释」≠「剔 docstring」,两者是**两件事**。
|
||||
# ✅ 正解=用 `tokenize` 剔掉 COMMENT + STRING(最稳,不怕多行字符串/嵌套引号)。
|
||||
_code = _strip_comments_and_docs(src)
|
||||
# 🔴🔴 2026-10-06 **扩到三个文件**(本轮实测抓到的探照灯打偏):
|
||||
# 原版只扫 `collabd.py` ⇒ 而**同族的 `init_workspace.py` 整份没被扫到**,
|
||||
# 它揣着 `start-supervise.ps1.tpl` 路径拼接 + `-File start-supervise.ps1` 动作
|
||||
# 活到今天(`_register_keeper_task` 又写了一份建任务代码 = 第二套起法)。
|
||||
# ⚠️ 教训:**判据只扫"我以为哪个文件"就等于没扫** —— 凡是"这一族文件都不许有 X",
|
||||
# 必须**枚举整族**,⛔ 不许只挑一个代表。
|
||||
_FAMILY = ["collabd.py", "init_workspace.py", "collabctl.py"]
|
||||
res = []
|
||||
res.append(("⛔ 已无「读 `start-supervise.ps1.tpl`」的路径拼接(形态废弃 ⇒ 不该有人再找它)",
|
||||
"start-supervise.ps1.tpl" not in _code))
|
||||
res.append(("🔴 任务动作=`pythonw.exe` + 启动器 `supervise-launch.py`",
|
||||
"New-ScheduledTaskAction" in _code and "supervise-launch.py" in _code
|
||||
and "with_name" in _code and "pythonw.exe" in src))
|
||||
res.append(("🔴 动作里**不再有** `-File start-supervise.ps1`(⛔ 壳脚本当看守=闪黑窗)",
|
||||
"-File start-supervise.ps1" not in _code))
|
||||
res.append(("🔴 `CREATE_NO_WINDOW`(0x08000000)在位(⛔ 缺了每次登记都闪一下)",
|
||||
"0x08000000" in _code))
|
||||
res.append(("`_find_keeper_tpl` 死代码已清(⛔ 留着会再招一批过期判据)",
|
||||
"def _find_keeper_tpl(" not in _code))
|
||||
_codes = {}
|
||||
for _n in _FAMILY:
|
||||
_p = HERE / _n
|
||||
if not _p.is_file():
|
||||
res.append((f"⛔ `{_n}` 存在(同族文件缺了 ⇒ 判据无从施加)", False))
|
||||
continue
|
||||
_s = _p.read_text(encoding="utf-8")
|
||||
_codes[_n] = (_s, _strip_comments_and_docs(_s))
|
||||
|
||||
# ① 整族:⛔ 谁也不许再去找那份废模板
|
||||
_bad = [n for n, (_, c) in _codes.items() if "start-supervise.ps1.tpl" in c]
|
||||
res.append(("⛔ 全族已无「读 `start-supervise.ps1.tpl`」的路径拼接(形态废弃 ⇒ 不该有人再找它)",
|
||||
not _bad))
|
||||
# ② 整族:⛔ 谁也不许把壳脚本当看守
|
||||
_bad = [n for n, (_, c) in _codes.items() if "-File start-supervise.ps1" in c]
|
||||
res.append(("⛔ 全族动作里**不再有** `-File start-supervise.ps1`(⛔ 壳脚本当看守=闪黑窗)",
|
||||
not _bad))
|
||||
# ③ 建任务者:动作必须是 pythonw + 启动器
|
||||
_mk = {n: c for n, (_, c) in _codes.items() if "New-ScheduledTaskAction" in c}
|
||||
_ok = bool(_mk)
|
||||
_ok = _ok and all("supervise-launch.py" in c for c in _mk.values())
|
||||
_ok = _ok and all("pythonw.exe" in _codes[n][0] for n in _mk)
|
||||
res.append(("🔴 建任务者(%s)动作=`pythonw.exe` + 启动器 `supervise-launch.py`"
|
||||
% "/".join(sorted(_mk)), bool(_ok)))
|
||||
# ④ 建任务者:`CREATE_NO_WINDOW` 在位
|
||||
_bad = [n for n, c in _mk.items() if "0x08000000" not in c]
|
||||
res.append(("🔴 建任务者 `CREATE_NO_WINDOW`(0x08000000)在位(⛔ 缺了每次登记都闪一下)",
|
||||
not _bad))
|
||||
# ⑤ ⭐⭐ **任务名口径必须唯一**(本轮抓到的病:`collabd.py` 建 `collabd-supervise-*`,
|
||||
# 而 `collabctl.py` 建 `collabd-keepalive-*` ⇒ 前者在 `SCHED_TASKS` 禁用名单里
|
||||
# ⇒ **自愈起的常驻会被下一次 `off` 当旧形态干掉**)
|
||||
_names = set()
|
||||
for n, c in _mk.items():
|
||||
# ⚠️ **先剥掉「禁用名单」块再扫** —— `SCHED_TASKS` 里是**故意**列着旧名去**禁用它**的,
|
||||
# 不剥就会把"它自己的否定句"当成违规。
|
||||
# 🔴 这是本项目**第三次**踩同一个坑(前两次:查"PRD 有没有视觉词"命中免责声明、
|
||||
# 查"有没有六份旧产出"命中 PRD 里的禁令文本)⇒ 判据凡扫文本,先问"这句是不是它的否定句"。
|
||||
c2 = re.sub(r"SCHED_TASKS\s*=\s*\[[^\]]*\]", "", c)
|
||||
_names |= set(re.findall(r'"collabd-(?:supervise|keepalive)-', c2))
|
||||
res.append(("⭐⭐ 任务名口径唯一(建任务者只许用 `collabd-keepalive-`;"
|
||||
"⛔ 出现 `collabd-supervise-` 会被 `off` 当旧形态禁掉)|实测命中:%s"
|
||||
% (sorted(_names) or "无"),
|
||||
_names == {"\"collabd-keepalive-"}))
|
||||
# ⑥ init_workspace 侧:死代码已清
|
||||
_iw = _codes.get("init_workspace.py", ("", ""))[1]
|
||||
_dead = [d for d in ("def _register_keeper_task(", "def _keeper_task_state(",
|
||||
"def _ps_check(", "def _find_keeper_tpl(") if d in _iw]
|
||||
res.append(("`init_workspace.py` 旧形态死代码已清(⛔ 留着会再招一批过期判据)|实测残留:%s"
|
||||
% (_dead or "无"), not _dead))
|
||||
return res
|
||||
|
||||
|
||||
|
||||
Reference in new issue
Block a user