chore(技能): 浏览器起法收敛为「会话后台任务」+ 关窗即停手;补索引与自测清理

- browser-harness/SKILL.md:§1 起法整段重写为「只允许这一种 —— 本会话的后台任务」(字面命令/
  为什么只许这一种/守卫语义/已知代价/起不来对照表);§3 补一条排障 —— 认进程命令行与它自己的日志,
  ⛔ 别信 pid 父链;**「删掉脚本」≠「停掉进程」**(依据 2026-10-10 实测:孤儿守卫在脚本已删后仍把浏览器拉回)
- browser-harness/agent-workspace/bh_chrome_keepalive.py:守卫语义分两档 —— Chrome 正常关窗(rc=0) ⇒ 停手退出;
  异常退出(rc≠0) ⇒ 重拉(依据:用户手关被无条件拉回,连关三次弹三次)
- session-mechanism/scripts/selftest.py:删掉重复定义的 `t_check_cooldown_no_pileup`(两段逐字相同);自测 PASS 109 / FAIL 0
- session-mechanism/references/humanizer-en/SKILL.md:与顶层 `humanizer` 技能同名冲突 ⇒ 去掉本包这份的注册
  (`user-invocable: false` + `disable-model-invocation: true`),6 个文件正文一字未动
- session-mechanism/references/01-文档索引.md:补登 `04-决策方法论.md`/`dsh-decision-method/`/`作业规矩/`/`humanizer-en/`
This commit is contained in:
admin committed 2026-10-10 23:25:36 +08:00
1 parent a04b2c08e8
commit fd9c902bcc
5 files changed
+112 -65

No files matched your search

