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

23 KiB
Raw Permalink Blame History

回报 · 桌面线:设备接入垫片常驻化(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 那条(原文,节选):

{"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 垫片自检(原文)

{"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 问题落点 —— 泳道图(角色 × 阶段)

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 事故链(改前怎么坏的 + 本轮怎么接上的)

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 / 记录文件里都没有口令;全程未读、未打印、未落盘

复跑一条命令(判据 ①②③)

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

判据 ④ 的复跑(需设备属主的门户会话**,⛔ 本线不代取)**

# 前提:把 <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。