技能包:配置与代码分家 —— 每包自建 env/ 放本机专属配置,scripts/ 不再写死本机路径
用户 2026-10-11 原话: · 19:22「这个环境都是针对一台机器的,同步到别的机器把环境都搞坏了」 · 19:28「不用挪出 用env文件夹区分就行,重点是 脚本是脚本 配置是配置」 · 19:29「每个技能 需要环境配置的 都有自己的 env文件夹」 · 19:30「修改完成后要仔细检查 避免调整后 各技能脚本运行失败」 改动: 1、session-mechanism 新增 env/(roots.env=本机真身不入库 + roots.env.example=样例入库), install.py 的 ROOTS_ENV 改指 env/roots.env;全包脚本/hook 的配置定位统一为 「向上 4 层 × env/roots.env 优先 → 包根 roots.env 兼容 → 技能库那份兜底」。 2、去硬编码:collabctl / keepalive / supervise-launch / board-launch 里的本机路径与本机工作区 清单全清,改为 DSH_AIROOT(兜底 dirname(DSH_WS_ROOT))+ 扫 <AIROOT>/*/.workbuddy/collab/ 自动发现;保活任务名按 collabd-keepalive-<工作区名> 现推;启动器默认值改 ~/.workbuddy 形态。 3、browser-harness:bh_chrome_keepalive.py 默认值全部改 env 驱动,新增 env/bh.env.example。 4、.gitignore:env/ 下只放行 *.example(三行三种层级)+ 兜住旧位置的包根 roots.env。 5、改包纪律.md 新增「四、包结构:脚本是脚本,配置是配置」,立为全仓通用规矩。 6、文档同步:SKILL.md / deploy.md(新增 §3b 配置层级、§6 换机器必查 6 件事)/ rules.md / pitfalls.md / supervise-persistence.md。 验收(本机):py_compile 34 个 py 全过;selftest PASS 109 / FAIL 0; 逐文件实证 8 个 loader 命中 env/roots.env;preflight-lock.sh 同目录实测解出两个根; install.py --manifest 77 份、语法失败 0。 异机验证(包拷成「新克隆」(只有 *.example)+ 另造假工作区 ws-alpha/ws-beta): install.py --apply 自生成该机 env/roots.env;collabctl/keepalive 自动发现的确实是 ws-alpha/ws-beta、任务名现推(写死清单确已消除);部署副本本地无 roots.env 时靠 CODEBUDDY_CONFIG_DIR 兜底读到技能库那份;整套 selftest 有宿主库时 PASS 108 / FAIL 1 (唯一 FAIL=该区没有常驻心跳,环境所致;本机常驻在跑时同一套为 PASS 109 / FAIL 0)。
This commit is contained in:
1 parent
90cc4315d5
commit
d81f75f173
37 files changed
+763
-291
No files matched your search
+30
-21
@@ -23,7 +23,7 @@ Direct browser control via CDP. For task-specific edits, use `agent-workspace/ag
|
||||
症状是 `netstat` 查不到 9223、`curl` 报 `WinError 10061`(2026-10-09 实测踩到一次)。
|
||||
做法:**先用常驻守端口把浏览器开着,之后每次操作只连不起**。
|
||||
✅ **起法只有一条:用本会话的后台任务跑那条命令**(启动器在哪、怎么起、起不来怎么查,都在下面「调 AI 工具」§1)—— 那里给的是能整段粘的命令,不是叮嘱。
|
||||
- 🔴🔴 **一类任务只开一个标签页并复用,⛔ 禁止反复开**(2026-10-09 用户令)。
|
||||
- 🔴🔴 **标签页按「页面类型」分:一类页面一个标签、同类只开一个、异类各开一个**(2026-10-09 用户令「一类任务只开一个标签页并复用」;2026-10-11 用户令「不同的网站单独开标签 不要相互抢标签页」+「不同类型的网页 开不同的标签 不要相互覆盖」)。
|
||||
反复开页会把共享实例堆到**整机卡死**(端口不应答、进程不死,保活脚本也救不回 —— 见下面那条的实测读数)。
|
||||
细则**只写在下面那颗同名的 🔴🔴 子弹里**,⛔ 本处不复述 —— 但它**优先于任何"顺手再开一个"的念头**。
|
||||
- 🔴 **先探测、能复用就复用**(2026-10-08 用户当场点破:「为什么要重新开浏览器,先检查之前是否开过浏览能否复用」)。
|
||||
@@ -35,24 +35,28 @@ Direct browser control via CDP. For task-specific edits, use `agent-workspace/ag
|
||||
`curl --noproxy '*' -s http://127.0.0.1:9223/json/version` ⇒ 回 JSON 说明端口后面确实是 CDP。
|
||||
确认在跑之后按下面第 2 条的 env 写法连上去,先 `list_tabs()` 摸清现有页,
|
||||
自己需要的页用 `new_tab()` 开 —— ⛔ 不碰别人的页,也⛔ 不为自己方便关掉现有页。
|
||||
- 🔴🔴 **一类任务只开一个标签页并复用,⛔ 禁止反复开**(2026-10-08 用户令「后续调试网页 一个页面开一个标签就行了不要反复开」;2026-10-09 加严到**按任务算**:「一类任务只允许开一个标签页并复用,禁止反复开标签页」)。
|
||||
这条接在上一颗子弹后面:**先复用浏览器**是做对了,但**别在同一个浏览器里反复开页**。
|
||||
⚠️ **这不是"标签堆着难看"的问题 —— 它能把整机拖到卡死**(2026-10-09 实测):反复开页把共享实例堆爆后,
|
||||
Chrome 会进入**端口不应答、进程却还活着**的僵死态,而保活脚本只看端口 ⇒ 查不出、也救不回。
|
||||
当天现场读数:保活日志 `06:56 端口 9223 不应答(pid alive=True)`,直到 `07:06` 进程才退出;用户手动关掉全部会话。
|
||||
- 🔴🔴 **标签页按「页面类型」分:一类页面一个标签,各不覆盖**(2026-10-08 用户令「一个页面开一个标签就行了不要反复开」;2026-10-09 加严「一类任务只开一个标签页并复用」;2026-10-11 用户令,逐字:「**不同的网站单独开标签 不要相互抢标签页**」+「**不同类型的网页 开不同的标签 不要相互覆盖**」)。
|
||||
🔴 这是**同一条规则的两面,一起读**:**同一类页面只开一个**(别堆)+**不同类页面各开一个**(别合并)。
|
||||
⛔ 最容易犯的错是**只记住前一半** —— 把「复用同一个标签」当成原则,于是换页面时 `goto_url` 顶掉正在用的页;
|
||||
用户说的「相互覆盖 / 抢标签页」就是这个。
|
||||
⚠️ **堆页的代价**(2026-10-09 实测):反复开页把共享实例堆爆后,Chrome 会进入**端口不应答、进程却还活着**的僵死态,
|
||||
而保活脚本只看端口 ⇒ 查不出、也救不回。当天读数:`06:56 端口 9223 不应答(pid alive=True)`,
|
||||
直到 `07:06` 进程才退出;用户手动关掉全部会话。
|
||||
典型错法:调试一个原型,量一次 `new_tab(原型)` 一次、量完忘了关 ⇒ 一次会话堆出一串同一个页面的标签。
|
||||
|
||||
**什么算「一类任务」**:同一件事的一整串动作 —— 同一个原型 / 同一轮站点调研 / 同一批数据抓取。
|
||||
这类任务**从头到尾只用一个标签**:
|
||||
- **同一个页面**(同一 URL / 同一份原型)⇒ 就那一个标签。
|
||||
- **换路由** ⇒ 改 hash(`js("location.hash='#/x'")`)或 `goto_url()`,⛔ 不要 `new_tab()`。
|
||||
- **换到同任务的另一个页面** ⇒ 也优先在**同一个标签**里 `goto_url()`,⛔ 不要为新页面开新标签。
|
||||
**分页口径**:
|
||||
- 🔴 **一个用途一个标签**:列表页 / 登录页 / 正文页 / 解析页 / 后台面板 / 原型页 …… **每类页面各占自己的标签**。
|
||||
- 🔴 **动手前先 `list_tabs()` 认领本用途那一个**:已有 ⇒ 用它;没有 ⇒ `new_tab()`。
|
||||
- 🔴 **绝不对一个不属于本用途的页 `goto_url()`** —— 那会把上面正开着的东西顶掉,就是「覆盖」。
|
||||
- **同一类页面内部换路由** ⇒ 改 hash(`js("location.hash='#/x'")`)或 `goto_url()`,就在**这一个**标签里做。
|
||||
- **同一类页面反复开**(同一 URL 开两次以上)⇒ 那才是「堆页」,⛔ 禁止。
|
||||
- **换视口** ⇒ `cdp("Emulation.setDeviceMetricsOverride", width=…, height=…)` 在**同一个标签**里改,
|
||||
⛔ 不要为了量另一个宽度再开一个标签;量完 `cdp("Emulation.clearDeviceMetricsOverride")` 还原。
|
||||
- **只有必须并排看两个页面时**(参照物 vs 我方)才开第二个,**用完立刻 `close_tab()`**。
|
||||
- **完全不需要页面时**(只取一个数、截一张图)⇒ 复用**当前那个**标签,别再开。
|
||||
- 🔴 **收工前必须 `list_tabs()` 复核一遍,把自己开的一个不留**。共享实例是别人的工作现场,
|
||||
⛔ 别留尾巴;反向也一样 —— ⛔ 别顺手关掉不是自己开的页。
|
||||
- 🔴 **收工按用途清点**:自己为本任务开的页交代去留,⛔ 别留尾巴;反向也一样 —— ⛔ 别顺手关掉不是自己开的页。
|
||||
(依据 2026-10-11 同一目标两轮实测:① 主会话开「小侣坊登录页」等用户登录,任务会话在**同一标签** `goto_url`
|
||||
去 B 站列表 ⇒ 登录页当场消失、用户没法登录;② 任务会话又用**同一标签**从 B 站列表转去小侣坊解析页
|
||||
⇒ B 站列表那页没了。两次都只因「复用同一个标签」做过头,⛔ 不是标签开多了。)
|
||||
- ✅ **动手前先 `list_tabs()`** 看清有哪些标签页,别盲点。(这条同时是上面那条的**收工复核**用的)
|
||||
- ⛔ **别自建取数通道**:需要 DOM 数值就用 `js(...)`、需要截图就用 harness 的截图helper,
|
||||
⛔ 不要为了「量一个高度」另写一个脚本去起 Chrome。
|
||||
@@ -78,20 +82,25 @@ curl --noproxy '*' -s http://127.0.0.1:9223/json/version
|
||||
|
||||
**第 1 步 · 没在跑 ⇒ 用本会话的后台任务起**
|
||||
|
||||
两份绝对路径固定,启动器就用**技能包内那一份**(参数化,换工作区不用改代码):
|
||||
启动器就用**技能包内那一份**(参数化,换工作区不用改代码)。🔴 **路径按环境变量拼,⛔ 不写死本机路径**
|
||||
(2026-10-11 用户报「这个环境都是针对一台机器的,同步到别的机器把环境都搞坏了」):
|
||||
|
||||
- 启动器:`E:\ProgramData\.workbuddy\skills\browser-harness\agent-workspace\bh_chrome_keepalive.py`
|
||||
- 解释器:`E:\ProgramData\.workbuddy\binaries\python\versions\3.13.12\pythonw.exe`
|
||||
- 技能包根 = `$CODEBUDDY_CONFIG_DIR/skills/browser-harness`(`CODEBUDDY_CONFIG_DIR` 由宿主给;本机=`E:/ProgramData/.workbuddy`)
|
||||
- 启动器 = `<技能包根>/agent-workspace/bh_chrome_keepalive.py`
|
||||
- 解释器 = 与**当前会话同一个 Python** 的兄弟 `pythonw.exe`(本机例 `<配置目录>/binaries/python/versions/<版本>/pythonw.exe`)
|
||||
- 机器专属值(profile/日志/端口)⇒ 本包 **`env/bh.env`**(不入库;样例 `env/bh.env.example`)
|
||||
|
||||
命令(整段抄,然后**交给你这个会话的后台任务去跑**):
|
||||
|
||||
```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
|
||||
SK="$CODEBUDDY_CONFIG_DIR/skills/browser-harness"
|
||||
PYW="$(dirname "$(readlink -f "$(command -v python)")")/pythonw.exe" # 与当前会话同版本
|
||||
"$PYW" "$SK/agent-workspace/bh_chrome_keepalive.py" \
|
||||
>> "$CODEBUDDY_CONFIG_DIR/tmp/_bh_bg.out" 2>&1
|
||||
```
|
||||
|
||||
(`--log` 不必给:默认取 `env/bh.env` 的 `BH_LOG`,再兜底到 `<配置目录>/tmp/_bh_chrome.log`。)
|
||||
|
||||
🔴 **"交给你这个会话的后台任务跑"是这条起法的本体**:后台任务只活到本会话结束
|
||||
⇒ 会话在、浏览器在;会话收工、浏览器随之下线。这正是要的语义。
|
||||
|
||||
|
||||
@@ -33,9 +33,13 @@
|
||||
- **只用独立实例**:⛔ 不附着用户日常 Chrome(那是 9222,会弹「允许远程调试?」动到用户浏览器)。
|
||||
|
||||
用法:
|
||||
pythonw bh_chrome_keepalive.py --log "E:/ProgramData/.workbuddy/tmp/_bh_chrome.log"
|
||||
pythonw bh_chrome_keepalive.py
|
||||
(整条命令见 SKILL.md「调 AI 工具」§1;本脚本要放在**会话的后台任务**里跑)
|
||||
可选:`--port` `--chrome` `--profile` `--proxy` `--log`(都有默认值,见下)
|
||||
可选:`--port` `--chrome` `--profile` `--proxy` `--log`
|
||||
|
||||
🔴 **默认值从哪来(2026-10-11 起)**:先读本包 `env/bh.env`(每台机器一份,不入库),
|
||||
其次宿主 env 的 `BH_*`;再兜底到「WorkBuddy 配置目录(`CODEBUDDY_CONFIG_DIR`,否则 `~/.workbuddy`)
|
||||
+ tmp/…」。⛔ 代码里不写死本机路径 —— 要改就往 `env/bh.env` 里写(样例 `env/bh.env.example`)。
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
@@ -52,9 +56,39 @@ for _k in ("HTTP_PROXY", "HTTPS_PROXY", "http_proxy", "https_proxy"):
|
||||
os.environ.pop(_k, None)
|
||||
os.environ.setdefault("NO_PROXY", "127.0.0.1,localhost")
|
||||
|
||||
DEFAULT_CHROME = r"C:\Program Files\Google\Chrome\Application\chrome.exe"
|
||||
DEFAULT_PROFILE = r"E:/ProgramData/.workbuddy/tmp/bu-chatgpt-profile"
|
||||
DEFAULT_PORT = 9223
|
||||
# [browser-harness] 环境专属配置外置(🔴 2026-10-11 用户令「脚本是脚本 配置是配置」⇒「每个技能
|
||||
# 需要环境配置的 都有自己的 env文件夹」):**每台机器各不同**的值写本包 `env/bh.env`
|
||||
# (本机生成、不入库;样例 `env/bh.env.example` 入库)。⛔ 代码里不写死本机路径。
|
||||
def _bh_load_env():
|
||||
import os as _os
|
||||
_here = _os.path.dirname(_os.path.abspath(__file__))
|
||||
for _up in range(4):
|
||||
_base = _os.path.normpath(_os.path.join(_here, *([".."] * _up)))
|
||||
for _rel in (("env", "bh.env"), ("bh.env",)):
|
||||
_p = _os.path.join(_base, *_rel)
|
||||
if _os.path.isfile(_p):
|
||||
try:
|
||||
with io.open(_p, encoding="utf-8") as _f:
|
||||
for _ln in _f:
|
||||
_ln = _ln.strip()
|
||||
if _ln and not _ln.startswith("#") and "=" in _ln:
|
||||
_k, _v = _ln.split("=", 1)
|
||||
_os.environ.setdefault(_k.strip(), _v.strip())
|
||||
except Exception:
|
||||
pass
|
||||
return
|
||||
_bh_load_env()
|
||||
|
||||
# WorkBuddy 配置目录:宿主 env 优先,其次 `~/.workbuddy`(⛔ 不写死本机盘符/盘)
|
||||
_CB_CONF = os.environ.get("CODEBUDDY_CONFIG_DIR") or os.path.expanduser("~/.workbuddy")
|
||||
|
||||
DEFAULT_CHROME = (os.environ.get("BH_CHROME")
|
||||
or r"C:\Program Files\Google\Chrome\Application\chrome.exe")
|
||||
DEFAULT_PROFILE = (os.environ.get("BH_PROFILE")
|
||||
or os.path.join(_CB_CONF, "tmp", "bu-chatgpt-profile"))
|
||||
DEFAULT_LOG = (os.environ.get("BH_LOG")
|
||||
or os.path.join(_CB_CONF, "tmp", "_bh_chrome.log"))
|
||||
DEFAULT_PORT = int(os.environ.get("BH_PORT") or 9223)
|
||||
|
||||
|
||||
def _log(path, msg):
|
||||
@@ -95,9 +129,9 @@ def _launch(chrome: str, profile: str, port: int, proxy: str = ""):
|
||||
def main() -> int:
|
||||
ap = argparse.ArgumentParser(add_help=True)
|
||||
ap.add_argument("--port", type=int, default=DEFAULT_PORT)
|
||||
ap.add_argument("--chrome", default=os.environ.get("BH_CHROME") or DEFAULT_CHROME)
|
||||
ap.add_argument("--profile", default=os.environ.get("BH_PROFILE") or DEFAULT_PROFILE)
|
||||
ap.add_argument("--log", default=os.environ.get("BH_LOG") or "")
|
||||
ap.add_argument("--chrome", default=DEFAULT_CHROME)
|
||||
ap.add_argument("--profile", default=DEFAULT_PROFILE)
|
||||
ap.add_argument("--log", default=DEFAULT_LOG)
|
||||
ap.add_argument("--proxy", default=os.environ.get("BH_PROXY") or "",
|
||||
help='目标站点要走代理时才给,例 --proxy socks5://127.0.0.1:10800')
|
||||
a = ap.parse_args()
|
||||
|
||||
Vendored
+26
@@ -0,0 +1,26 @@
|
||||
# browser-harness · 环境专属配置(样例 · ⛔ 请勿当成真文件用)
|
||||
#
|
||||
# 🔴 位置约定(2026-10-11 用户令:「脚本是脚本 配置是配置」⇒「每个技能 需要环境配置的 都有自己的 env文件夹」):
|
||||
# 本技能**每台机器各不同**的值一律放 **`env/`**;脚本从 `env/` 读,代码里不写死。
|
||||
# 真文件=`env/bh.env`(本机生成,⛔ 不入库);本文件=入库样例。仓根 `.gitignore` 只放行 `env/**/*.example`。
|
||||
#
|
||||
# 读取机制:`agent-workspace/bh_chrome_keepalive.py` 头部 `_bh_load_env()` 以 setdefault 注入
|
||||
# ⇒ **宿主 env 优先,本文件兜底**。所有键都可省 —— 省了就按下面的兜底走。
|
||||
|
||||
# Chrome 独立实例的 profile 目录(固定复用 ⇒ 登录态保留)
|
||||
# 兜底:<WorkBuddy 配置目录>/tmp/bu-chatgpt-profile
|
||||
BH_PROFILE=<WorkBuddy 配置目录>/tmp/bu-chatgpt-profile
|
||||
|
||||
# 保活日志路径(本脚本要放在会话后台任务里跑 ⇒ 不落盘就看不到结果)
|
||||
# 兜底:<WorkBuddy 配置目录>/tmp/_bh_chrome.log
|
||||
BH_LOG=<WorkBuddy 配置目录>/tmp/_bh_chrome.log
|
||||
|
||||
# Chrome 可执行体;不填则用 Windows 默认安装位置:
|
||||
# C:\Program Files\Google\Chrome\Application\chrome.exe
|
||||
BH_CHROME=<chrome 可执行体的绝对路径>
|
||||
|
||||
# 调试端口(默认 9223;用户日常 Chrome 是 9222,两者别撞)
|
||||
BH_PORT=9223
|
||||
|
||||
# 可选:目标站点要走代理时才填,例 socks5://127.0.0.1:10800
|
||||
BH_PROXY=
|
||||
Reference in new issue
Block a user