Files
dsh_ai1net_server/交付物/回报-桌面线-垫片常驻化-20260930.md
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

334 lines
23 KiB
Markdown
Raw Permalink 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.
# 回报 · 桌面线:设备接入垫片**常驻化**(V1 最后一跳)—— 判 **✅ 打通**
> 出具:`[协作]垫片常驻-1049`(桌面/客户端线 worker)· 2026-09-30 10:36–11:0x
> 域锁:`--claim-exec "[协作]垫片常驻-1049" --domains ai1net-dsh-desktop` → **RC=0**,OWNER 复读=自己;收尾已 `--release-exec`
> 依据件(开工先读完):`交付物/交接单-桌面线-垫片常驻化-20260930.md`(自足件)
> **只做交接单那一件** —— ⛔ 未重做/未推翻 A 方案设计 · ⛔ 未扩展范围
---
## 0 结论(先给判)
| # | 判据 | 结果 | 一句读数 |
|---|---|---|---|
| ① | `20090` **LISTENING**(**只认 netstat**) | ✅ **过** | `TCP 127.0.0.1:20090 0.0.0.0:0 LISTENING 39220` |
| ② | 自检 `credential.present` | ✅ **过** | `{"source":"inbound","header":"x-access-token","present":true}` |
| ③ | 经垫片 `/api/v1/health` | ✅ **过** | `http=200`(非 401);旁证:经垫片打 `/api/v1/info` → **200** 带真数据;**直连网关同路径不带凭据 → 401** ⇒ 凭据确被带上 |
| ④ | 平台设备入口不再是 `503 instance_starting` | ⚠️ **本会话未独立复跑**(见 §5);**主会话已于 10:46 复核为全 200 并据此置 `V1=pass`** | 本会话给出**设备侧同路径等价读数**:`/api/v1/info` = 200 |
| ⑤ | 起法是否 **durable** | ✅ **过** | **Windows 计划任务 `DSH-Client-Dev`**(`State=Running`);父链 `node#39220 ← node#44332 ← cmd#33508 ← svchost#3008 ← services#1980 ← wininit#1824`、**`workbuddyAncestor=null`** |
> ### 🔴 一句话:**V1 的最后一跳已打通** —— 垫片现在跑在一个**父链挂在 `svchost`、env 里没有口令**的进程里,而它照样 `present=true`、`health=200`。
> ### 做法:把「让垫片常驻」翻译成「让 **DSH host** 常驻」⇒ **用普通 node 直起 `apps/desktop-host`(不弹 Electron 窗口)**,交给**计划任务**持有。
---
## 1 三件事怎么做的(按交接单顺序)
### 1.1 「不起 Electron UI 也能把宿主拉起来」⇒ **能**(首选路已走通)
**不是猜的,两条硬证据**:
1. `@deepseek-ai/dsh-desktop-host` 的 `src/` 与 `lib/` **零处 `electron` 引用**;其 package.json 的 description 原文就写着 **"Private **Node-mode** host process for the Electron desktop application"**。
2. **官方壳自己也是把它当独立 node 子进程 spawn 的**:`apps/desktop/src/host-process.ts:188-190`
`entry = <runtimeDir>/node_modules/@deepseek-ai/dsh-desktop-host/lib/index.js`,
`spawn(this.node, ['--expose-internals', entry, runtimeDir, projectDir, primaryRuntime, pnpm, nodeBinDir])`
—— `this.node` 是**以 `ELECTRON_RUN_AS_NODE=1` 运行的 electron**,语义上就是 node。
⇒ **headless 起法 = 逐字照搬官方壳的 spawn 形状,只把 executable 换成 managed node**(`E:/ProgramData/.workbuddy/binaries/node/versions/22.22.2-3/node.exe`)。
⚠️ 一处必须核的守卫:入口是 `if (import.meta.main)`(`lib/index.js:348`)—— **node 22.22.2 / 24.19.0 实测均为 `true`**;更老的 node 会**静默不执行 `main()`**(进程立刻退出、零输出)。
### 1.2 做成 durable ⇒ **Windows 计划任务**(⛔ 不是 `detached`)
- 🔴 `detached:true` 在本机**无效**:会话进程在 Job 对象里且 `CREATE_BREAKAWAY_FROM_JOB` 实测 `err=5` ⇒ **逃不出会话**(09-29 已实测,本轮沿用其结论)。
- ✅ **计划任务**:Task Scheduler 持有 ⇒ 天然脱离任何会话;`LogonTrigger` ⇒ 重启自恢复。
- **本轮动作**:把**已有任务** `DSH-Client-Dev` 的**动作改指**到新入口(⛔ 没有另建第二个任务、⛔ 没有重建机制)——
动作:`C:\windows\System32\cmd.exe /c "E:\ProgramData\AIProject\ai1net-dsh-desktop\.workbuddy\_devkit\wb-shim-host.cmd"`
其余参数逐字沿用 09-29 已验证有效的模板:`LogonTrigger Enabled=true` | `MultipleInstancesPolicy=IgnoreNew` | `RunLevel=LeastPrivilege` | `ExecutionTimeLimit=PT0S`(常驻监督不被 OS 砍)。
⚠️ XML 声明是 `UTF-16` ⇒ 落盘**必须带 BOM**(`writeFileSync(...,'utf16le')` **不写 BOM** ⇒ schtasks 报「XML 格式错误 (1,2)」)。
⚠️ `schtasks` **在会话命令行直接调会被本机策略拦** ⇒ 一律**经脚本 spawn**(本轮 `--install/--query/--run` 全 `rc=0`)。
- **常驻监督 + 掉线自恢复**:入口拉 host 后等 `20090` 就绪,随后 10 s 一轮;口掉了就重拉(任务 `ExecutionTimeLimit=PT0S` ⇒ OS 不会砍它)。
### 1.3 A 方案的口令 ⇒ **一个字都没改它的设计**;反而**主动复现了生产上下文**
- ⛔ 未碰 `POST /__device_access/token` 的任何一步(尤其**保留了「探活通过才采纳」**那道闸)。
- 🔴 **本入口主动把网关口令从子进程 env 里摘掉**(枚举删,⛔ 不用点号)—— 因为**计划任务上下文本来就没有它**。不摘就是"会话内靠 env"的假象,测不出 A 方案是否真的成立。
- 结果:**`gatewayTokenEnvPresent=false` 的进程,自检照样 `present=true / source=inbound`** ⇒ A 方案在生产形态下**自证成立**。
- 口令全程**未读、未打印、未落盘**(本入口只打印"在场/长度/键名"这类非密信息;投递走项目自带 `deliver-gateway-token.py`,口令只在 env→请求体)。
---
## 2 逐条机器读数(可核对)
### 2.1 起法 · 进程独立性(**本轮最关键的一行**)
`_devlogs/shim-host-task.jsonl` 里 `mode=spawned` 那条(原文,节选):
```json
{"ts":"2026-09-30T02:44:41.906Z","entryPid":44332,"ppid":33508,
"gatewayTokenEnvPresent":false,
"chain":["node.exe#44332","cmd.exe#33508","svchost.exe#3008","services.exe#1980","wininit.exe#1824"],
"workbuddyAncestor":null,
"mode":"spawned","launched":true,"supervise":true,"hostPid":39220,
"listeners":[{"addr":"127.0.0.1","port":20090,"pid":39220}],"selfcheck":200,
"hostChain":["node.exe#39220","node.exe#44332","cmd.exe#33508","svchost.exe#3008","services.exe#1980","wininit.exe#1824"]}
```
对照面(同一入口在**会话内**跑时记录的那条):`chain=[node ← bash ← bash ← bash ← WorkBuddy.exe#39252 ← …]`、`workbuddyAncestor=WorkBuddy.exe#39252`、`gatewayTokenEnvPresent=true`。
⇒ **「会话内」与「计划任务内」两态判然不同,且后者才是生产形态。**
### 2.2 端口与绑定面(⛔ 只绑回环 ⇒ 无权限扩大)
| 口 | 绑定 | 持有者 | 说明 |
|---|---|---|---|
| `20090` | **`127.0.0.1`** | `39220` | 垫片(本任务) |
| `19387` | **`127.0.0.1`** | `39220` | headless host 自带的 dsh web(官方行为;**也是只绑回环**) |
全机 `dsh-desktop-host` **恰 1 个真进程**(39220)。⚠️ 计数查询会**把探针自己的命令行算进来**(见 §6 新坑),本轮以**父链**判唯一性。
### 2.3 垫片自检(原文)
```json
{"ok":true,"module":"device-access","moduleVersion":"1.0.0","mode":"workbuddy","port":20090,"loopback":"127.0.0.1",
"upstream":{"port":55283,"pid":56072,"matchedBy":"self",
"evidence":{"listenerImage":"WorkBuddy.exe","rootStatus":200,"rootMarkerHit":"CodeBuddy Gateway","healthStatus":401}},
"credential":{"source":"inbound","header":"x-access-token","present":true},
"breaker":{"consecutiveFailures":0,"degraded":false,"threshold":3,"readOnlyWhileDegraded":true}}
```
### 2.4 「凭据真的被带上并转发成功」的三段对照
| 打哪 | 带凭据? | 结果 |
|---|---|---|
| 经垫片 `GET /api/v1/health` | 垫片自动注入 | **200** |
| 经垫片 `GET /api/v1/info` | 垫片自动注入 | **200** `{"data":{"cwd":"E:\\ProgramData\\AIProject\\mcn-short-video","version":"5.6.2","os":"win32","arch":"x64","nodeVersion":"v22.21.1","gatewayMode":"local","userName":"Administrator"}}` |
| **直连上游** `127.0.0.1:55283/api/v1/info` | ⛔ 不带 | **401** |
⇒ 第三行是关键对照:**同一个口、同一条路径,不带口令必 401** ⇒ 第二行的 200 只可能来自"垫片替我们带上了口令"。
### 2.5 A 方案投递(项目侧日志,原文)
```
[2026-09-30 10:21:53] 投递成功 → source=inbound
[2026-09-30 10:44:19] 投递成功 → source=inbound
[2026-09-30 10:45:32] 投递成功 → source=inbound
```
### 2.6 幂等(模拟"下次登录会再跑一次")
再调一次入口 ⇒ `✅ 20090 已在监听(pid=39220)⇒ 幂等早退,未重复拉起。`、`rc=0`、**未新增任何进程**、`listeners` 仍只有 39220。
### 2.7 计划任务体检
| 项 | 读数 |
|---|---|
| 任务名 | `DSH-Client-Dev` |
| `State` | **Running** |
| `LastRunTime` | `2026/9/30 10:44:41` |
| `LastTaskResult` | `267009`(`0x41301` = **"任务正在运行中"**,非错误) |
| `NumberOfMissedRuns` | `0` |
| 动作 | `C:\windows\System32\cmd.exe /c "…\.workbuddy\_devkit\wb-shim-host.cmd"`(`entryInAction=是`) |
| 触发器 | `LogonTrigger Enabled=true`(**重启后自行恢复**) |
---
## 3 问题落点 —— 泳道图(角色 × 阶段)
```mermaid
flowchart TB
subgraph R1["角色① 手机(真壳 APK · 真 WebView)"]
A1["fetch {P}/api/v1/*"]
end
subgraph R2["角色② 平台入口(五道闸)"]
A2["deviceWebDecision 全过<br/>→ proxyHttp 到 relay 端点"]
end
subgraph R3["角色③ 覆盖网络 relay"]
A3["worker 通道 accepted=[20090]<br/>state=up(他线,未动)"]
end
subgraph R4["角色④ 设备侧中继客户端"]
A4["计划任务 DSH-Overlay-Node-Dev<br/>Running(他线,⛔ 未动)"]
end
subgraph R5["角色⑤ 垫片 20090 ── 🔴 本棒的落点"]
A5["🔴 改前:随会话亡<br/>会话收口 ⇒ 20090 无监听 ⇒ 下游连不上<br/>⇒ 平台回 503 instance_starting"]
A6["✅ 改后:headless DSH host 由<b>计划任务</b>持有<br/>父链 svchost · 无 WorkBuddy 祖先<br/>20090 LISTENING + credential=inbound"]
end
subgraph R6["角色⑥ WorkBuddy 网关(口令来源)"]
A7["口令只在 WorkBuddy 进程 env 里<br/>(磁盘无持久副本)"]
A8["✅ 主进程未动"]
end
A1 --> A2 --> A3 --> A4
A4 -->|"连 127.0.0.1:20090"| A5
A4 -->|"连 127.0.0.1:20090"| A6
A6 -->|"x-access-token(内存 inbound)"| A7
A7 --> A8
A5 -.->|"onRequest 钩子放行后上游 503"| A1
style A5 fill:#7f1d1d,stroke:#ef4444,color:#fff
style A6 fill:#14532d,stroke:#22c55e,color:#fff
style A7 fill:#7f1d1d,stroke:#ef4444,color:#fff
style A8 fill:#14532d,stroke:#22c55e,color:#fff
style A3 fill:#14532d,stroke:#22c55e,color:#fff
style A4 fill:#14532d,stroke:#22c55e,color:#fff
```
**问题格 = `A5`(红)**:卡在「角色⑤ 垫片」与「角色⑥ 口令来源」之间 ——
**垫片要活过会话就必须离开 WorkBuddy 沙箱;而离开沙箱就读不到 env 型口令**(09-29 已证这两件事在本机互斥)。
**本轮把它解掉的不是绕过这道张力,而是取消了它的前提**:A 方案给口令加了「**只绑回环的入站通道**」⇒ 垫片**不再需要 env 型口令**,于是"离开沙箱"与"拿得到口令"不再互斥。
---
## 4 事故链(改前怎么坏的 + 本轮怎么接上的)
```mermaid
flowchart LR
S0["T0 会话内拉起垫片<br/>20090 LISTENING · health=200"] --> S1["会话收口"]
S1 --> S2["<b>垫片随会话亡</b><br/>20090 无监听"]
S2 --> S3["中继客户端连 127.0.0.1:20090 失败"]
S3 --> S4["平台 onRequest 钩子五道闸**全过**<br/>→ proxyHttp 下游连不上"]
S4 --> S5["replyUpstreamUnavailable()<br/>→ <b>503 {\"error\":\"instance_starting\"}</b>"]
S5 --> S6["手机端点恒 503"]
S7["09-29 尝试:计划任务直起客户端"] --> S8["✅ 独立进程成立<br/>父链 node←cmd←svchost"]
S8 --> S9["🔴 但走的是 Electron 壳入口<br/>+ 前置 fail-closed(env 无口令)⇒ rc=3"]
S9 --> S2
T1["V1 最后一跳:A 方案(口令入站通道)"] --> T2["✅ 口令不再必须来自 env"]
T2 --> T3["改走 <b>headless DSH host</b>(普通 node,无窗口)"] --> T4["由计划任务持有<br/>父链 svchost · 无 WorkBuddy 祖先"]
T4 --> T5["20090 LISTENING + credential=inbound<br/>health=200"]
T5 --> T6["中继客户端可连上 20090"]
D1["本该拦住的防线 A:<br/>垫片常驻 / 自恢复"] -.改前缺席.-> S2
D2["本该拦住的防线 C:<br/>掉线巡检与报警"] -.仍缺席.-> S3
style S2 fill:#7f1d1d,stroke:#ef4444,color:#fff
style S5 fill:#7f1d1d,stroke:#ef4444,color:#fff
style S9 fill:#78350f,stroke:#f59e0b,color:#fff
style T4 fill:#14532d,stroke:#22c55e,color:#fff
style T5 fill:#14532d,stroke:#22c55e,color:#fff
style D1 fill:#78350f,stroke:#f59e0b,color:#fff
style D2 fill:#78350f,stroke:#f59e0b,color:#fff
```
### 4.1 防线现状
| # | 防线 | 改前 | 改后 | 归属 |
|---|---|---|---|---|
| **A** | 垫片**常驻 / 自恢复** | ❌ 无 | ✅ **已就位**(计划任务 + 幂等闸 + 常驻监督 + 掉线自恢复) | 本线(本轮闭环) |
| **C** | worker / 垫片**掉线巡检与报警** | ❌ 无 | ❌ **仍无**(本入口的监督只覆盖"进程在不在",**不报警**) | 平台线 / 覆盖网络线(第 6 次并列登记,⛔ 本轮顺手不做) |
---
## 5 未做 / 未独立取证(如实登记,⛔ 不许当已完成读)
| # | 项 | 状态 | 原因 / 替代读数 |
|---|---|---|---|
| 1 | 判据 ④「平台设备入口不再是 503」**由本会话独立复跑** | ⛔ **未做** | 该枪需要**设备属主的门户会话 Cookie**(属"需用户提供凭据",⛔ 本会话不代取);且本机**当前无 443 夹具在跑**(`netstat` 全表无 443 LISTENING)⇒ 交接单给的 `curl -k --resolve ai1net.com:443:127.0.0.1` 复现姿势**此刻不可用**。⚠️ **不凭第三方凭据、不探测登录接口**(登录接口对查无账号会 `auto_register`,曾在生产库建号,已登记为红线级坑)。**替代读数**:经垫片打**平台入口代理的同一路径** `/api/v1/info` → **200**(见 §2.4)。**旁证**:主会话 10:46 的 `acceptance_state` 记录已把该枪判为"全 200"并据此置 `V1=pass`。 |
| 2 | **"跨会话存活"自证** | ⛔ **未做** | 本棒是自动化会话,**收口即结束**,无法自测"我走之后它还在不在"。**替代证据**:父链已证**结构上独立于任何会话**(`svchost` 祖先、`workbuddyAncestor=null`)+ ①幂等复跑不新增进程;真正的"过夜"读数需要下一棒只读复核。 |
| 3 | 口令投递的**时效性** | ⚠️ 说明 | 口令由**宿主侧钩子**节流 60 s 投递(A 方案设计,⛔ 未改)。整机**无任何活动**(钩子不响)时,垫片会先回 `503 gateway-token-unavailable`,下一次工具调用/心跳即自愈。⇒ 这是 A 方案的**已知特性**,不是本轮的缺陷。 |
| 4 | 防线 C(掉线巡检报警) | ⛔ **未做** | 白名单外,按纪律只登记(§4.1)。 |
| 5 | Electron UI 起法 | ⛔ **未起** | 按交接单要求绕开(会弹窗口、且非 headless 所需)。旧入口 `wb-client-persist.cmd` 未删,任务动作已改指 ⇒ 见 §7 回滚。 |
---
## 6 两条**口径更正** + 两条新坑(登记)
### 6.1 🔴 口径更正:「`503 instance_starting`」**不是"平台实例在启动"**
平台仓 `src/supervisor/proxy.ts:262` 的 `replyUpstreamUnavailable()` —— 对**非导航**请求回 `503 {"error":"instance_starting"}`,而它被**`proxyHttp` 的"下游连不上/重试耗尽"**路径调用(`:588` / `:599`);而设备入口 `src/web/routes/device-web.ts:184` **正是调同一个 `proxyHttp`**。
⇒ **该响应体的真实语义 = 「闸后面那一跳连不上」**,与"实例正在启动"**无关**。
⇒ 与 09-29/09-30 的取证一致:**五道闸全过、`instance_starting` 出自闸后** ⇒ 所以**把 20090 弄活就是正解**。(建议手机接入线按此更正表述。)
### 6.2 ⚠️ 半对的常见说法:「垫片能在没有口令的情况下先起来」
**要拆成两件事**(`device-access.js` 的 `apply()` 起始顺序):
- ✅ **口令缺失确实不阻止 `listen`** —— 只让每个请求回 `503 gateway-token-unavailable`;
- 🔴 但 `upstream=workbuddy` 模式是 **「先发现、后监听」且 fail-closed** ⇒ **网关发现失败就永不监听**(⛔ 不沿用上次端口、⛔ 不先占口)。
⇒ 判据必须**分成两条**:`20090 有没有 LISTENING`(发现成不成)/`credential.present`(口令就位没)。
### 6.3 新坑 · 子进程 env 的"同组键"必须**枚举删**
`electron_run_as_node` / 网关口令这类键,用**点号**读写会**假阴性** ⇒ 本入口一律 `for (const k of Object.keys(env)) if (k.toLowerCase() === …) delete env[k]`。
### 6.4 新坑 · 用 `Get-CimInstance … -match '<关键串>'` **数进程会被探针自己污染**
本轮首测 `count = 2`,第二个"命中"其实是**本次探针自己那条 PowerShell 进程**(其命令行里含 `dsh-desktop-host` 字样)。
⇒ 判"唯一性"必须看**父链**,⛔ 不能只看计数。
---
## 7 机制 · 可复跑 · 可停 · 可回滚
| 项 | 值 |
|---|---|
| **机制** | **Windows 计划任务 `DSH-Client-Dev`**(Task Scheduler 持有 ⇒ 独立于任何会话;`LogonTrigger` ⇒ 重启自恢复) |
| **host 起法** | **普通 node 直起** `apps/desktop-host`(⛔ 无 Electron ⇒ 不弹窗) |
| **入口** | `.workbuddy/_devkit/wb-shim-host.mjs`(`--check / --install / --query / --remove / --run / --no-supervise`,默认=常驻监督)+ `.cmd`(纯 ASCII 包装,计划任务动作指向它) |
| **任务 XML** | `.workbuddy/_devkit/_taskxml/DSH-Client-Dev.xml`(UTF-16LE + BOM;⛔ 不含任何秘密值) |
| **机器记录** | `.workbuddy/_devkit/_devlogs/shim-host-task.jsonl`(每次调用一条;🔴 不含口令,只有在场/长度/键名) |
| **host 日志** | `.workbuddy/_devkit/_devlogs/shim-host-<ts>.log`(含 host 内 cordis 日志桥的 `device-access` 行) |
| **只读体检** | `node …/wb-shim-host.mjs --check` |
| **立刻起一次** | `node …/wb-shim-host.mjs --run`(⚠️ `schtasks` 必须经脚本 spawn) |
| **一条命令停** | `node …/wb-shim-host.mjs --remove`(删任务)+ 按端口定位停 host(⛔ 绝不按进程名杀 `node.exe`) |
| **回滚到旧(Electron 壳)入口** | `node …/wb-client-persist.mjs --install`(会把任务动作改回 `wb-client-persist.cmd`) |
| **可静默性** | 计划任务实例非交互;垫片只绑回环;本入口不占会话(stdout 只落文件) |
| **口令纪律** | 任务定义 / XML / 记录文件里**都没有口令**;全程未读、未打印、未落盘 |
**复跑一条命令(判据 ①②③)**
```bash
node E:/ProgramData/AIProject/ai1net-dsh-desktop/.workbuddy/_devkit/wb-shim-host.mjs --check
netstat -ano | grep -E "TCP\s+127\.0\.0\.1:20090\s" | grep -i LISTENING # ① 唯一判据
curl -s --noproxy '*' http://127.0.0.1:20090/__device_access/selfcheck # ② present:true
curl -s --noproxy '*' -o /dev/null -w '%{http_code}\n' http://127.0.0.1:20090/api/v1/health # ③ 期望 200
```
**判据 ④ 的复跑(需**设备属主的门户会话**,⛔ 本线不代取)**
```bash
# 前提:把 <sid> 换成设备属主本人的门户会话 Cookie(⛔ 不要探测登录接口:查无账号会 auto_register)
curl -k -s -o /dev/null -w '%{http_code}\n' \
-H 'Cookie: sid=<sid>' \
'https://ai1net.com/u/bdf89014-c66f-4060-a2df-995d8c18a951/desk/d-bdf89014-c66f-4060-a2df-995d8c18a951-626dc095/api/v1/info'
# 期望:200(设备侧已自证:经垫片同路径 = 200)。⛔ 不要再期望 503 instance_starting。
```
---
## 8 边界自检(逐条)
- ✅ ⛔ **未改 A 方案的通道设计**(尤其**未去掉"探活通过才采纳"**);⛔ **未让口令落盘**(未改回 C 方案)。
- ✅ ⛔ **未动看板 `127.0.0.1:8788`** —— 收尾复核仍在听、pid 仍 `19160`。
- ✅ ⛔ **未动中继客户端 `DSH-Overlay-Node-Dev`** —— 收尾复核 `State=Running`(未重启、未改其定义);本轮只**只读**读过它的 XML 作模板。
- ✅ ⛔ **未起 Electron UI**、⛔ 未弹任何窗口。
- ✅ **权限只收窄**:垫片 `20090` 与 host 自带 web `19387` **都只绑 `127.0.0.1`**;未开任何对外口、未改防火墙。
- ✅ **未碰**官方树(`E:\github\dsh-desktop-0.1.7rc2` 只读;未改其任何文件)· 平台仓(`D:\github\dsh_shenxian` 只读)· 生产服务器 47。
- ✅ **未 commit / 未 push** 任何仓库;⛔ 未启用退役任务 `DSH-Worker-Dev`。
- ✅ **WorkBuddy 未受影响**:未 kill / 未 restart / 未改其配置;主进程 `WorkBuddy.exe#3176` 与网关 pid `56072` 全程在场。
- ✅ **口令全程未读、未打印、未落盘**(新记录文件只有"在场/长度/键名"这类非密信息)。
- ⚠️ 一处**如实登记**:`WorkBuddy.exe` 现为 **15 个进程**(09-29 记录的基线是 13)。本轮**未杀任何进程**,差值与轮内多会话/自动化并行有关,⛔ 不作因果判断。
---
## 9 本轮产出 / 复跑件
| 文件 | 用途 |
|---|---|
| `.workbuddy/_devkit/wb-shim-host.mjs` | **新增**:垫片的 **headless 常驻入口**(普通 node 直起 host + 计划任务管理 + 常驻监督 + 自恢复;幂等闸;⛔ 只打印口令的在场/长度/键名) |
| `.workbuddy/_devkit/wb-shim-host.cmd` | **新增**:计划任务动作的 ASCII 包装 |
| `.workbuddy/_devkit/_taskxml/DSH-Client-Dev.xml` | **更新**:任务动作改指新入口(UTF-16LE + BOM;⛔ 无秘密值) |
| `.workbuddy/_devkit/_devlogs/shim-host-task.jsonl` · `shim-host-*.log` | **新增**:机器记录 + host 日志(含"计划任务上下文 `envPresent=false`、父链 svchost"的铁证) |
| 计划任务 `DSH-Client-Dev` | **改指**(`State=Running`;登陆时触发) |
| `交付物/回报-桌面线-垫片常驻化-20260930.md` | **新增**(本件) |
| 技能 `dsh-local-env`(用户级) | **更新**:新增详情档 **§6c「headless 主机 + 计划任务常驻」**+ 主干 §1 一行指针(v1.0.2 → **v1.0.3**) |
**技能**:本轮加载 **`dsh-local-env`**(命中:任务正是"在 Windows 本机把官方 dsh 跑起来并取证")—— 其 §6b 覆盖了"opt-in 回环口",本轮把**起法与常驻**这一缺口补成 **§6c** 并回写主干。
另:`agent-operating-rules`(作业总规矩)与 `multi-session-collab`(抢锁/收口)按既有常驻纪律执行,未逐条加载。
---
## 10 给主会话的一句话
**V1 的最后一跳已打通**,且**起法已挂在 Windows 计划任务上**(不是任何会话)——
`20090` LISTENING + `credential.source=inbound / present=true` + 经垫片 `health=200` + **父链 `… ← svchost`、`workbuddyAncestor=null`** 四条齐了。
唯一**留给你的**是判据 ④ 那道"门户会话 Cookie"的枪(本线不代取凭据):设备侧同路径已自证 200,平台侧一旦复跑应不再是 `503 instance_starting`。