- 按用户指示清空原有 25 技能内容,只提交 session-mechanism(57 文件) - 附 .gitignore(产物 + 本机凭据) - 令牌明文已脱敏(历史 .neodata_token 与 pitfalls 引用均不入库) - 本提交为孤儿提交(父提交为空),历史自此重新开始
16 KiB
换一台机器怎么用(部署手册)
🔴 当前结论(先读这里 · 最后更新 2026-09-30 05:55)
项 当前结论 本机起法 唯一可行 = 宿主后台任务机制 + stdout全重定向到文件(完全静默)。⛔detached活不过工具调用边界;⛔schtasks被内置程序黑名单硬拦。投递 🔴 定案:协作与投递「一直运行」(常驻) —— 09-29 定案,2026-10-01 用户再确认(原话「协作与投递一直运行(常驻)」;理由「可能不是所有队列都是钩子产生的」)。⚠️ 现状:守护处于停机(2026-09-29 23:59 止损;其归因已于 09-30 审计修正 —— 真因是日志撞 ~10 MiB 被丢写,⛔ 不是"挂在会话名下")⇒ 拉起常驻见 §5。⛔ 「用自动任务当闹钟」2026-10-01 已废弃。 前置(覆盖网络节点中继客户端 + 设备接入本地反代) 会周期性掉(实测:设备接入本地反代约 1h 被 reg.exe拦死)⇒ 探测与自起见 §5b;⚠️ AI 侧无法自动重起,只能报警 + 人工/会话拉起。 ⚠️ 两段都在线 ≠ 链路通过(判据=中继真转发过流streams>0)。三条死路(都实测过,⛔ 别再试) detachedspawn ·schtasks· 从 bash 调powershell.exe⚠️ 本文件为追加式 ⇒ 与本节冲突时以本节 + architecture.md的「当前结论」节为准。
目标:整套「多会话协作机制」只在这一个 skill 里(代码 + 架构 + 规范 + 手册),换机器=拷这个目录 + 改一份配置 + 接一次钩子。 ⛔ 换机器不需要拷贝任何工作区里的脚本 —— 若发现机制代码出现在工作区,那就是旧副本,按 §4 退役。
0 这个 skill 里有什么(机制本体)
| 文件 | 是什么 |
|---|---|
SKILL.md |
操作入口:怎么派活、怎么收尾、怎么排坑 |
references/architecture.md |
🔴 唯一架构文档(五主体/需求台账四态/单条握手/三条件唤醒/红线/判据/术语) |
references/deploy.md |
本文件(换机器) |
references/pitfalls.md |
踩坑清单(每条都真实发生过) |
references/taskgraph.md |
任务图规范 |
scripts/collabd.py |
常驻程序(--once 投影轮)+ 投递(--tick 投递轮)+ 🔴 常驻形态(--supervise,定案=一直运行,见 §5)+ 上报 / 台账 / 看板 |
scripts/guard.py |
🟡 可选:守护(只做发现+落盘,⛔ 不承担投递)。⛔ 不是投递的前置 |
scripts/selftest.py |
回归自测(20 条用例 · 不碰生产):改完必跑,全绿才算改完 |
scripts/collabd.config.example.json |
配置模板(拷成 collabd.config.json 改路径) |
⛔ 不进 skill 的:运行态产物(台账/看板/通知/日志)—— 它们属于每个工作区自己,路径由配置的 workspace + inbox 决定。
1 前置(一次性)
| 项 | 要求 |
|---|---|
| Python | 3.11+(实测 3.13);ast.parse 可用的标准库即可,⛔ 无第三方依赖 |
| 宿主 | WorkBuddy 桌面版(需要它的三张只读表 + 自动化排期 + 钩子) |
| 目录 | 把本 skill 放到 <配置目录>/skills/session-mechanism/(如 E:/ProgramData/.workbuddy/skills/) |
2 三分钟部署
⓪ 声明**任务目标**(🔴 没有目标 ⇒ guard **拒绝启动**,rc=3):
<python> <skill>/scripts/guard.py --goal "<一句话任务目标>"
也支持手写 <workspace>/<inbox>/goal.json(title 必需;可附 acceptance_doc / taskgraph / lines)
① 拷 skill 目录到新机器的 skills/ 下
② 在 scripts/ 下:cp collabd.config.example.json collabd.config.json
改 4 处必填:workspace / inbox / live / taskgraph (其余按需,见 §3)
③ 🔴 **拉起常驻(投递本体)**:`<python> <skill>/scripts/collabd.py --supervise` —— **它同时就是唤醒时钟**。
⚠️ 起法约束见 §5;⛔ 「用自动任务当闹钟」已于 2026-10-01 废弃。
④ 🔴 **接钩子(只作"即时性"补充,⛔ 不是主路径)**
让宿主在**两个**事件上调用一个**本地脚本**(脚本内部再去调 collabd):
事件 A:`UserPromptSubmit` → <python> .../wb-result-hook.py
事件 B:`PreToolUse`(matcher `^Bash$`) → <python> .../wb-result-hook.py
脚本内部做两件事(都**节流**、都**隐窗**):
· `collabd.py --once` ⇒ 常驻程序跑一轮**投影**(⛔ 不投递)
· `collabd.py --tick` ⇒ 投递跑一轮 ⇒ **投递 + 推进队列**
🔴 **它的定位 = 补充**:钩子是**宿主起的子进程** ⇒ 自带网关口令 + ⛔ 不占会话 + 零 token ⇒ 有事件时**立刻**投一版;
但 ⛔ **不能靠它兜底** —— 「需要投递的时刻必然伴随会话在动」**是错假设**(队列可能由**非钩子来源**产生,那时没有事件 ⇒ 漏)。
⚠️ **新加的钩子事件要宿主重启后才生效**(`UserPromptSubmit` 那条**立即生效**);
⚠️ 钩子**只指向 skill/工作区里那一份**脚本,⛔ 不许有第二份(见 §4)。
⑤ 🟡 **守护:可选**(⛔ 不是投递前置;**投递本体= ③ 的常驻**)。
它的职责只剩**发现**:长时间没人动 ⇒ 落 `STALL.md`/`NEED-USER.md`。
· ⛔ 别在会话里起(会被回收);要起 ⇒ 独立窗口或「启动」文件夹 ⇒ 但它**拿不到口令 ⇒ 投递不了**(设计使然)。
验收(逐条可测)
<python> collabd.py --where⇒ 打印出正确的 workspace / inbox / 任务图路径;<python> collabd.py --once⇒rc=0,inbox/下出现看板与机械摘要,无异常栈;- 造一条上报:
collabd.py --report T1 --state running --line <线> --by <会话名>⇒collabd.py --reqs能看到T1 执行中; <python> collabd.py --tick⇒rc=0,打印一行tick: … deliver=…; 🔴 真投递的判据 ⛔ 不是 rc=0:看wakeups.jsonl有没有新增一行ok:true; 打印no-token⇒ 说明这个进程不在宿主进程树内(手工跑属正常;由钩子唤起时出现 ⇒ 异常)。- 让一个会话声明角色:
collabd.py --declare --role main⇒ 输出已声明角色:<sid> = main; <python> selftest.py⇒ PASS 39 / FAIL 0。- 🔴 常驻投递在跑(定案项,见 §5):进程活着 +
collabd.py --where解析到正确 workspace; 判「真投递」⛔ 不看 rc,看wakeups.jsonl有没有新增ok:true。
3 配置项(collabd.config.json)
| 键 | 必填 | 说明 |
|---|---|---|
workspace |
✅ | 你的工作区绝对路径(正斜杠);台账/看板/通知都相对于它 |
inbox |
✅ | 运行态目录(相对 workspace),建议 tmp/supervise-inbox |
live / taskgraph |
✅ | 看板文件 / 任务图 JSON(相对 workspace) |
lines |
建议 | 线名 → 中文名 |
host_db |
否 | 宿主库路径;空 ⇒ 用 CODEBUDDY_CONFIG_DIR 推导 |
shim_port / client_entry / client_runner |
否 | 服务自愈用;不做自愈就留空 |
wake_enable / wake_min_gap / wake_text |
否 | 唤醒投递(需要网关口令在环境里) |
⚠️ 本机配置(collabd.config.json)不要提交到公共仓库 —— 它含本机绝对路径。
4 旧副本退役(⛔ 否则"代码在 A、钩子在 B")
症状:改了 skill 里的程序却没生效;或 advance.md 等产物由另一个副本写出(内容与你预期不同)。
① 工作区里若有 collabd.py / advance-watch.py / keepalive.py / thin-consumer.py
⇒ 全是旧副本(职责已被本 skill 吸收)⇒ 改名加 `.retired-<日期>`,⛔ 不删(留回滚)
② 确认钩子指向的是 **skill 里的** collabd.py(`--where` 自证路径)
③ 重启后看 `inbox/` 产物的 mtime 是否随会话事件刷新 ⇒ 是则接线正确
🔴 同名的两份实现是"最难查的故障"(实测踩过:两份同时跑,同一秒给出互相矛盾的读数)。
5 投递的边界(🔴 一句话:投递 = 一个常驻进程(它同时就是唤醒时钟);钩子只作补充,⛔ 不排期、⛔ 不用自动任务)
| 有口令(能投递) | 需要容器会话 | 必须活着的进程数 | 跨重启自动生效 | 投递延迟 | 零 token | |
|---|---|---|---|---|---|---|
| 🔴 宿主后台任务(现定案的载体) | ✅ | ✅ 需 1 个 | ≥1 | ⛔ 需再拉起 | ✅ 可调(轮询间隔) | ✅ |
| 宿主钩子唤起的一次性进程(降为补充) | ✅ | ⛔ 不需要 | 0 | ✅ | ⛔ 依赖事件 ⇒ 会漏 | ✅ |
| 会话外常驻(启动文件夹/独立窗口) | ⛔ 否 | ⛔ 不需要 | ≥1 | ✅ | ✅ 可调 | ✅ |
⇒ 🔴 载体 = 宿主后台任务(本机实测:detached 活不过工具调用边界、schtasks 被内置程序黑名单硬拦)
⇒ ✅ 唯一可行 = 宿主后台任务机制 + stdout 全重定向到文件(完全静默)。
⚠️ 「必须活着的进程数 = 0」⛔ 不得再拿来否掉常驻(2026-09-30 踩过:丢掉时钟 ⇒ 用户点破"成摆设")。
三条硬约束:
- 🔴 必须完全静默 ——
stdout重定向到文件。⚠️ 真风险是输出/事件量把会话日志推过 ~10 MiB 上限 ⇒ 宿主diagnostic-log:dropped⇒ 界面不再显示(⇒pitfalls.md P0-2); ⛔ 不是"任务挂在会话名下"(该归因已作废)。 - ⚠️ 别从会话/工具调用里"直接"起(
detached spawn)—— 调用一结束就被回收(实测:唤醒停在调用结束那一秒); 要用宿主后台任务机制起,stdout重定向到文件。 - ⛔ 计划任务(
schtasks)在部分机器被安全策略硬拦 ⇒ 兜底走「启动」文件夹(⚠️ 但那拿不到口令 ⇒ 投递不了,只能做发现)。
⚠️ 口令边界的正确表述:由会话之外起的常驻拿不到网关口令 ⇒ 它只发现 + 落盘(STALL.md/NEED-USER.md/看板)——
这不是故障,是设计:collabd.py 用 FROM_HOOK 分辨两种"没口令"(钩子唤起却没口令 ⇒ 异常 ⇒ 落 NEED-USER.md;常驻没口令 ⇒ 只记日志)。
5b 🔴 「覆盖网络节点 · 中继客户端 + 设备接入 · 本地反代」的探测与自起(2026-09-30 实测定型 · 任何棒都要会)
背景:手机接入链路的最前一格是"设备接入本地反代 + 覆盖网络节点中继客户端 都在线"。它不在 ⇒ relay 无通道 ⇒ 入口④闸
503 device-unreachable⇒ 整条链断。 ⚠️ 这两个进程不跨 WorkBuddy 重启,且本机无法做成计划任务(schtasks被内置程序黑名单硬拦)。
① 先探测(⛔ 在跑就别重复起 —— 两份会抢同一 host 注册/同一端口)
netstat -ano | grep ":20090" # 设备接入 · 本地反代在听否
tail -1 E:/dsh-worker-dev/logs/overlay-bg-*.out.log # 最近一行 state=up(for …) ⇒ 覆盖网络节点中继客户端在跑
curl -s --noproxy '*' -o /dev/null -w "%{http_code}\n" http://127.0.0.1:20090/ # 期望 200
② 不在才起 —— 🔴 必须"宿主后台任务机制 + stdout 全重定向到文件"(完全静默)
| 件 | 命令 |
|---|---|
| 覆盖网络节点 · 中继客户端 | node D:/github/dsh_shenxian/lib/net/relay/main.js --client --url wss://ai1net.com/dshs-relay --host <hostId> --network <network> --keys-file E:/dsh-worker-dev/overlay/relay-keys.local.json --ports 20090(env:DSHS_OVERLAY_NODE_KEY_FILE / DSHS_OVERLAY_NODE_GRANT_FILE;<hostId>/<network> 从 E:/dsh-worker-dev/overlay/overlay-node.local.json 读) |
| 设备接入 · 本地反代 | node E:/github/dsh-desktop-0.1.7rc2/node_modules/tsx/dist/cli.mjs E:/ProgramData/AIProject/ai1net-dsh-desktop/.workbuddy/_devkit/launch-desktop-dev-017.mts(cwd=E:/github/dsh-desktop-0.1.7rc2;⚠️ 必须先 unset ELECTRON_RUN_AS_NODE,否则 Electron 退化成纯 Node) |
成功判据(两条都要):覆盖网络节点中继客户端 ⇒ 日志出现本轮新增的 registered host=… accepted=[20090] 且 state=up;设备接入本地反代 ⇒ 20090 LISTENING + curl 200。
⛔ 三条死路,别再试(都实测过)
detachedspawn —— 活不过工具调用边界(日志 0 字节即死);wb-overlay-node-launch.mjs --detached注释里"本机实测不可用"是真的。schtasks—— 内置程序黑名单硬拦(见pitfalls里那六项),⛔ 命令内不可放行。- 从 bash 调
powershell.exe—— 被策略拦("绕过 PowerShell 安全检查");而overlay-node-daemon.ps1 -Action run是 powershell 前台长跑,也活不下来。
⚠️ 遗留单点(如实登记):这两条后台任务挂在"起它的那个会话"名下 ⇒ 起它的会话被回收 / WorkBuddy 退出 ⇒ 链路断。 ⇒ 起它们的会话在那段时间内不要关;断了就按上面"② 不在才起"重起一次。
6 换机器后必查的 5 件事
--where的三个路径是否都对;- 钩子接线是否指向 skill(§4);
- 宿主库能否只读打开(
sessions/automation_runs/automations三张表读得到); - 有没有别的实现在抢同一个 inbox(§4);
guard.py是否真的起来了(--status)+ 它是否会随开机自启。
5d 🔴 「会话整体停止」后怎么恢复(2026-09-30 立 · 用户追问逼出来的)
用户原话:「所以现在的问题就是 会话整体停止了怎么办 的问题」。
先分清三档 —— 只有第三档才需要动手:
| 档 | 场景 | 会怎样 | 怎么办 |
|---|---|---|---|
| ① | 只有某个会话停了(WorkBuddy 还开着) | 机制照跑 —— 钩子是全局注册的,任何会话跑 Bash/发消息都会唤起它 | 不用办 |
| ② | 所有会话都 idle(WorkBuddy 还开着) | 钩子不被唤起 ⇒ 不投递 | 不用办 —— 那时没人在看;一有动作,积压立刻补投 |
| ③ | WorkBuddy 退出 / 机器休眠 | 整条协作链停摆 —— 唯一能"开新会话"的通道就是宿主自动化 | 按下面清单恢复 |
恢复清单(重开 WorkBuddy 后按序跑)
- 看前置两段:
netstat -ano | grep ":20090"(判据只认 LISTENING 行,⛔ 别用 curl/connect —— 本机 Proxifier 会代理回环,返回是假的)。 缺了 ⇒ 按 §5b 拉。⚠️ 中继客户端那半边已计划任务化,通常自己会回来。 - 查过期未跑的一次性排期(
status='ACTIVE' and schedule_type='once'且next_run_at < 现在): 有 ⇒ 新建一条(⛔ 改时间不会触发)。 - 跑一轮投影
collabd.py --once,让队列/看板/NEXT.md刷新。 - 看
NEED-USER.md:有没有等你拍板的事。 - 看
blocked.json:受阻件是否已随状态更新解除(⛔ 别让它一直当队首、每次唤醒白跑)。
🔴 为什么"不需要防"(实测结论,⛔ 别再想做常驻去扛)
- 实测(2026-09-30 08:2x):11 条一次性排期里 9 条准点跑;唯一 0 次那条的排期在创建时就已过期 —— ⇒ 宿主在跑,排期就准点;宿主不在,排期不会自己跑(调度器就是宿主本身)。
- 能扛过 WorkBuddy 退出的:只有不需要口令的长跑 —— 实测就是覆盖网络节点中继客户端(已由计划任务持有,
父链
powershell ← svchost ← services ← wininit,⛔ 无 bash/无 WorkBuddy)。 - 扛不过的:任何要投递的东西 —— 投递要网关口令,而口令的唯一合法来源是 WorkBuddy 进程 env
(08:06:31 实测:计划任务上下文
envPresent=false / envLen=0)⇒ 「不占会话」与「有口令」二选一。 - ⇒ 结论:「会话整体停止」不需要"防",只需要"恢复流程"。 ⛔ 不要再设计"常驻监督进程"去扛 —— 那条路已被实测堵死(要么没口令、要么拖住会话)。