浏览器持久化(bh-chrome-keepalive 计划任务+幂等守护脚本)+记忆:技能重载/孤儿锁自动解除/环境三样齐
This commit is contained in:
1 parent
a98999c765
commit
0638609f15
3 files changed
+91
-1
No files matched your search
@@ -310,6 +310,25 @@
|
||||
⚠️ 宿主库 `cc07347c` 的 status = **`completed`**(不是 terminated)——**手动关被标成 completed**,所以更不会触发任何释放逻辑。
|
||||
⇒ **同一成因、第二次发生**(第一次是 `a257d30f` 在 00:53 被 daemon 换代终止)。两层缺口(锁不回收/判定不看死活)都还在。
|
||||
|
||||
## 07:38–07:41 · 技能重载 + 浏览器持久化 + 孤儿锁自动解除(用户令「1、重新加载会话技能 2、打开浏览器」)
|
||||
|
||||
**① 技能重载** ✅:`session-mechanism` 已有新提交 **`c76f5be`**(「接上『遇阻碍 ⇒ 置阻碍 ⇒ 机制暂停』+ 授权检查会话自解除孤儿锁」)。读到的新口径:
|
||||
- **R9 的唯一例外** —— 用户 2026-10-09 逐字授权:「如果 工作区 项目机制 对应的会话异常 导致未解锁的情况下, 可以授权 检查会话自解除孤儿锁」
|
||||
- 新工具 **`scripts/lock/orphan-lock.py`**:判据 = **查宿主库 `sessions.status`**(⛔ 不靠目测 `OWNER` 猜死活)—— `working` ⇒ 不动;`terminated`/`error`/`archived` ⇒ 可解;`completed` ⇒ 要过**时间闸**(`--idle-min`,默认 30 分钟);查不到 sid/库读不到/OWNER 没记 sid ⇒ **fail-closed 不动**。用法 `--dry-run` 先看。
|
||||
|
||||
**② 浏览器做成持久**(关键:上轮的临时起法被证明不持久):
|
||||
- ⚠️ **上轮 07:18 用后台任务起的实例(pid 8236)已随会话被回收** —— 07:37 复查 9223 已无监听。⇒ 「后台任务」不等于「持久」,只等于「活到本会话结束」。
|
||||
- ✅ 本轮改**计划任务**:`bh-chrome-keepalive` —— 动作 = `pythonw.exe "…/bh_chrome_keepalive.py"`(⛔ 按本机规矩绝不用 `python.exe`,会闪窗);每 5 分钟触发、`MultipleInstances=IgnoreNew`、`ExecutionTimeLimit=0`(长跑)、`RestartCount=999`。
|
||||
- ✅ 脚本从 `tmp/`(会被清)**移到工作区根** `bh_chrome_keepalive.py`,并加**幂等前置**(端口已在听 ⇒ `exit 0`,⛔ 不叠加第二个实例、不抢同一个 profile)。
|
||||
- ✅ 现取:任务 `State=Running`/`LastRun 07:38:25`/`NextRun 07:43:25`;`9223 LISTENING pid=1988`;CDP 返回 `Chrome/155.0.8059.39`。
|
||||
|
||||
**③ 孤儿锁已解**(工具判定,⛔ 非人工目测):
|
||||
- 目标那把(`cc07347c`,`completed`)首次判「静默 29 分钟 < 30 ⇒ **不动**」,等 95 秒后再判 ⇒ **解除**(静默 31 分钟)。
|
||||
- 顺带解除同型第二例:`ai1net-dsh-anywhere ← [协作]N9复测-2248`(挂了 10 天,静默 13 487 分钟)。
|
||||
- ✅ 现取 `--domain-check 执行会话` ⇒ **✅ 可派|域空闲**;流水记在 `.locks/.orphan-release.log`。
|
||||
|
||||
**④ 一句话现状**:环境三样全齐(浏览器可用 + 域锁空闲 + 技能口径最新),第④步「补闸门取证与上报」随时可派。
|
||||
|
||||
## 07:12 · 结果检查第 33 棒 —— 阻碍换人(旧孤儿锁已解、新持锁会话在保护期),本轮零派活
|
||||
|
||||
- 本棒(自动化 `cdf24e8d`,第 33 棒)现取五样:`state.py` 仍不存在、`taskgraph.json` 仍不存在、台账 16 条(15 done + 1 blocked)、`lifecycle`=进行中、`acceptance` 3 过 + 1 不过。
|
||||
|
||||
@@ -57,6 +57,7 @@
|
||||
|
||||
- 🔴 **开关 `off`,但常驻仍在跑**(开关只挡「自动续命」,⛔ 别把 off 读成「没在跑」)。2026-10-08 23:4x 实测:常驻 ✅ 活 pid 40016/心跳 2 秒前 | 看板在听 20099 | 保活任务 `collabd-keepalive-contentm_agent` = Ready。要真起/真停 ⇒ `collabctl.py on|off --ws E:/ProgramData/AIProject/contentm_agent`。
|
||||
- 🔴 机制登记**已全改现名** `contentm_agent`:`collabd.config.json` + `collabctl.py` 的 `WORKSPACES`/`KEEPALIVE_TASKS`(**源 + 3 个区副本共 4 份**)+ ai1net 的 `peer_workspaces`(改完**看板要重启**)。旧空壳目录 `content_marketing_agent.RETIRED-20261008` 保留(零删除)。⚠️ `collabctl.py off --ws` 曾越区(`kill_all()` 全机扫描杀到他区+看板),已修;⛔ 改这文件记住源+3 副本。
|
||||
- **域锁**:域目录=**工作区下的第一层目录**;⛔ 别在域目录外套公共父目录(所有子目录会算成同一个域键 ⇒ 域锁失效)。抢锁 `handoff-guard.sh --claim-exec "<会话名>" --domains "<域目录>"`(包在 `skills/session-mechanism/scripts/lock/`);抢不到 ⇒ **停手报告**。释放 `--release-exec "<会话名>"`(或 `ME=<名>`;⛔ 取不到名 fail-closed,不静默清空)。🔴 **孤儿锁怎么来的(2026-10-09 两次实查)**:锁是**磁盘持久物**(`.locks/<域目录转义>/OWNER`),而会话**无论怎么结束都不释放**它 —— ① 被宿主终止(第一次:`a257d30f` 遇 daemon 冷启动换代,`status=terminated`);② **被用户手动关**(第二次:`cc07347c` 卡死后用户关窗,`status=completed`,⚠️ 手动关是标 `completed` 不是 `terminated`)⇒ 都不会跑 `--release-exec`。而域冲突判定**只比会话名、不判死活**,R9 又禁 AI 删锁 ⇒ 只能用户处置。**第二把已实证在 06:33 建、07:10 成孤儿**。**同型第二例**:`ai1net-dsh-anywhere ← [协作]N9复测-2248`(09-29 建)已挂 10 天。
|
||||
- **域锁**:域目录=**工作区下的第一层目录**;⛔ 别在域目录外套公共父目录(所有子目录会算成同一个域键 ⇒ 域锁失效)。抢锁 `handoff-guard.sh --claim-exec "<会话名>" --domains "<域目录>"`(包在 `skills/session-mechanism/scripts/lock/`);抢不到 ⇒ **停手报告**。释放 `--release-exec "<会话名>"`(或 `ME=<名>`;⛔ 取不到名 fail-closed,不静默清空)。🔴 **孤儿锁怎么来的(2026-10-09 两次实查)**:锁是**磁盘持久物**(`.locks/<域目录转义>/OWNER`),而会话**无论怎么结束都不释放**它 —— ① 被宿主终止(第一次:`a257d30f` 遇 daemon 冷启动换代,`status=terminated`);② **被用户手动关**(第二次:`cc07347c` 卡死后用户关窗,`status=completed`,⚠️ 手动关是标 `completed` 不是 `terminated`)⇒ 都不会跑 `--release-exec`。而域冲突判定**只比会话名、不判死活**,R9 又禁 AI 删锁 ⇒ 只能用户处置。**第二把已实证在 06:33 建、07:10 成孤儿**。✅ **2026-10-09 已有自动解除工具**:`session-mechanism/scripts/lock/orphan-lock.py`(用户 2026-10-09 逐字授权「可以授权 检查会话自解除孤儿锁」= **R9 的唯一例外**)—— 判据是**查宿主库 `sessions.status`**(⛔ 不靠目测 `OWNER`):`working` 不动|`terminated`/`error`/`archived` 可解|`completed` 要过时间闸(`--idle-min` 默认 30 分钟)|查不到 sid ⇒ fail-closed 不动;先 `--dry-run`。本区实测已解开两把(含挂了 10 天的 `ai1net-dsh-anywhere ← [协作]N9复测-2248`)。
|
||||
- 🔴 **本机浏览器实例要用「计划任务」保活,⛔ 别用后台任务**(2026-10-09 实测):「后台任务」=只活到发起它的那个会话结束(07:18 起的 pid 8236,07:37 已被回收)。正解=计划任务 **`bh-chrome-keepalive`**:动作 `pythonw.exe "E:/ProgramData/AIProject/contentm_agent/bh_chrome_keepalive.py"`(幂等:端口在听就退出),每 5 分钟触发 + `MultipleInstances=IgnoreNew` + `ExecutionTimeLimit=0`;脚本**放工作区根**(⛔ `tmp/` 会被清)。端口 9223,验证=`netstat` 见 LISTENING + `curl --noproxy '*' http://127.0.0.1:9223/json/version` 回 JSON。**同型第二例**:`ai1net-dsh-anywhere ← [协作]N9复测-2248`(09-29 建)已挂 10 天。
|
||||
- ⚠️ **本目标(`…-071c5b`)产物落点分岔**:目标目录合同判「不在目标目录内」,而域门禁要求「只许写域目录」。①棒取域目录 `产品规划/`(收到软提醒,非拒收);②③棒改取**域目录 `执行会话`**(第一层目录 ⇒ 能盖住目标目录)+ 产物落目标目录 —— 两种都记在案,口径待用户裁定。
|
||||
- ⚠️ `--report <sid> --state done --artifact "<路径>"`:产物**读不到**⇒硬拒收;**落点不在目标目录**只给软提醒(不拒收)。**sid 取法**:查宿主库 `E:/ProgramData/.workbuddy/workbuddy.db` 的 `sessions` 表(按 `cwd` + `created_at desc`,本区会话 `is_background_automation=1`)。
|
||||
@@ -0,0 +1,70 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""常驻:确保本机有一个可用的独立 Chrome 实例(9223,固定 profile ⇒ 登录态保留)。
|
||||
|
||||
为什么必须常驻:本机沙箱在父进程退出时连带杀子进程 ⇒ 会话/工具调用里起的浏览器一返回就死。
|
||||
为什么放工作区根而不是 tmp/:tmp/ 会被清理,脚本一没计划任务就静默失效。
|
||||
|
||||
形态:幂等 —— 端口已在听 ⇒ 立刻退出(⛔ 不叠加第二个实例、不抢同一个 profile)。
|
||||
由计划任务 `bh-chrome-keepalive` 每 5 分钟触发一次(MultipleInstances=IgnoreNew),
|
||||
所以"活着的那份"负责守端口,任务只是兜它挂掉时重新拉起。
|
||||
⛔ 不附着用户日常 Chrome(那是 9222,会弹「允许远程调试?」动到用户浏览器)。
|
||||
"""
|
||||
import os, subprocess, sys, time, socket, io
|
||||
|
||||
for k in ["HTTP_PROXY", "HTTPS_PROXY", "http_proxy", "https_proxy"]:
|
||||
os.environ.pop(k, None)
|
||||
os.environ["NO_PROXY"] = "127.0.0.1,localhost"
|
||||
|
||||
WS = r"E:/ProgramData/AIProject/contentm_agent"
|
||||
CHROME = r"C:\Program Files\Google\Chrome\Application\chrome.exe"
|
||||
PROFILE = r"E:/ProgramData/.workbuddy/tmp/bu-chatgpt-profile"
|
||||
PORT = 9223
|
||||
LOG = os.path.join(WS, "tmp", "_bh_chrome.log")
|
||||
|
||||
|
||||
def log(msg):
|
||||
try:
|
||||
os.makedirs(os.path.dirname(LOG), exist_ok=True)
|
||||
with io.open(LOG, "a", encoding="utf-8") as f:
|
||||
f.write("[%s] %s\n" % (time.strftime("%m-%d %H:%M:%S"), msg))
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def up():
|
||||
try:
|
||||
s = socket.create_connection(("127.0.0.1", PORT), 1)
|
||||
s.close()
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
def launch():
|
||||
return subprocess.Popen(
|
||||
[CHROME, "--remote-debugging-port=%d" % PORT,
|
||||
"--user-data-dir=%s" % PROFILE,
|
||||
"--no-first-run", "--no-default-browser-check",
|
||||
"about:blank"],
|
||||
creationflags=0x00000008 | 0x08000000 | 0x00000200, # DETACHED | NO_WINDOW | NEW_PROCESS_GROUP
|
||||
)
|
||||
|
||||
|
||||
# ---- 幂等前置:端口已在听 ⇒ 什么都不做 ----
|
||||
if up():
|
||||
log("port %d already listening ⇒ exit (idempotent)" % PORT)
|
||||
sys.exit(0)
|
||||
|
||||
p = launch()
|
||||
log("launched pid=%s" % p.pid)
|
||||
|
||||
while True:
|
||||
time.sleep(5)
|
||||
if p.poll() is not None:
|
||||
log("chrome exited rc=%s ⇒ relaunch" % p.returncode)
|
||||
p = launch()
|
||||
log("relaunched pid=%s" % p.pid)
|
||||
if not up():
|
||||
# ⚠️ 这里只记日志:僵死态(端口不应答、进程不死)按规则由人/会话判定后重启实例,
|
||||
# ⛔ 脚本不做自动重启,免得把"正在被人排查的现场"清掉。
|
||||
log("port %d not answering (pid alive=%s)" % (PORT, p.poll() is None))
|
||||
Reference in new issue
Block a user