- 变更规模:新增 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/ 知识文件,按口径入库)
334 lines
23 KiB
Markdown
334 lines
23 KiB
Markdown
# 回报 · 桌面线:设备接入垫片**常驻化**(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`。
|