212 lines
16 KiB
Markdown
212 lines
16 KiB
Markdown
# 换一台机器怎么用(部署手册)
|
||||
|
|
|
|||
|
|
> ## 🔴 当前结论(**先读这里** · 最后更新 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`)。 |
|
|||
|
|
> | **三条死路(都实测过,⛔ 别再试)** | `detached` spawn · `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`。
|
|||
|
|
· ⛔ 别在会话里起(会被回收);要起 ⇒ 独立窗口或「启动」文件夹 ⇒ 但它**拿不到口令 ⇒ 投递不了**(设计使然)。
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**验收(逐条可测)**
|
|||
|
|
1. `<python> collabd.py --where` ⇒ 打印出正确的 workspace / inbox / 任务图路径;
|
|||
|
|
2. `<python> collabd.py --once` ⇒ `rc=0`,`inbox/` 下出现看板与机械摘要,**无异常栈**;
|
|||
|
|
3. 造一条上报:`collabd.py --report T1 --state running --line <线> --by <会话名>` ⇒
|
|||
|
|
`collabd.py --reqs` 能看到 `T1 执行中`;
|
|||
|
|
4. `<python> collabd.py --tick` ⇒ `rc=0`,打印一行 `tick: … deliver=…`;
|
|||
|
|
🔴 **真投递的判据 ⛔ 不是 rc=0**:看 `wakeups.jsonl` **有没有新增一行 `ok:true`**;
|
|||
|
|
打印 `no-token` ⇒ 说明这个进程**不在宿主进程树内**(手工跑属正常;由钩子唤起时出现 ⇒ **异常**)。
|
|||
|
|
5. 让一个会话声明角色:`collabd.py --declare --role main` ⇒ 输出 `已声明角色:<sid> = main`;
|
|||
|
|
6. `<python> selftest.py` ⇒ **PASS 39 / FAIL 0**。
|
|||
|
|
7. 🔴 **常驻投递在跑**(定案项,见 §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 踩过:丢掉时钟 ⇒ 用户点破"成摆设")。
|
|||
|
|
|
|||
|
|
**三条硬约束**:
|
|||
|
|
1. 🔴 **必须完全静默** —— `stdout` 重定向到文件。⚠️ 真风险是**输出/事件量把会话日志推过 ~10 MiB 上限 ⇒ 宿主 `diagnostic-log:dropped` ⇒ 界面不再显示**(⇒ `pitfalls.md P0-2`);
|
|||
|
|
⛔ **不是"任务挂在会话名下"**(该归因已作废)。
|
|||
|
|
2. ⚠️ **别从会话/工具调用里"直接"起**(`detached spawn`)—— 调用一结束就被回收(实测:唤醒停在调用结束那一秒);
|
|||
|
|
要用**宿主后台任务机制**起,`stdout` 重定向到文件。
|
|||
|
|
3. ⛔ **计划任务(`schtasks`)在部分机器被安全策略硬拦** ⇒ 兜底走「启动」文件夹(⚠️ 但那**拿不到口令 ⇒ 投递不了**,只能做发现)。
|
|||
|
|
|
|||
|
|
⚠️ **口令边界的正确表述**:由会话之外起的常驻**拿不到网关口令** ⇒ 它只**发现 + 落盘**(`STALL.md`/`NEED-USER.md`/看板)——
|
|||
|
|
**这不是故障,是设计**:`collabd.py` 用 `FROM_HOOK` 分辨两种"没口令"(钩子唤起却没口令 ⇒ 异常 ⇒ 落 `NEED-USER.md`;常驻没口令 ⇒ 只记日志)。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 5b 🔴 「覆盖网络节点 · 中继客户端 + 设备接入 · 本地反代」的**探测与自起**(2026-09-30 实测定型 · 任何棒都要会)
|
|||
|
|
|
|||
|
|
> 背景:手机接入链路的**最前一格**是"设备接入本地反代 + 覆盖网络节点中继客户端 都在线"。它不在 ⇒ relay 无通道 ⇒ 入口④闸 `503 device-unreachable` ⇒ 整条链断。
|
|||
|
|
> ⚠️ 这两个进程**不跨 WorkBuddy 重启**,且**本机无法做成计划任务**(`schtasks` 被内置程序黑名单硬拦)。
|
|||
|
|
|
|||
|
|
**① 先探测(⛔ 在跑就别重复起 —— 两份会抢同一 host 注册/同一端口)**
|
|||
|
|
```bash
|
|||
|
|
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**。
|
|||
|
|
|
|||
|
|
**⛔ 三条死路,别再试**(都实测过)
|
|||
|
|
1. **`detached` spawn** —— 活不过工具调用边界(日志 0 字节即死);`wb-overlay-node-launch.mjs --detached` 注释里"本机实测不可用"是真的。
|
|||
|
|
2. **`schtasks`** —— 内置程序黑名单硬拦(见 `pitfalls` 里那六项),⛔ 命令内不可放行。
|
|||
|
|
3. **从 bash 调 `powershell.exe`** —— 被策略拦("绕过 PowerShell 安全检查");而 `overlay-node-daemon.ps1 -Action run` 是 **powershell 前台长跑**,也活不下来。
|
|||
|
|
|
|||
|
|
**⚠️ 遗留单点(如实登记)**:这两条后台任务**挂在"起它的那个会话"名下** ⇒ **起它的会话被回收 / WorkBuddy 退出 ⇒ 链路断**。
|
|||
|
|
⇒ 起它们的会话**在那段时间内不要关**;断了就按上面"② 不在才起"重起一次。
|
|||
|
|
|
|||
|
|
## 6 换机器后必查的 5 件事
|
|||
|
|
|
|||
|
|
1. `--where` 的三个路径是否都对;
|
|||
|
|
2. 钩子接线是否指向 skill(§4);
|
|||
|
|
3. 宿主库能否只读打开(`sessions` / `automation_runs` / `automations` 三张表读得到);
|
|||
|
|
4. 有没有**别的实现**在抢同一个 inbox(§4);
|
|||
|
|
5. `guard.py` 是否真的起来了(`--status`)+ 它是否会随开机自启。
|
|||
|
|
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 5d 🔴 「会话整体停止」后怎么恢复(2026-09-30 立 · 用户追问逼出来的)
|
|||
|
|
|
|||
|
|
> 用户原话:「所以现在的问题就是 **会话整体停止了怎么办** 的问题」。
|
|||
|
|
|
|||
|
|
**先分清三档 —— 只有第三档才需要动手:**
|
|||
|
|
|
|||
|
|
| 档 | 场景 | 会怎样 | 怎么办 |
|
|||
|
|
|---|---|---|---|
|
|||
|
|
| ① | **只有某个会话停了**(WorkBuddy 还开着) | 机制照跑 —— 钩子是**全局**注册的,**任何**会话跑 Bash/发消息都会唤起它 | **不用办** |
|
|||
|
|
| ② | **所有会话都 idle**(WorkBuddy 还开着) | 钩子不被唤起 ⇒ 不投递 | **不用办** —— 那时没人在看;一有动作,积压立刻补投 |
|
|||
|
|
| ③ | **WorkBuddy 退出 / 机器休眠** | **整条协作链停摆** —— 唯一能"开新会话"的通道就是宿主自动化 | **按下面清单恢复** |
|
|||
|
|
|
|||
|
|
### 恢复清单(重开 WorkBuddy 后按序跑)
|
|||
|
|
|
|||
|
|
1. **看前置两段**:`netstat -ano | grep ":20090"`(判据**只认 LISTENING 行**,⛔ 别用 curl/connect —— 本机 Proxifier 会代理回环,返回是假的)。
|
|||
|
|
缺了 ⇒ 按 **§5b** 拉。⚠️ **中继客户端那半边已计划任务化**,通常自己会回来。
|
|||
|
|
2. **查过期未跑的一次性排期**(`status='ACTIVE' and schedule_type='once'` 且 `next_run_at < 现在`):
|
|||
|
|
有 ⇒ **新建一条**(⛔ **改时间不会触发**)。
|
|||
|
|
3. **跑一轮投影** `collabd.py --once`,让队列/看板/`NEXT.md` 刷新。
|
|||
|
|
4. **看 `NEED-USER.md`**:有没有等你拍板的事。
|
|||
|
|
5. **看 `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`)⇒ **「不占会话」与「有口令」二选一**。
|
|||
|
|
- ⇒ **结论**:**「会话整体停止」不需要"防",只需要"恢复流程"**。
|
|||
|
|
⛔ 不要再设计"常驻监督进程"去扛 —— 那条路已被实测堵死(要么没口令、要么拖住会话)。
|