Files
dsh_ai1net_server/归档/技能包快照/session-mechanism-20261004/references/deploy.md
T
admin c1b5e4d966 chore(工作区): 全量入库 + 补齐 .gitignore(以工作区为准)
- 变更规模:新增 514 / 修改 62 / 重命名 155 / 删除 4(归档重组与文档轮次)
- .gitignore 修:`归档/**/db-cwd归一-备份-*/` —— 原规则写绝对层级(归档/db-cwd归一-…),
  目录搬进 归档/配置与备份/ 后**静默失效**,43 MB 的 DB 备份又变成未跟踪
- .gitignore 补:嵌套 git 内部数据(归档/内嵌git-20261008/、归档/skills-git-旧线-20261007/dotgit-原样移出/)
- .gitignore 补:运行态与部署副本(.workbuddy/collab/、.workbuddy/tools/、.workbuddy/.load-pending、.workbuddy/tmp-*)
- .gitignore 补:备份件(*.bak-*)
- 未跟踪文件从 2190 降到 890(其余为 归档/ 归档件与 .workbuddy/memory/ 知识文件,按口径入库)
2026-10-10 23:13:22 +08:00

213 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 换一台机器怎么用(部署手册)
> ## 🔴 当前结论(**先读这里** · 最后更新 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`)⇒ **「不占会话」与「有口令」二选一**。
- ⇒ **结论**:**「会话整体停止」不需要"防",只需要"恢复流程"**。
⛔ 不要再设计"常驻监督进程"去扛 —— 那条路已被实测堵死(要么没口令、要么拖住会话)。