+79 -24
View File
@@ -22,7 +22,7 @@ Direct browser control via CDP. For task-specific edits, use `agent-workspace/ag
本机沙箱**父进程一退就连带杀子进程** ⇒ 同步调用(含 PowerShell `Start-Process`)里起的 Chrome **会立刻死**,
症状是 `netstat` 查不到 9223、`curl` 报 `WinError 10061`(2026-10-09 实测踩到一次)。
做法:**先用常驻守端口把浏览器开着,之后每次操作只连不起**。
✅ **可直接抄的三步(含启动器在哪、怎么起、计划任务怎么兜)见下面「调 AI 工具」§1** —— 那里给的是命令,不是叮嘱。
✅ **起法只有一条:用本会话的后台任务跑那条命令**(启动器在哪、怎么起、起不来怎么查,都在下面「调 AI 工具」§1)—— 那里给的是能整段粘的命令,不是叮嘱。
- 🔴🔴 **一类任务只开一个标签页并复用,⛔ 禁止反复开**(2026-10-09 用户令)。
反复开页会把共享实例堆到**整机卡死**(端口不应答、进程不死,保活脚本也救不回 —— 见下面那条的实测读数)。
细则**只写在下面那颗同名的 🔴🔴 子弹里**,⛔ 本处不复述 —— 但它**优先于任何"顺手再开一个"的念头**。
@@ -62,39 +62,94 @@ Direct browser control via CDP. For task-specific edits, use `agent-workspace/ag
> 2026-10-07 用户定案。适用于「打开 ChatGPT / Claude 网页版求建议、核对方案、做结构评审」这类活。
### 1. 🔴 起法:浏览器由**一个守端口的常驻循环**开着(三步照抄)
### 1. 🔴 起法:只允许这一种 —— **本会话的后台任务**(字面命令,照抄)
**要什么**:浏览器活在一个**永不退出的循环**里 —— 循环起完 Chrome 就守在端口上;循环不结束,Chrome 就不会被连坐杀掉。
(避坑依据 2026-10-09 实测对照:同步调用里起 Chrome ⇒ 端口 1 秒内就绪、**调用一返回立刻死**,随后 `curl` 报 `WinError 10061`;
丢进「起完就守着端口、且一直不退出」的循环 ⇒ 浏览器全程常驻。)
**目标**:本会话要用浏览器时有一个 9223 上的独立 Chrome;会话结束,浏览器随之下线。
- 🔴 **可直接抄的三步**(Windows 实测,2026-10-10):
**第 0 步 · 先探测** —— 先跑这两条:
① 把技能包里那份启动器复制到**工作区根**(⛔ 别放 `tmp/`:`tmp/` 会被清理,脚本一没、计划任务就静默失效):
`cp "<技能包>/agent-workspace/bh_chrome_keepalive.py" "<工作区根>/bh_chrome_keepalive.py"`
```bash
netstat -ano | grep -E "127\.0\.0\.1:9223 .*LISTENING"
curl --noproxy '*' -s http://127.0.0.1:9223/json/version
```
② 用 **`pythonw.exe`** 起它(⛔ 别用 `python.exe`:控制台程序会闪黑窗,2026-10-01 实测用户当场投诉):
`"<py>/pythonw.exe" "<工作区根>/bh_chrome_keepalive.py" --log "<工作区根>/tmp/_bh_chrome.log"`
有 pid + 回 JSON ⇒ **已经在跑,直接用**(怎么连见第 2 节),本节到此为止。
(避坑依据 2026-10-08 用户当场点破:「为什么要重新开浏览器,先检查之前是否开过浏览能否复用」。)
③ 让**计划任务**兜住它(进程挂了自动拉起)—— 动作同样用 `pythonw.exe`,每 5 分钟一次、
`MultipleInstances=IgnoreNew`。⚠️ `schtasks.exe` 在本机黑名单里(走 PowerShell 的 `ScheduledTasks` 模块)。
**第 1 步 · 没在跑 ⇒ 用本会话的后台任务起**
- ✅ **校验(两条一起看)**:`netstat -ano | grep -E "127\.0\.0\.1:9223 .*LISTENING"` 有 pid;
`curl --noproxy '*' -s http://127.0.0.1:9223/json/version` 回 JSON。
- **起来之后,每次操作只连不起**(用第 2 节那条 env 连上去)—— 多个实例会抢同一个 profile。
两份绝对路径固定,启动器就用**技能包内那一份**(参数化,换工作区不用改代码):
- 🔴 **僵死态要会认**(避坑依据 2026-10-09 实测):共享实例被堆爆后会进入「**端口不应答、进程还活着**」的僵死态
—— 连接探不通,但 `tasklist` 里那个 chrome 进程仍在。
**判据(两条一起看)**:① `netstat -ano | grep 127.0.0.1:9223` **无 LISTENING**;② `tasklist | grep -i chrome` **仍有那个 pid**。
⇒ 处置:**重启实例**(关掉残留 chrome,再按本节三步重起);在原地反复重试连接只会白等。
两条要记牢:**「端口在」=还要看进程**;**「进程在」=还要看端口** —— 两条都过才叫可用。
- ⚠️ **守端口脚本对僵死态只记日志、不自动重启**(正向目标:**保住现场**,交给会话按上面的判据处置)——
依据:自动重启会把正在被人排查的现场清掉。所以它只会连续写
`port 9223 not answering (pid alive=True)`(2026-10-09 `06:56:11` → `07:06:49` 进程才退出)。
- 启动器:`E:\ProgramData\.workbuddy\skills\browser-harness\agent-workspace\bh_chrome_keepalive.py`
- 解释器:`E:\ProgramData\.workbuddy\binaries\python\versions\3.13.12\pythonw.exe`
命令(整段抄,然后**交给你这个会话的后台任务去跑**):
```bash
E:/ProgramData/.workbuddy/binaries/python/versions/3.13.12/pythonw.exe \
"E:/ProgramData/.workbuddy/skills/browser-harness/agent-workspace/bh_chrome_keepalive.py" \
--log "E:/ProgramData/.workbuddy/tmp/_bh_chrome.log" \
>> "E:/ProgramData/.workbuddy/tmp/_bh_bg.out" 2>&1
```
🔴 **"交给你这个会话的后台任务跑"是这条起法的本体**:后台任务只活到本会话结束
⇒ 会话在、浏览器在;会话收工、浏览器随之下线。这正是要的语义。
两处不能省:
- **`>> …_bh_bg.out 2>&1`(stdout 全重定向到文件)** —— 依据 2026-10-01 实测:常驻任务往会话里吐输出会把会话日志推过 ~10 MiB ⇒ 宿主 `diagnostic-log:dropped` ⇒ 界面不刷新、看着像卡死。
- **走后端任务,⛔ 不在同步调用里跑** —— 本机沙箱父进程一退就连带杀子进程;同步调用里起的 Chrome 端口 1 秒内就绪、调用一返回立刻死,随后 `curl` 报 `WinError 10061`(2026-10-09 实测)。
**为什么只许这一种**(用户 2026-10-10 令:「只允许这一种方式,使用 browser harness 打开浏览器的时候也必须用这种方式打开浏览器」)
- **同步调用里起** ⇒ 父进程一退 Chrome 就死(见上一段的实测)。
- **Windows 计划任务** ⇒ 它跨会话常驻,用户手关的浏览器会被守卫拉回来(2026-10-10 实测:连关三次、每次都在 1 秒内弹回),语义与"会话结束即回收"相反。⇒ 本机 2026-10-10 已把 `bh-chrome-keepalive` 计划任务**整个撤掉**,起法只留本会话后台任务这一条。
**守卫语义**(2026-10-10 改)
- Chrome **正常关窗**(`rc=0`,=用户自己点的关闭)⇒ 守卫**停手退出**,关掉就是关掉。
- Chrome **异常退出**(`rc≠0`,崩溃/被杀)⇒ 立刻重拉,干活不中断。
**已知代价(必须知道)**
- 本会话只要有 `pending`/`running` 的后台任务,宿主 idle 钩子就被**静默压制**(2026-10-05 实测:被僵尸任务压 6h20m、零日志)⇒ 这段期间本会话的「钩子驱动的唤醒/监管」不生效。所以**只在确实要用浏览器时起它,用完把后台任务停掉**。
**第 2 步 · 校验** —— 两条一起看:
```bash
netstat -ano | grep -E "127\.0\.0\.1:9223 .*LISTENING" # 有 pid
curl --noproxy '*' -s http://127.0.0.1:9223/json/version # 回 JSON
```
两条都过才叫可用。之后每次操作**只连不起**(用第 2 节那条 env 连上去)。
**第 3 步 · 起不来时按这张对照表查**
- **后台任务立刻结束、端口也没有** ⇒ 读 `E:/ProgramData/.workbuddy/tmp/_bh_bg.out` 与 `E:/ProgramData/.workbuddy/tmp/_bh_chrome.log` 的最后几行,理由都写在那里。
- **日志写着 `chrome 可执行体不存在`** ⇒ Chrome 不在默认路径。给启动器加 `--chrome "<你的 chrome.exe>"`,或设环境变量 `BH_CHROME`。
- **日志写着 `port 9223 already listening ⇒ exit`** ⇒ 已经有实例在跑,直接连。
- **日志写着 `chrome closed cleanly (rc=0) ⇒ stop guarding, exit`** ⇒ 浏览器被人手关过、守卫已按新语义停手。要再用就跑第 1 步那条命令。
- **端口被拉起来、却没人承认是自己起的** ⇒ 走**进程级证据**:先看 cmdline 找出是哪个脚本在守
(`Get-CimInstance Win32_Process -Filter "Name='pythonw.exe'"` 打 `CommandLine`),再看**它自己写的那份日志** ——
时间戳能直接和"关窗"动作对上(2026-10-10 实测:`[23:01:36] chrome exited rc=0 ⇒ relaunch` 连出两条,
正是用户两次关窗被弹回)。⚠️ **删掉脚本文件 ≠ 停掉进程** —— Python 启动时已把脚本读进内存,
文件删了它照样守;要 `Stop-Process` 按 pid 结束。⚠️ 也⛔ 别拿 pid 父链当结论(pid 会复用)。
- **后台任务还在跑、端口却没了** ⇒ 走下面「僵死态」那两条判据。
**僵死态:端口不应答、进程却还活着**(避坑依据 2026-10-09 实测)
共享实例被堆爆后会进入这个状态 —— 连接探不通,但 `tasklist` 里那个 chrome 进程仍在。
**判据(两条一起看)**:① `netstat -ano | grep 127.0.0.1:9223` **无 LISTENING**;② `tasklist | grep -i chrome` **仍有那个 pid**。
⇒ 处置:**重启实例**(关掉残留 chrome,再按第 1 步重起);在原地反复重试连接只会白等。
两条要记牢:**「端口在」=还要看进程**;**「进程在」=还要看端口** —— 两条都过才叫可用。
⚠️ **守端口脚本对僵死态只记日志、不自动重启**(正向目标:**保住现场**,交给会话按上面的判据处置)——
依据:自动重启会把正在被人排查的现场清掉。所以它只会连续写
`port 9223 not answering (pid alive=True)`(2026-10-09 `06:56:11` → `07:06:49` 进程才退出)。
### 2. 起独立实例(端口 9223)—— 下面这条是**启动器脚本内部**那一步
> 🔴 **要它常驻,用第 1 节的三步**(复制启动器 → `pythonw.exe` 起 → 计划任务兜)。
> 🔴 **要它常驻,用第 1 节那条命令(本会话的后台任务)**。
> 本节这条命令的用途是**看懂它在干什么**与**排障**:照抄它**同步**跑 ⇒ 父进程一退 Chrome 立刻死
> (避坑依据 2026-10-09:端口 1 秒内就绪、调用一返回就死,随后 `curl` 报 `WinError 10061`)。
@@ -7,24 +7,35 @@
(症状:`netstat` 查不到端口、`curl` 报 `WinError 10061`)。
要的是:浏览器活在一个**永不退出的循环**里 —— 起完 Chrome 就守在端口上。
## 怎么用(三步,Windows 实测 2026-10-10)
## 怎么用(Windows 实测 2026-10-10)
1、复制到**工作区根**(⛔ 别放 `tmp/`:`tmp/` 会被清理,脚本一没、计划任务就静默失效)
2、用 **`pythonw.exe`** 起(⛔ 别用 `python.exe`:控制台程序会闪黑窗)
3、让**计划任务**兜住它(进程挂了自动拉起)
🔴 **只允许一种起法**(用户 2026-10-10 令:「只允许这一种方式,使用 browser harness 打开浏览器的时候
也必须用这种方式打开浏览器」):**在本会话的后台任务里跑本文件**,stdout 全重定向到文件。
1、命令**整段抄**(见 `SKILL.md`「调 AI 工具」§1),解释器用 **`pythonw.exe`**
2、**交给你这个会话的后台任务去跑**(后台任务只活到本会话结束 ⇒ 会话在、浏览器在;会话收工、浏览器下线)
3、⛔ 不往工作区复制副本、⛔ 不建 Windows 计划任务(计划任务跨会话常驻,会把用户手关的浏览器拉回来)
⛔ 也不在同步调用里跑(父进程一退 Chrome 就死)
**为什么必须有这一步**:后台任务的 stdout 会灌进会话日志 ⇒ 超 ~10 MiB 后宿主丢日志、界面看着卡死
⇒ 所以命令里那截 `>> …_bh_bg.out 2>&1` 不能省。
## 做法约定
- **幂等**:端口已在听 ⇒ 立刻退出。(避免叠加第二个实例、避免两个实例抢同一个 profile)
- **守**:`while True` 里看着子进程;它退出了就重新起。
- **守**:`while True` 里看着子进程;它**异常**退出(rc≠0)就重新起。
- 🔴 **手关窗 ⇒ 停手**:Chrome 正常退出(rc=0,=用户点的关闭窗口)时脚本**自己退出**,不再拉起。
依据(实测 2026-10-10):用户关掉浏览器,守卫在 1 秒内把它拉回,连关三次弹三次。
要重新开,跑一条命令起本脚本即可(见 `SKILL.md`「调 AI 工具」§1)。
- **僵死态只记日志**:端口不应答而进程仍在时,脚本**不自动重启** —— 那会把正在被排查的现场清掉,
留给人/会话按 SKILL.md 的判据处置。
- **清代理 env**:`HTTP_PROXY` / `HTTPS_PROXY` 会劫持本地 CDP 连接(报 `502 Bad Gateway`)。
- **只用独立实例**:⛔ 不附着用户日常 Chrome(那是 9222,会弹「允许远程调试?」动到用户浏览器)。
用法:
pythonw bh_chrome_keepalive.py --log "<工作区根>/tmp/_bh_chrome.log"
可选:`--port` `--chrome` `--profile`(都有默认值,见下)
pythonw bh_chrome_keepalive.py --log "E:/ProgramData/.workbuddy/tmp/_bh_chrome.log"
(整条命令见 SKILL.md「调 AI 工具」§1;本脚本要放在**会话的后台任务**里跑)
可选:`--port` `--chrome` `--profile` `--proxy` `--log`(都有默认值,见下)
"""
from __future__ import annotations
@@ -105,9 +116,16 @@ def main() -> int:
_log(a.log, "launched pid=%s" % p.pid)
# ── 守:循环不结束 ⇒ Chrome 不被连坐 ───────────────────────────
# 🔴 退出码分两档(2026-10-10 用户报障「关掉就自动打开」后改):
# rc=0 = 正常关窗(用户自己点的关闭)⇒ **停手退出**,把浏览器还给用户;
# rc≠0 = 异常退出(崩溃/被杀)⇒ 重拉。
# 依据(实测):`[22:45:06] chrome exited rc=0 ⇒ relaunch` —— 用户手关三次、被拉回三次。
while True:
time.sleep(5)
if p.poll() is not None:
if p.returncode == 0:
_log(a.log, "chrome closed cleanly (rc=0) ⇒ stop guarding, exit")
return 0
_log(a.log, "chrome exited rc=%s ⇒ relaunch" % p.returncode)
p = _launch(a.chrome, a.profile, a.port, a.proxy)
_log(a.log, "relaunched pid=%s" % p.pid)
@@ -32,6 +32,12 @@
| 只想要速查 | `99-速查清单.md` | — |
| **问「这是什么、为什么这样」** | `architecture.md` | — |
| **要把一个话题讲清楚(文字 → 图 → 互动页 三级递进)** | `karpathy-output-ladder/SKILL.md`(**随包子技能**) | — |
| **判断「要不要做/怎么做/能不能先凑合」** | `04-决策方法论.md`(判据正文)+ `dsh-decision-method/`(U/A/X 三份素材库) | — |
| **写语气/去 AI 味** | `作业规矩/04-去AI味与说话方式.md`(**会话场景加固版**,含交付前清单与质量评分)+ `humanizer-en/SKILL.md`(**英文硬核版**:55 模式/5 语气档/0–100 打分) | — |
| **作业总规矩/工作区纪律/多棒接力** | `作业规矩/00-作业总规矩(原 agent-operating-rules).md` + `作业规矩/02-工作区纪律.md` + `作业规矩/03-多棒接力编排.md` | — |
> 📌 **上表三行 2026-10-10 补齐**:`04-决策方法论.md`、`dsh-decision-method/`、`作业规矩/`、`humanizer-en/` 原先只被正文引用、没进本表
> ⇒ 换会话时会被当成孤儿(本包 §二 规则 1 要求「新增文档必须在这张表里登记」)。
> 📌 **`karpathy-output-ladder/` 是随包子技能**(2026-10-07 按用户令从全局 skills 移入本包 `references/`,内容逐字未改)。
> ⚠️ **它的"用法"尚未接线**(用户原话「**后续再看如何使用**」)⇒ 现在只做到"**找得到**":
@@ -1,7 +1,8 @@
---
name: humanizer
description: Detects 55 AI writing patterns and rewrites text in five voice profiles so it reads like a specific human wrote it, with an optional 0-100 AI-tell score. Use when text sounds AI-generated or like a chatbot, when preparing a blog post, README, or LinkedIn post for publication, when auditing prose for AI tells, or when editing a Markdown file in place. Triggers on phrases like "humanize this", "make this sound less AI", "make this sound human", "remove AI tells", "does this read like ChatGPT", and "rewrite so it does not sound AI-generated". Pure Markdown, zero dependencies, no network calls.
user-invocable: true
user-invocable: false
disable-model-invocation: true
argument-hint: '"your text" [--mode detect|rewrite|edit] [--voice casual|professional|technical|warm|blunt] [--file path/to/file.md] [--aggressive] [--iterate N] [--score] [--purpose essay|email|marketing|technical|general] [--openings N] [--ignore-code] [--ignore-quotes]'
allowed-tools:
- Read
-33
View File
@@ -901,39 +901,6 @@ def t_check_cooldown_no_pileup():
("闸⑥ 排在闸⑤ 之后(先精确后兜底)", ok_order)]
@case("🔴🔴 闸⑥ 同类检查冷却期:两种情况 × 每类最多一条 ⇒ 不堆积(用户「不该有任何堆积」)")
def t_check_cooldown_no_pileup():
"""🔴 2026-10-05 立(用户口径逐字:「只有两种情况会创建 定时任务创建会话,不应该又任何堆积」)。
⛔ **病根**(实测,见 `references/pitfalls.md` P0-69):排期名带递增棒次(`第%d棒`)
⇒ 闸⑤「同名去抖」**永不命中** ⇒ 同一种情况可被反复建 ⇒ 自测区 2 小时堆 11 条。
🔴 **判据必须能「改前报红」**(P0-13 同族):
· 删掉闸⑥ 那段 ⇒ ②③④ 全红;
· 把 `_stamp.update(...)` 改回整体覆盖 ⇒ ③ 红(另一类的冷却记忆被抹);
· 把 `CHECK_COOLDOWN_S` 改成 0 ⇒ ① 红。
"""
import inspect as _ins
m = imp()
src = _ins.getsource(m.maybe_spawn_check_agent)
cd = getattr(m, "CHECK_COOLDOWN_S", None)
# ① 冷却常量在位且是「真窗口」(⛔ 0 / None 等于没冷却)
ok_const = isinstance(cd, (int, float)) and cd >= 600
# ② 闸⑥ 在:按 reason 记冷却键(⛔ 只认带引号的字符串拼装,别认注释里的裸串)
ok_key = ('"last_" + reason' in src) or ("'last_' + reason" in src)
# ③ 合并写:⛔ 不许整体覆盖 stamp(覆盖 ⇒ 另一类的 last_* 被抹 ⇒ 闸⑥ 对另一情况失效)
ok_merge = "_stamp.update(" in src
# ④ 顺序:闸⑥ 必须在闸⑤(同名去抖)**之后**(⛔ 顺序颠倒 ⇒ 语义变成"先拦粗的")
p5 = src.find("active_schedule_exists(name)")
p6 = src.find('"last_" + reason')
if p6 < 0:
p6 = src.find("'last_' + reason")
ok_order = p5 >= 0 and p6 >= 0 and p5 < p6
return [("冷却窗口 CHECK_COOLDOWN_S 在位且 ≥10 分钟(实测=%s)" % cd, ok_const),
("闸⑥ 按 reason 记冷却键(同名去抖失效时的兜底)", ok_key),
("stamp 合并写(⛔ 整体覆盖会抹掉另一类的冷却记忆)", ok_merge),
("闸⑥ 排在闸⑤ 之后(先精确后兜底)", ok_order)]
@case("检查会话的 prompt:**引用的命令必须真存在**(⛔ 引用不存在的命令 ⇒ 静默当成功)")
def t_check_prompt_cmds_exist():
"""🔴🔴 2026-10-03 实测坐实:`CHECK_PROMPT` 让检查会话用