dsh-qiyu-stack 0.15.4 → 0.15.5
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/docs/status.md +29 -1
- package/package.json +1 -1
- package/src/doctor.mjs +28 -1
- package/src/qr.mjs +7 -10
package/docs/status.md
CHANGED
|
@@ -6,10 +6,38 @@
|
|
|
6
6
|
|
|
7
7
|
**能用了,也发出去了,但还没被陌生人在干净机器上验证过。**
|
|
8
8
|
|
|
9
|
-
- npm:`dsh-qiyu-stack@0.15.
|
|
9
|
+
- npm:`dsh-qiyu-stack@0.15.5`(`latest`)
|
|
10
10
|
- GitHub:<https://github.com/aesfls/dsh-qiyu-stack>(公开,main)
|
|
11
11
|
- 千语(爸爸本人的实例):正常运行中,桥、NapCat、Docker 都通
|
|
12
12
|
|
|
13
|
+
## 2026-10-23 第四轮:验收命令也补上沙箱降级(v0.15.5)
|
|
14
|
+
|
|
15
|
+
第三轮只修了 `preflight`,**验收命令 `doctor` 漏了**——在 workspace-write 下它照样报
|
|
16
|
+
「装了 Docker,但引擎没起来」,`有失败: true`。而 AGENTS.md 写的验收标准是「不通必须为 0」,
|
|
17
|
+
陌生人跑验收会永远看到 1 个失败。0.15.5 补上:
|
|
18
|
+
|
|
19
|
+
1. `doctor` 接 `沙箱受限过()`:Docker / 容器 / 端口相关的 fail 降级成 `warn`,
|
|
20
|
+
标题加「沙箱限制,这条不可信」,并附一句「去用户终端或提权再验」
|
|
21
|
+
2. `qr.mjs` 的 `docker cp` 改走 `跑()`(原来直连 `execFileSync`,沙箱里会假失败「拿不到二维码」)
|
|
22
|
+
|
|
23
|
+
## 2026-10-23 第三轮:沙箱假阴性(重要)
|
|
24
|
+
|
|
25
|
+
另一个会话在**默认沙箱**(workspace-write)里实测发现:`preflight` 报「这台机器上没有 Docker」,
|
|
26
|
+
可机器上 Docker 装着、容器还在跑。
|
|
27
|
+
|
|
28
|
+
**根因**(平台文档写明):受限沙箱不允许程序开命名管道,所以任何
|
|
29
|
+
`execFile/spawn` 抓输出(`stdio: 'pipe'`)都会 `EPERM`。而 qiyu 的每个 `跑()` 都靠抓输出,
|
|
30
|
+
于是所有判断变成假阴性——这是 agent 路径上最危险的一类 bug,会让 agent 去让用户装一个已经装好的东西。
|
|
31
|
+
|
|
32
|
+
**修法(v0.15.4)**:
|
|
33
|
+
|
|
34
|
+
1. 新增 `src/run.mjs`:先试管道,`EPERM/EACCES/EPIPE` 时**改走「重定向到临时文件再读」**
|
|
35
|
+
(沙箱允许写工作区),并把这次运行标记为「沙箱受限过」
|
|
36
|
+
2. `preflight` 在受限时**把 Docker/端口/Python 这类 fail 降级成 `warn`**,
|
|
37
|
+
标题加「沙箱限制,这条不可信」,并明确告诉 agent:别让用户重装,去他自己的终端或提权再验
|
|
38
|
+
3. `AGENTS.md` 专门加了一节讲这个坑
|
|
39
|
+
4. `AGENTS.md` 已进 npm 包(`files` 字段,0.15.4 起 80 个文件)
|
|
40
|
+
|
|
13
41
|
## 2026-10-23 第二轮补的(agent 入口)
|
|
14
42
|
|
|
15
43
|
一个陌生人会话实测后提的问题,采纳了三条:
|
package/package.json
CHANGED
package/src/doctor.mjs
CHANGED
|
@@ -9,7 +9,7 @@ import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs'
|
|
|
9
9
|
import { join, dirname, resolve } from 'node:path'
|
|
10
10
|
import { fileURLToPath } from 'node:url'
|
|
11
11
|
import { 读配置 } from './config.mjs'
|
|
12
|
-
import {
|
|
12
|
+
import { 跑, 沙箱受限过 } from './run.mjs'
|
|
13
13
|
|
|
14
14
|
const 包根 = join(dirname(fileURLToPath(import.meta.url)), '..')
|
|
15
15
|
|
|
@@ -284,6 +284,33 @@ export async function doctor({ 只输出JSON = false, 跳过WebSocket = false }
|
|
|
284
284
|
记('skip', '跳过模板体检', String(错.message || 错))
|
|
285
285
|
}
|
|
286
286
|
|
|
287
|
+
// ---------- 沙箱兜底:读不到子进程输出时,别把「没装/没起」当成事实 ----------
|
|
288
|
+
//
|
|
289
|
+
// 受限沙箱不允许程序开管道,所有"跑条命令看输出"的判断都可能拿到空结果。
|
|
290
|
+
// 这时把 Docker / 容器 / 端口相关的 fail 降级成 warn——否则 agent 会把
|
|
291
|
+
// 「验收标准:不通必须为 0」理解成"装失败了",去修一台根本没坏的机器。
|
|
292
|
+
if (沙箱受限过()) {
|
|
293
|
+
let 降级了 = 0
|
|
294
|
+
for (const x of 结果) {
|
|
295
|
+
if (x.级别 !== 'fail') continue
|
|
296
|
+
if (!/Docker|引擎|容器|端口/.test(x.标题)) continue
|
|
297
|
+
x.级别 = 'warn'
|
|
298
|
+
x.标题 = '沙箱限制,这条不可信:' + x.标题
|
|
299
|
+
x.怎么修 = '在用户自己的终端里跑 qiyu doctor 再确认一次;或者把这次会话提到 danger-full-access'
|
|
300
|
+
降级了++
|
|
301
|
+
}
|
|
302
|
+
if (降级了) {
|
|
303
|
+
结果.push({
|
|
304
|
+
级别: 'warn',
|
|
305
|
+
标题: '当前沙箱读不到子进程输出',
|
|
306
|
+
依据:
|
|
307
|
+
'受限沙箱不允许程序开管道(stdio: pipe 会 EPERM)。qiyu 已改用"重定向到文件再读"兜底,' +
|
|
308
|
+
'但个别命令仍可能失败,上面 ' + 降级了 + ' 条结论已降级。',
|
|
309
|
+
怎么修: '这类结论不能当真:让用户在自己终端跑一遍,或把会话权限提到 danger-full-access',
|
|
310
|
+
})
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
287
314
|
// ---------- 输出 ----------
|
|
288
315
|
const 有失败 = 结果.some((x) => x.级别 === 'fail')
|
|
289
316
|
|
package/src/qr.mjs
CHANGED
|
@@ -2,10 +2,11 @@
|
|
|
2
2
|
//
|
|
3
3
|
// 约定:需要用户扫码时,不能只给一个路径(用户还得自己去找文件)。
|
|
4
4
|
// 一律「复制出来 + 用系统默认看图程序打开 + 在终端里说清什么时候过期」。
|
|
5
|
-
import {
|
|
5
|
+
import { spawn } from 'node:child_process'
|
|
6
6
|
import { resolve } from 'node:path'
|
|
7
7
|
import { statSync } from 'node:fs'
|
|
8
8
|
import { 读配置 } from './config.mjs'
|
|
9
|
+
import { 跑 } from './run.mjs'
|
|
9
10
|
|
|
10
11
|
export function 打开文件(路径) {
|
|
11
12
|
try {
|
|
@@ -47,15 +48,11 @@ export async function qr() {
|
|
|
47
48
|
|
|
48
49
|
const 目标 = resolve(process.cwd(), 'napcat-qrcode.png')
|
|
49
50
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
56
|
-
})
|
|
57
|
-
} catch (错) {
|
|
58
|
-
console.error('拿不到二维码:' + String((错 && (错.stderr || 错.message)) || 错).trim().slice(0, 200))
|
|
51
|
+
// 走 run.mjs:受限沙箱里 execFileSync 抓管道会 EPERM,
|
|
52
|
+
// 它会自动改成「重定向到临时文件再读」,不然会误报「拿不到二维码」。
|
|
53
|
+
const 复制 = 跑('docker', ['cp', `${配置.napcat.container}:/app/napcat/cache/qrcode.png`, 目标], 15000)
|
|
54
|
+
if (!复制.ok) {
|
|
55
|
+
console.error('拿不到二维码:' + String(复制.错 || 'docker cp 失败').trim().slice(0, 200))
|
|
59
56
|
console.error('')
|
|
60
57
|
console.error('可能的原因:')
|
|
61
58
|
console.error(' · 容器没起 —— 跑 qiyu doctor 看看')
|