From 9a152a19524418484f24dcb3b67f202993d67a26 Mon Sep 17 00:00:00 2001 From: maogeigei Date: Wed, 7 Oct 2026 08:15:31 +0800 Subject: [PATCH] =?UTF-8?q?=E5=8A=A0=E8=BD=BD=E9=97=B8=E9=97=A8=E4=B8=A4?= =?UTF-8?q?=E5=A4=84=E6=94=B6=E5=8F=A3=EF=BC=9A=E6=B4=BE=E6=B4=BB=E6=97=8F?= =?UTF-8?q?=E4=B8=8D=E5=8F=97=E5=86=B7=E5=8D=B4=20=EF=BC=8B=20=E5=86=B7?= =?UTF-8?q?=E5=8D=B4=E8=B7=B3=E8=BF=87=E7=95=99=E7=97=95=EF=BC=88=E7=94=A8?= =?UTF-8?q?=E6=88=B7=E9=97=AE=E3=80=8C=E6=9C=89=E4=BB=80=E4=B9=88=E5=BD=B1?= =?UTF-8?q?=E5=93=8D=20=E8=AF=A5=E5=A6=82=E4=BD=95=E5=A4=84=E7=90=86?= =?UTF-8?q?=E3=80=8D=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 一、影响评估(实测,会话 b232218f) - 冷却窗口 COOLDOWN=300;旧实现:命中 → 查冷却 → 落在窗口内就 return,**且只在注入成功后才写日志**。 - 后果两条: ① 01:32:00 命中后,**01:33:46 那条最明确的触发句**(「每次我说使用执行会话完成目标…」) 距上次仅 105 s ⇒ **被吞掉,那一轮 AI 收不到任何提醒**; ② 被吞掉**连一行痕迹都没有** ⇒ "没命中"与"被吞"在日志上分不出来,排查只能靠状态文件时间戳反推。 - ⚠️ 危害已被 2026-10-07 新增的「派活收工闸」**部分补偿**(待办标记写在冷却之前 ⇒ 不受冷却影响, 仍会在收工前拦一次);剩下的是"口头提醒缺失"与"日志盲点"。 二、处置(两处,都在 skill-load-guard.py) 1. 抽出共用的 `_is_dispatch()`;冷却旁路改为 `if _cooling(workdir, sid) and not _is_dispatch(prompt):` ⇒ **派活族显式触发句不受冷却**(其余词照旧受)。 2. 被跳过时写一行 `COOL_SKIP sid=… hits=…(冷却中,本轮不注入)` ⇒ 抑制态不再隐身。 三、判据与验证 - 新增 selftest 用例 t_skillguard_cooldown(3 项,**源码级** —— 跑真钩子会动到真工作区的冷却状态, ⛔ 不适合进回归套)。 - 另做了一次**行为级**验证(外部脚本,用真作用域内的工作区、测前备份冷却状态、测后还原): 5/5 通过 —— 非派活首次注入 ✓/非派活冷却中不注入 ✓/**派活族冷却中仍注入 ✓**/ 日志有 COOL_SKIP ✓/日志有 DISPATCH_PENDING ✓。 - ⚠️ 该验证脚本第一版**读日志读错**:`log_before` 是字节数却按字符切片 ⇒ 误判成"日志没写"。 改成按字节 seek 后全绿(同族坑:本项目已记过"字节 vs 字符")。 验收:selftest.py rc=0 PASS 103 / FAIL 0(改前 102);manifest 70 份、语法失败 0。 --- session-mechanism/references/manifest.md | 6 ++--- .../scripts/hooks/skill-load-guard.py | 22 ++++++++++++++++--- session-mechanism/scripts/selftest.py | 20 +++++++++++++++++ 3 files changed, 42 insertions(+), 6 deletions(-) diff --git a/session-mechanism/references/manifest.md b/session-mechanism/references/manifest.md index 4e27fa0..c50e71c 100644 --- a/session-mechanism/references/manifest.md +++ b/session-mechanism/references/manifest.md @@ -1,6 +1,6 @@ # manifest · 包内文件清单 -> 生成方式:逐文件 `compile()` / `json.loads` + md5 | **最近一次全量重算:2026-10-07 08:04 (排版核心块:明确「当前状态:」是小标题只写一次(禁止每条带前缀))** +> 生成方式:逐文件 `compile()` / `json.loads` + md5 | **最近一次全量重算:2026-10-07 08:15 (加载闸门:派活族不受冷却 + 冷却跳过留痕(含新判据))** > ⚠️ **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`(自引用,写完即失真)。 @@ -165,7 +165,7 @@ | `scripts/hooks/prompt-guards.py` | 9332 | `0aa1d1fdd2bf5a40cda2e60d7a785534` | ok | | `scripts/hooks/reply-style-guard.py` | 11040 | `7e271b342f91e2e98cbc03631b7e68f3` | ok | | `scripts/hooks/session-log-guard.py` | 24636 | `4dd13b24d541d1afc7b4874dba6bb1eb` | ok | -| `scripts/hooks/skill-load-guard.py` | 26499 | `f04b4ffdcdbd7f292bad83cbd77e0a46` | ok | +| `scripts/hooks/skill-load-guard.py` | 27515 | `d544387152d9140380484d046051c023` | ok | | `scripts/hooks/stop-dialog-guard.py` | 46083 | `fab654dfacfeb6253fc751707bebcac6` | ok | | `scripts/hooks/supervise-ensure-hook.py` | 14308 | `dc6f62584a8384baba0a1f2460828013` | ok | | `scripts/hooks/wb-result-hook.py` | 37199 | `2d4f694acd031222bf03b98a859b6e90` | ok | @@ -177,7 +177,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` | 487051 | `8ef1fb1b33bf088fdae3dcef158011c9` | ok | +| `scripts/selftest.py` | 488379 | `9f5d2f8d1888866c8a7a590e2471d85c` | ok | | `scripts/session-rules-check.py` | 44412 | `b2b468992d80421106316eaf451e2477` | ok | | `scripts/stop-collab.py` | 10462 | `d8043bedd23b52b6642aaf7ec8b8c980` | ok | | `scripts/supervise-launch.py` | 2503 | `f416a2f197e5524a8859c1d227ba1d05` | ok | diff --git a/session-mechanism/scripts/hooks/skill-load-guard.py b/session-mechanism/scripts/hooks/skill-load-guard.py index 1c8ebbb..58bb542 100644 --- a/session-mechanism/scripts/hooks/skill-load-guard.py +++ b/session-mechanism/scripts/hooks/skill-load-guard.py @@ -241,6 +241,15 @@ def _log(workdir, line): pass +def _is_dispatch(prompt): + """本轮是不是**派活族**触发句(比对前去掉空白)。抽出来给两处共用,⛔ 别各写一遍。""" + try: + seg = re.sub(r'\s+', '', prompt or '') + return any(w in seg for w in _DISPATCH_WORDS) + except Exception: + return False + + def _mark_dispatch(workdir, sid, prompt, hits): """命中「派活族」触发句 ⇒ 写一条待办(TSV:会话 id / 时间戳 / 命中词)。 @@ -251,8 +260,7 @@ def _mark_dispatch(workdir, sid, prompt, hits): ⛔ 全程 fail-open:任何异常只吞掉,绝不影响本钩子的正常注入。 """ try: - seg = re.sub(r'\s+', '', prompt or '') - if not any(w in seg for w in _DISPATCH_WORDS): + if not _is_dispatch(prompt): return with io.open(os.path.join(_norm_path(workdir), DISPATCH_REL), 'w', encoding='utf-8', newline='\n') as f: @@ -319,7 +327,15 @@ def main(): # 🔴 先留待办(⛔ 必须在冷却之前,见 `_mark_dispatch` 注释) _mark_dispatch(workdir, sid, prompt, hits) - if _cooling(workdir, sid): + # 🔴🔴 2026-10-07 两处改(用户问「有什么影响 该如何处理」时落的): + # ① **派活族显式触发句不受冷却** —— 那是最明确的一类"要动作",被 300 s 吞掉最亏。 + # 实测:会话 `b232218f` 里 01:32:00 命中后,01:33:46(105 s,最明确的那句) + # 被冷却吞掉、**连日志都没有**。 + # ② **被冷却跳过也要留一行**(`COOL_SKIP`)—— 否则"没命中"与"被吞"在日志上分不出来, + # 排查时只能靠状态文件时间戳旁证反推(上一次就是这么反推的)。 + if _cooling(workdir, sid) and not _is_dispatch(prompt): + _log(workdir, 'COOL_SKIP sid=%s hits=%s(冷却中,本轮不注入)' + % (sid[:8], ','.join(hits))) return seg = re.sub(r'\s+', ' ', prompt) diff --git a/session-mechanism/scripts/selftest.py b/session-mechanism/scripts/selftest.py index 2bdc2e4..9508757 100644 --- a/session-mechanism/scripts/selftest.py +++ b/session-mechanism/scripts/selftest.py @@ -7345,5 +7345,25 @@ def t_check_idle_min(): ] +@case("⛔ 加载闸门:派活族不受冷却 + 冷却跳过要留痕(⛔ 别让抑制态隐身)") +def t_skillguard_cooldown(): + """🔴 2026-10-07 立(用户问「有什么影响 该如何处理」时落的)。 + + 实测(会话 `b232218f`):01:32:00 命中后,**01:33:46 那条最明确的触发句** + (「每次我说使用执行会话完成目标…」)距上次仅 **105 s < COOLDOWN(300)** ⇒ 被吞掉, + 且旧实现**只在注入成功后才写日志** ⇒ 连一行痕迹都没有(排查时只能靠状态文件时间戳反推)。 + 两处处置:① 派活族显式触发句**不受冷却**;② 被冷却跳过**写 `COOL_SKIP`**。 + ⚠️ 本条是**源码级**判据(跑一次真钩子会动到真工作区的冷却状态,⛔ 不适合放进回归套)。 + """ + src = (HERE / "hooks" / "skill-load-guard.py").read_text(encoding="utf-8", errors="replace") + return [ + ("有共用的 `_is_dispatch()`(⛔ 别在两处各写一遍判定)", "def _is_dispatch(" in src), + ("冷却旁路在:`_cooling(...) and not _is_dispatch(...)`", + "_cooling(workdir, sid) and not _is_dispatch(prompt)" in src), + ("被跳过写 `COOL_SKIP`(⛔ 否则「没命中」与「被吞」分不出来)", + "COOL_SKIP" in src), + ] + + if __name__ == "__main__": sys.exit(main()) \ No newline at end of file