dsh-remote-agent 0.0.0-stage → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +700 -0
- package/CONTRIBUTING.md +358 -0
- package/KNOWN-ISSUES.md +601 -0
- package/LICENSE +21 -0
- package/README.en.md +353 -0
- package/README.md +317 -2
- package/SECURITY.md +348 -0
- package/bin/dsh-remote-relay.js +257 -0
- package/bin/dsh-remote-setup.js +440 -0
- package/bin//345/217/214/345/207/273/350/277/220/350/241/214-/351/205/215/347/275/256/345/220/221/345/257/274.cmd +28 -0
- package/cordis.patch.yml +87 -0
- package/deploy/relay-vps/Caddyfile +24 -0
- package/deploy/relay-vps/README.md +132 -0
- package/deploy/relay-vps/dsh-remote-relay.service +47 -0
- package/deploy/relay-vps/nginx.conf +49 -0
- package/deploy/relay-vps/relay.env.example +20 -0
- package/docs/dispatch-automation-eval.md +140 -0
- package/docs/screenshots/panel-main.png +0 -0
- package/docs/screenshots/panel-task-detail.png +0 -0
- package/docs/verification-agent-e2e.md +425 -0
- package/docs/verification-repos-routing-real.md +521 -0
- package/docs/verification-resume-real.md +254 -0
- package/docs/worker-setup-pitfalls.md +266 -0
- package/lib/artifacts.js +451 -0
- package/lib/client.js +1752 -0
- package/lib/e2e.js +304 -0
- package/lib/glob.js +225 -0
- package/lib/hub/audit-log.js +161 -0
- package/lib/hub/hub-util.js +114 -0
- package/lib/hub/index.js +500 -0
- package/lib/hub/node-registry.js +943 -0
- package/lib/hub/pull-manager.js +781 -0
- package/lib/hub/registry-handshake.js +403 -0
- package/lib/hub/registry-ingest.js +346 -0
- package/lib/hub/relay-link.js +228 -0
- package/lib/hub/subagent-provider.js +424 -0
- package/lib/hub/templates.js +693 -0
- package/lib/hub/tools.js +1059 -0
- package/lib/hub/ui-routes.js +856 -0
- package/lib/hub/views.js +351 -0
- package/lib/identity.js +271 -0
- package/lib/index.js +272 -0
- package/lib/node/artifact-service.js +231 -0
- package/lib/node/diff.js +124 -0
- package/lib/node/exec.js +536 -0
- package/lib/node/hub-pin.js +71 -0
- package/lib/node/index.js +1022 -0
- package/lib/node/repos.js +134 -0
- package/lib/node/resource.js +63 -0
- package/lib/node/runner.js +792 -0
- package/lib/node/task-engine.js +712 -0
- package/lib/node/task-store.js +273 -0
- package/lib/node/transport.js +763 -0
- package/lib/pairing.js +186 -0
- package/lib/protocol.js +414 -0
- package/lib/relay-config.js +317 -0
- package/lib/relay.js +732 -0
- package/lib/setup-routes.js +313 -0
- package/lib/setup.js +522 -0
- package/lib/ws.js +466 -0
- package/package.json +103 -4
- package/profiles/README.md +573 -0
- package/profiles/hub.cordis.yml +84 -0
- package/profiles/worker.cordis.yml +60 -0
- package/scripts/check-doc-drift.mjs +785 -0
- package/scripts/check-packaging.mjs +130 -0
- package/scripts/diag-inject.mjs +243 -0
- package/scripts/e2e-live-check.mjs +181 -0
- package/scripts/relay-init.ps1 +286 -0
- package/scripts/relay-init.sh +199 -0
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
# 真机验证:agent 模式全链路(2026-10-16)
|
|
2
|
+
|
|
3
|
+
> 对应 [KNOWN-ISSUES.md](../KNOWN-ISSUES.md) 第 8 节「仍未覆盖(需要两台真机)」的第一条:
|
|
4
|
+
> **agent 模式全链路** —— 副机需要真实 DSH 提供的 agent 服务(ctx 里的 `agentLoop`)。
|
|
5
|
+
> 本次是**第一次在真实双机环境**下跑通(并跑出问题)的记录。
|
|
6
|
+
|
|
7
|
+
## 结论速览
|
|
8
|
+
|
|
9
|
+
| 待验证项 | 结果 |
|
|
10
|
+
|---|---|
|
|
11
|
+
| 双机环境是否存在 | ✅ 存在(Tailscale 组网的两台真机) |
|
|
12
|
+
| 任务是否被接受 | ✅ 接受,`accepted=true`,状态机走完 queued → running → completed |
|
|
13
|
+
| 副机是否起独立 agent 会话 | ✅ 起了,`runner=in-process`,`sessionId=session-remote-<taskId>`,进度里出现 `agent-start` |
|
|
14
|
+
| 结果是否回传 | ⚠️ 回传了 handoff,但**内容是 LLM 传输错误**;根因在副机凭证,不在插件 |
|
|
15
|
+
| 失败时的完整报错 | ✅ 已捕获(见下),但**任务被标记成 completed** —— 这是一个真缺陷 |
|
|
16
|
+
|
|
17
|
+
**一句话**:agent 链路的**管道全通**(派单 → 独立会话 → handoff 回传 → hub 投影),
|
|
18
|
+
但**副机侧 LLM 调用失败被吞成了成功**。前者证明 KNOWN-ISSUES §8 的担忧(拿不到
|
|
19
|
+
`agentLoop`)在真机上**不成立**;后者是本次新发现的缺陷。
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## 1. 环境
|
|
24
|
+
|
|
25
|
+
| 角色 | 主机名 | 系统 | Node | DSH | 插件版本 | 接入方式 |
|
|
26
|
+
|---|---|---|---|---|---|---|
|
|
27
|
+
| hub(主电脑) | `desktop-obojm0b` | Windows | v24.18.0 | **0.2.0-rc.2** | **0.3.0** | — |
|
|
28
|
+
| worker(副电脑) | `newland` | Windows | — | — | **0.1.0** | `transportMode: direct` |
|
|
29
|
+
|
|
30
|
+
- 组网:Tailscale 直连(`100.86.48.54` ↔ `100.72.146.73`,`active; direct`)。
|
|
31
|
+
- hub 监听 `0.0.0.0:19387`,副机 `hubUrl: ws://100.86.48.54:19387/api/remote-agent/node`。
|
|
32
|
+
- 副机工作区 `roots: ['D:\work']`,`token: ''`(空 token,按"仅限可信内网"语义放行,与文档一致)。
|
|
33
|
+
- 副机配置里 `inject: ['tools', 'agentLoop', 'sessions']` —— **这正是 agent 模式必需的服务**。
|
|
34
|
+
|
|
35
|
+
> ⚠ **两端插件版本不一致(hub 0.3.0 / worker 0.1.0)**。协议层没有报
|
|
36
|
+
> `protocol-mismatch`,说明 v1 协议帧仍然兼容,但这属于非受控变量,记录在此。
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## 1.5 副机接入:配置向导
|
|
41
|
+
|
|
42
|
+
副机是用 `bin/dsh-remote-setup.js` 接入的(也支持 `bin/双击运行-配置向导.cmd` 图形入口)。
|
|
43
|
+
本次用非交互模式复验了生成逻辑,输出可直接并入副机 profile:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
node bin/dsh-remote-setup.js --role worker \
|
|
47
|
+
--hub-url ws://100.86.48.54:19387/api/remote-agent/node \
|
|
48
|
+
--node-id wizard-test --roots 'D:\work' --out wizard-worker.yml
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
生成结果:
|
|
52
|
+
|
|
53
|
+
```yaml
|
|
54
|
+
- id: remote-agent
|
|
55
|
+
name: dsh-remote-agent
|
|
56
|
+
inject: [] # ← 副机必须清空,否则永远 PENDING(生成器已替你填好)
|
|
57
|
+
config:
|
|
58
|
+
role: worker
|
|
59
|
+
nodeId: wizard-test
|
|
60
|
+
transportMode: direct
|
|
61
|
+
hubUrl: ws://100.86.48.54:19387/api/remote-agent/node
|
|
62
|
+
roots:
|
|
63
|
+
- D:\work
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
✅ 向导正确生成了 `inject: []` —— 这是副机侧的关键约束(worker 拿不到
|
|
67
|
+
`webServer`/`systemPrompt`,声明了会卡在 PENDING)。`id: remote-agent` 也与包内
|
|
68
|
+
`cordis.patch.yml` 同 id,不会造成 double-mount。
|
|
69
|
+
|
|
70
|
+
**hub 侧体检**(`--doctor`)在真实配置上跑出了三条可操作结论:
|
|
71
|
+
|
|
72
|
+
```
|
|
73
|
+
dsh-remote-agent 体检(role=hub)
|
|
74
|
+
✓ Node 版本 24.18.0
|
|
75
|
+
✗ nodeToken 已配置(副机入网口令)
|
|
76
|
+
↳ 不配置时直连端点不校验任何凭证 —— 仅限 Tailscale/同机等完全可信网络;公网部署必须配。
|
|
77
|
+
✗ 本机有公网地址但 token 为空
|
|
78
|
+
↳ 立即配置 nodeToken,或确认本机只暴露在可信内网
|
|
79
|
+
✓ uiPullRoot 已配置(面板拉取产物落地的目录)
|
|
80
|
+
✗ uiPullRoot 目录存在:D:\remote-artifacts
|
|
81
|
+
↳ 目录不存在 —— 先创建它
|
|
82
|
+
共 3 项需要处理。
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
> 这三条**都是真问题**,且正好印证了部署文档的取舍:
|
|
86
|
+
> ① 空 token 只在完全可信网络下安全(本机靠 Tailscale 组网,勉强符合,但
|
|
87
|
+
> `0.0.0.0:19387` 确实在监听所有网卡);
|
|
88
|
+
> ② `uiPullRoot` 指向的 `D:\remote-artifacts` **不存在** —— 面板的"拉取成品"
|
|
89
|
+
> 按钮会因目录不可用而失败。
|
|
90
|
+
>
|
|
91
|
+
> ⚠ 顺带发现一处**源码注释与实现不一致**(不是缺陷,但会误导):
|
|
92
|
+
> `bin/dsh-remote-setup.js` 的**文件头 JSDoc**(第 7 行)写的是
|
|
93
|
+
>
|
|
94
|
+
> ```
|
|
95
|
+
> node bin/dsh-remote-setup.js --doctor # 体检当前配置,只报问题 + 怎么修
|
|
96
|
+
> ```
|
|
97
|
+
>
|
|
98
|
+
> 但实现(`runDoctor(positionals)`,第 144 行)**要求必须带配置文件路径**:
|
|
99
|
+
> 不带位置参数时只打印 `用法:--doctor <cordis配置文件.yml>` 并以退出码 1 结束。
|
|
100
|
+
>
|
|
101
|
+
> 外部文档是对的(`README.md:83`、`README.en.md:96`、`CHANGELOG.md:218`
|
|
102
|
+
> 都写成 `--doctor 你的配置.yml`),**只有源码自己的 JSDoc 漏了参数**。
|
|
103
|
+
> 建议把那行注释补成 `--doctor <配置.yml>`。
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
## 2. 链路验证:exec 模式(对照组)
|
|
107
|
+
|
|
108
|
+
先跑一条 exec 任务确认基础链路,排除"根本连不上"这种平凡解释:
|
|
109
|
+
|
|
110
|
+
```
|
|
111
|
+
remote_task { node: "newland", mode: "exec", goal: "hostname; node -v; echo probe-ok" }
|
|
112
|
+
→ 已派单 t-mvav14t3e9o19 → newland(status=queued)
|
|
113
|
+
workspace: D:\work runner: in-process
|
|
114
|
+
remote_status { taskId: "t-mvav14t3e9o19" }
|
|
115
|
+
→ status=completed mode=exec accepted=true 耗时=1002ms
|
|
116
|
+
exit: code=0 timedOut=false
|
|
117
|
+
进度尾巴: started → exec: hostname; node -v; echo probe-ok
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
✅ 基础链路完全正常:派单、接受、执行、终态、进度上报、handoff 全通。
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
## 3. 链路验证:agent 模式(本节重点)
|
|
125
|
+
|
|
126
|
+
### 3.1 派单
|
|
127
|
+
|
|
128
|
+
```
|
|
129
|
+
remote_task {
|
|
130
|
+
node: "newland",
|
|
131
|
+
mode: "agent",
|
|
132
|
+
workspace: "D:\work",
|
|
133
|
+
goal: "请总结你本机的工作区:列出当前工作区根目录下有哪些文件与子目录(最多 20 条),
|
|
134
|
+
并说明这个工作区是做什么用的。把结论写成一段 100 字以内的中文摘要,直接作为你的回答返回。"
|
|
135
|
+
}
|
|
136
|
+
→ 已派单 t-mvav1chu4y21c → newland(status=queued)
|
|
137
|
+
workspace: D:\work runner: in-process
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
**任务被接受**:`accepted=true`,hub 返回 `runner: in-process` —— 说明副机侧
|
|
141
|
+
`selectRunner()` 选中了 `InProcessRunner`(而不是 headless 回退),
|
|
142
|
+
即 `ctx.get('agentLoop')` 与 `ctx.get('sessions')` 都**可用**。
|
|
143
|
+
|
|
144
|
+
### 3.2 副机起了独立 agent 会话
|
|
145
|
+
|
|
146
|
+
派单后立即查询,进度尾巴为:
|
|
147
|
+
|
|
148
|
+
```
|
|
149
|
+
- started
|
|
150
|
+
- agent-start
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
`agent-start` 是 `TaskEngine.runAgent()` 在调 `runner.run()` **之前**发的第一个
|
|
154
|
+
`task/progress` 帧,带 `runner: this.runner.kind`。它出现即证明:
|
|
155
|
+
|
|
156
|
+
1. 任务真的进了 `runAgent()` 分支(不是 `runExec()`);
|
|
157
|
+
2. `InProcessRunner.run()` 被调用,会话创建流程已启动。
|
|
158
|
+
|
|
159
|
+
随后从副机落盘的任务记录里确认了**会话 id 真的被创建并回写**:
|
|
160
|
+
|
|
161
|
+
```
|
|
162
|
+
status=completed
|
|
163
|
+
mode=agent
|
|
164
|
+
sessionId=session-remote-t-mvav1chu4y21c ← 独立会话真的建起来了
|
|
165
|
+
runner=in-process
|
|
166
|
+
exitCode=0
|
|
167
|
+
manifest={"total":0,"final":0,"intermediate":0,"other":0}
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
`sessionId` 形如 `session-remote-<taskId>`,来自 `agentLoop.createAgent()` 返回的
|
|
171
|
+
`agent.id`,经 `result.sessionId` 回写到 TaskStore。
|
|
172
|
+
|
|
173
|
+
> **这一条直接回答了 KNOWN-ISSUES §8 的担忧**:文档当时说"本机 spawn 的 worker
|
|
174
|
+
> 拿不到该服务(selectRunner 走 headless),agent 链路必须在两台都装了 DSH 的机器上
|
|
175
|
+
> 验证"。真机上 `selectRunner` **没有**走 headless,`InProcessRunner` 正常工作,
|
|
176
|
+
> 会话被真实创建。§8 的这条缺口**已关闭**。
|
|
177
|
+
|
|
178
|
+
### 3.3 结果回传 —— 以及失败时的完整报错
|
|
179
|
+
|
|
180
|
+
任务终态与交接卡:
|
|
181
|
+
|
|
182
|
+
```
|
|
183
|
+
remote_status { taskId: "t-mvav1chu4y21c" }
|
|
184
|
+
→ status=completed mode=agent accepted=true 耗时=15561ms
|
|
185
|
+
exit: code=0 timedOut=false
|
|
186
|
+
进度尾巴:
|
|
187
|
+
- started
|
|
188
|
+
- agent-start
|
|
189
|
+
- error: {"turn":1,"step":1,"error":{"message":"DeepSeek Messages transport failed",
|
|
190
|
+
"code":"TRANSPORT","name":"LlmError"},"agent":{"status":"running"}}
|
|
191
|
+
交接卡已就绪
|
|
192
|
+
|
|
193
|
+
remote_handoff { taskId: "t-mvav1chu4y21c" }
|
|
194
|
+
→ 状态:completed
|
|
195
|
+
模式:agent 运行器:in-process
|
|
196
|
+
耗时:16s
|
|
197
|
+
工作区:undefined
|
|
198
|
+
## 摘要
|
|
199
|
+
[agent 错误] {"turn":1,"step":1,"error":{"message":"DeepSeek Messages transport failed",
|
|
200
|
+
"code":"TRANSPORT","name":"LlmError"},"agent":{"status":"running"}}
|
|
201
|
+
## 产物清单(final 0 / intermediate 0)
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
**完整报错原文**(这是任务要求的"失败时的完整报错"):
|
|
205
|
+
|
|
206
|
+
```json
|
|
207
|
+
{
|
|
208
|
+
"turn": 1,
|
|
209
|
+
"step": 1,
|
|
210
|
+
"error": {
|
|
211
|
+
"message": "DeepSeek Messages transport failed",
|
|
212
|
+
"code": "TRANSPORT",
|
|
213
|
+
"name": "LlmError"
|
|
214
|
+
},
|
|
215
|
+
"agent": { "status": "running" }
|
|
216
|
+
}
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
### 3.4 根因定位:副机 LLM 凭证缺失(不是插件缺陷)
|
|
220
|
+
|
|
221
|
+
在副机上派 exec 任务做诊断:
|
|
222
|
+
|
|
223
|
+
```
|
|
224
|
+
=== dns/egress ===
|
|
225
|
+
api.deepseek.com ERR: 远程服务器返回错误: (401) 未经授权。 ← 到得了,但没凭证
|
|
226
|
+
baidu.com → 200 ← 出网正常
|
|
227
|
+
=== credentials keys (names only) ===
|
|
228
|
+
version: ← 凭证文件只有版本号,没有任何 key
|
|
229
|
+
=== env LLM keys ===
|
|
230
|
+
(空 —— 没有任何 DEEPSEEK/OPENAI/API_KEY 环境变量)
|
|
231
|
+
=== dsh profile ===
|
|
232
|
+
desktop
|
|
233
|
+
=== agent-default-model ===
|
|
234
|
+
provider: deepseek-account
|
|
235
|
+
model: deepseek-flash
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
**判定**:
|
|
239
|
+
|
|
240
|
+
- 出网正常(`baidu.com` 200),不是网络问题;
|
|
241
|
+
- `api.deepseek.com` 返回 **401** —— 请求到达了服务端,但**没有有效凭证**;
|
|
242
|
+
- 副机的 `.credentials.yaml` **只有 `version:` 一行**,环境变量里也没有任何 LLM key;
|
|
243
|
+
- 但副机 profile 的 `agent-default-model` 指向 `provider: deepseek-account`。
|
|
244
|
+
|
|
245
|
+
⇒ 副机的 `deepseek-account` provider **没有登录/没有凭证**,所以
|
|
246
|
+
`InProcessRunner` 一调 LLM 就拿到 `TRANSPORT / LlmError`。
|
|
247
|
+
|
|
248
|
+
**这是副机环境的配置问题,不是 dsh-remote-agent 的缺陷。** 链路的每一环
|
|
249
|
+
(派单、会话创建、错误捕获、handoff 回传)都按设计工作。
|
|
250
|
+
|
|
251
|
+
---
|
|
252
|
+
|
|
253
|
+
## 4. 本次新发现的缺陷:agent 侧 LLM 失败被标记为 completed
|
|
254
|
+
|
|
255
|
+
### 现象
|
|
256
|
+
|
|
257
|
+
副机的 LLM 调用**明确失败了**(`LlmError`),但:
|
|
258
|
+
|
|
259
|
+
| 观察点 | 实际值 | 期望 |
|
|
260
|
+
|---|---|---|
|
|
261
|
+
| 任务状态 | `completed` | `failed` |
|
|
262
|
+
| `exit.code` | `0` | 非 0 |
|
|
263
|
+
| `exit.timedOut` | `false` | — |
|
|
264
|
+
| `error` 字段(落盘) | **空** | 带 `LlmError` |
|
|
265
|
+
| handoff 摘要 | 一段 `[agent 错误] ...` 文本 | 明确的失败语义 |
|
|
266
|
+
|
|
267
|
+
### 根因(代码级)
|
|
268
|
+
|
|
269
|
+
`lib/node/runner.js` 的 `InProcessRunner.run()` 里,`agent/error` 事件只被
|
|
270
|
+
**记录成文本**,不影响返回值:
|
|
271
|
+
|
|
272
|
+
```js
|
|
273
|
+
// ctx.on('agent/error', ...) 内
|
|
274
|
+
liveText.push(`[agent 错误] ${msg}`)
|
|
275
|
+
if (typeof onEvent === 'function') onEvent({ kind: 'error', text: msg.slice(0, 2000) })
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
而返回值的 `ok` **只由 `timedOut` 决定**:
|
|
279
|
+
|
|
280
|
+
```js
|
|
281
|
+
return {
|
|
282
|
+
ok: !timedOut, // ← LLM 失败时 timedOut=false ⇒ ok=true
|
|
283
|
+
exitCode: timedOut ? null : 0,
|
|
284
|
+
timedOut,
|
|
285
|
+
final: finalText, // ← finalText 里混着 "[agent 错误] ..."
|
|
286
|
+
stderr: '',
|
|
287
|
+
...
|
|
288
|
+
}
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
然后 `lib/node/task-engine.js` 的状态映射:
|
|
292
|
+
|
|
293
|
+
```js
|
|
294
|
+
const status = result.aborted ? ABORTED
|
|
295
|
+
: result.timedOut ? INTERRUPTED
|
|
296
|
+
: result.ok ? COMPLETED // ← ok=true ⇒ COMPLETED
|
|
297
|
+
: FAILED
|
|
298
|
+
|
|
299
|
+
const failure = result.aborted || result.timedOut || result.ok
|
|
300
|
+
? null // ← ok=true ⇒ failure=null(error 字段为空)
|
|
301
|
+
: { code: 'task-failed', ... }
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
⇒ **一次彻底失败的 agent 调用,被投影成 `completed` + `exit.code=0` + `error=null`。**
|
|
305
|
+
|
|
306
|
+
### 对照证据:exec 侧的失败映射是**正确**的
|
|
307
|
+
|
|
308
|
+
同一次验证里,一条 exec 任务因为副机 PATH 上没有 `node` 而失败,映射完全正确:
|
|
309
|
+
|
|
310
|
+
```
|
|
311
|
+
remote_handoff { taskId: "t-mvavcgvbc902m" }
|
|
312
|
+
→ 状态:failed ← 正确落成 failed
|
|
313
|
+
模式:exec 运行器:in-process
|
|
314
|
+
## 摘要
|
|
315
|
+
任务未成功:node : The term 'node' is not recognized as the name of a cmdlet,
|
|
316
|
+
function, script file, or operable program. ...
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
对比表:
|
|
320
|
+
|
|
321
|
+
| 场景 | 实际终态 | 是否正确 |
|
|
322
|
+
|---|---|---|
|
|
323
|
+
| exec 命令失败(`node` 不在 PATH) | `failed` + 摘要含错误 | ✅ 正确 |
|
|
324
|
+
| agent 调 LLM 失败(`LlmError`) | `completed` + `exit.code=0` + `error=null` | ❌ **错误** |
|
|
325
|
+
|
|
326
|
+
⇒ 这说明**不是整条状态映射链坏了**,而是 agent 路径**独有**的一个漏洞:
|
|
327
|
+
`runExec()` 走 `exec.js`,命令非 0 退出会让 `result.ok=false` → `FAILED`;
|
|
328
|
+
而 `InProcessRunner.run()` 的 `ok` 只由 `timedOut` 决定,agent 级错误不参与判定。
|
|
329
|
+
**修复范围因此很窄** —— 只需改 `runner.js` 的 `InProcessRunner` 返回值,不必动
|
|
330
|
+
`task-engine.js` 的状态映射(它已经在正确工作了)。
|
|
331
|
+
|
|
332
|
+
### 影响
|
|
333
|
+
|
|
334
|
+
这是最坏的一类失败形态:**调用方无从判断任务是否真的做成了**。
|
|
335
|
+
`remote_handoff` 的摘要里虽然有 `[agent 错误]` 字样,但:
|
|
336
|
+
|
|
337
|
+
- `remote_status` 的 `status=completed` 会让自动化流程直接认为成功;
|
|
338
|
+
- `exit.code=0` 会让基于退出码的判断失效;
|
|
339
|
+
- `error` 为空,`remote_manifest` 也会给出"没有产物"这种中性结论。
|
|
340
|
+
|
|
341
|
+
与仓库自己在 `lib/hub/tools.js` 里反复强调的原则相悖 —— 那里花了大段注释
|
|
342
|
+
避免"有副作用却报失败",这里是镜像问题:**没做成却报成功**。
|
|
343
|
+
|
|
344
|
+
### 建议修法(未实施,仅记录)
|
|
345
|
+
|
|
346
|
+
在 `InProcessRunner.run()` 里把"捕获到 agent 级错误"提升为一等结果:
|
|
347
|
+
|
|
348
|
+
1. `agent/error` 回调里置一个 `sawAgentError = true`(并保留 `msg`);
|
|
349
|
+
2. 返回时 `ok: !timedOut && !sawAgentError`,并给出 `exitCode: 1`;
|
|
350
|
+
3. 把 `msg` 放进 `stderr`(而不是只塞进 `final`),让 `task-engine` 现有的
|
|
351
|
+
`failure = { code: 'task-failed', message: result.stderr }` 分支自然生效;
|
|
352
|
+
4. 补一条测试:mock `agent/error` → 断言任务终态为 `failed` 且 `error` 非空。
|
|
353
|
+
|
|
354
|
+
> 注意别把"agent 报了个非致命 warning"也判成失败 —— 需要区分错误级别。
|
|
355
|
+
> `agent/error` 事件本身语义就是错误,作为失败信号是安全的。
|
|
356
|
+
|
|
357
|
+
---
|
|
358
|
+
|
|
359
|
+
## 5. 顺带发现:hub 侧 `remote_nodes` 因热更新而失败(环境态,非缺陷)
|
|
360
|
+
|
|
361
|
+
验证开始时,`remote_nodes` 工具**整个调用失败**:
|
|
362
|
+
|
|
363
|
+
```
|
|
364
|
+
Error: tool "remote_nodes" returned invalid output:
|
|
365
|
+
"value.nodes[0].resource" must be an object
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
根因不是代码,而是**运行中的 DSH 持有旧模块**:
|
|
369
|
+
|
|
370
|
+
| 项 | 时间 |
|
|
371
|
+
|---|---|
|
|
372
|
+
| DSH 桌面进程启动 | `2026-10-16 18:53:00` |
|
|
373
|
+
| `tools.js` 被修好(含数组递归修复) | `2026-10-16 18:58:49` |
|
|
374
|
+
| 该修复的提交 | `24cae76 修 dropNullForSchema 不递归(工具返回值被 schema 拒)` |
|
|
375
|
+
|
|
376
|
+
`dropNullForSchema` 的数组分支是 **18:58:49** 才补上的,而 hub 进程
|
|
377
|
+
**18:53:00** 就起来了 —— 进程内还是旧版 `tools.js`,所以 `nodes[].resource`
|
|
378
|
+
的 `null` 没被剥掉。仓库工作区的文件与安装副本的 sha256 **完全一致**,
|
|
379
|
+
确认磁盘上已经是修好的版本。
|
|
380
|
+
|
|
381
|
+
⇒ **结论:重启 DSH 桌面版即消失**,属"热更新后未重启"的环境态。
|
|
382
|
+
记录在此,是为了避免下一个人把它当成真缺陷去查。
|
|
383
|
+
|
|
384
|
+
(另:`remote_nodes` 失败不影响其他六个工具 —— `remote_task` / `remote_status` /
|
|
385
|
+
`remote_handoff` 全程正常,本次验证就是靠它们完成的。)
|
|
386
|
+
|
|
387
|
+
---
|
|
388
|
+
|
|
389
|
+
## 6. 复现步骤
|
|
390
|
+
|
|
391
|
+
```powershell
|
|
392
|
+
# 前提:两台都装 DSH 的真机,副机配好 worker 并拨入 hub
|
|
393
|
+
|
|
394
|
+
# 1) 确认副机在线(hub 侧)
|
|
395
|
+
remote_nodes # 若报 schema 错,先重启 DSH 桌面版
|
|
396
|
+
# 2) exec 对照组
|
|
397
|
+
remote_task { node: "newland", mode: "exec", goal: "hostname; node -v" }
|
|
398
|
+
remote_status { taskId: "<上一步返回>" }
|
|
399
|
+
# 3) agent 模式
|
|
400
|
+
remote_task { node: "newland", mode: "agent", workspace: "D:\work",
|
|
401
|
+
goal: "总结你的工作区并给出 100 字摘要" }
|
|
402
|
+
remote_status { taskId: "<上一步返回>" } # 看 progressTail 里有没有 agent-start
|
|
403
|
+
remote_handoff { taskId: "<上一步返回>" } # 看摘要与产物清单
|
|
404
|
+
|
|
405
|
+
# 4) 副机侧核对会话是否真建起来
|
|
406
|
+
# 在副机上读 ~/.dsh-remote-agent/tasks/<taskId>.json
|
|
407
|
+
# 期望看到 sessionId=session-remote-<taskId>、runner=in-process
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
判定标准:
|
|
411
|
+
|
|
412
|
+
- 进度里出现 `agent-start` + 副机任务记录里有 `sessionId` ⇒ **独立会话已创建**;
|
|
413
|
+
- 摘要非 `[agent 错误]` 且产物清单符合预期 ⇒ **全链路成功**;
|
|
414
|
+
- 摘要含 `[agent 错误]` 但 `status=completed` ⇒ **命中 §4 缺陷**,先查副机凭证。
|
|
415
|
+
|
|
416
|
+
---
|
|
417
|
+
|
|
418
|
+
## 7. 待办
|
|
419
|
+
|
|
420
|
+
| # | 事项 | 优先级 |
|
|
421
|
+
|---|---|---|
|
|
422
|
+
| 1 | 修 §4:agent 级错误应把任务落成 `failed`(含测试) | 高 |
|
|
423
|
+
| 2 | 副机补 LLM 凭证后重跑,确认 agent 模式**成功**路径(本次只验到失败路径) | 高 |
|
|
424
|
+
| 3 | 统一两端插件版本(hub 0.3.0 / worker 0.1.0),排除版本偏斜 | 中 |
|
|
425
|
+
| 4 | 在副机登录后,复验 `remote_task(mode:'agent', model:{...})` 的模型选择链路 | 中 |
|