dsh-boot-suite 2.13.11 → 2.13.12

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 CHANGED
@@ -1,3 +1,23 @@
1
+ ## [2.13.12] — 2026-10-29
2
+
3
+ ### 修复:更新时「浏览器下载监听」过早超时、找不到下载文件
4
+ - 症状:用户点「立即更新」后浏览器开始下载,插件等 10 分钟没接到文件就超时;实际网络慢时 24MB 的包可能要十几分钟,下载完成后也没人接住。
5
+ - 2.13.12:等待时间 10 分钟 → **30 分钟**;文件名识别放宽(兼容 `dsh-boot-suite-win-V2.13.12 (1).zip` 等浏览器改名);超时文案改为引导用户直接找包。
6
+
7
+ ### 新增:更新等待期间的手动出口
8
+ - 等待下载时新增「**打开下载文件夹**」按钮,一键打开系统 Downloads;「选择安装包」在等待期间也可用,下载好的 zip 直接选中即可继续。
9
+ - 手动选包成功后,后台还在跑的下载监听会自动作废,不会弹第二次更新器。
10
+ - 等待期间页面显示明确的「正在等下载(最多 30 分钟)」提示。
11
+
12
+ ### 修复:「生成诊断报告」按钮连点会生成一堆文件
13
+ - 症状:按钮点击后没有“生成中”反馈,用户连点几次,桌面出现多份诊断报告。
14
+ - 2.13.12:按钮生成期间禁用并显示「生成中…」;宿主侧同一时间只允许生成一份,重复请求直接返回“正在生成中”,不再刷屏。
15
+
16
+ ### 构建
17
+ - 版本号 2.13.11 → 2.13.12;client BUILD `cw-35`;全套测试通过。
18
+
19
+ ---
20
+
1
21
  ## [2.13.11] — 2026-10-28
2
22
 
3
23
  ### 新增:纱的「镜头模糊」
package/lib/client.js CHANGED
@@ -265,7 +265,9 @@ window.__ModuleLoader__.load({
265
265
  —— 桌面端走浏览器半侧自举时没有 1,就靠这一条
266
266
  3) 下面这个常量:两份都没拿到时的兜底,别让面板显示 undefined
267
267
  版本号本身只在 lib/index.js 里读一次,这里不重复维护。 */
268
- var FALLBACK_VERSION = '2.13.11'
268
+ var FALLBACK_VERSION = '2.13.12'
269
+ /* 手动选包成功后置 true:让还在后台跑的“浏览器下载监听”不要再弹第二次更新器 */
270
+ var __manualUpdateApplied = false
269
271
  function readInjectedVersion() {
270
272
  try {
271
273
  var v = window.__DSH_STARTUP_VERSION__
@@ -287,6 +289,8 @@ window.__ModuleLoader__.load({
287
289
  var [checking, setChecking] = react.useState(false)
288
290
  var [updating, setUpdating] = react.useState(false)
289
291
  var [updateMsg, setUpdateMsg] = react.useState('')
292
+ var [waitingDownload, setWaitingDownload] = react.useState(false)
293
+ var [diagnosing, setDiagnosing] = react.useState(false)
290
294
 
291
295
  function load() {
292
296
  setError(null)
@@ -357,18 +361,23 @@ window.__ModuleLoader__.load({
357
361
  .then(() => setBusy(false))
358
362
  }
359
363
 
360
- /** 一键诊断:让宿主写一份报告到桌面,用户直接发给作者。 */
364
+ /** 一键诊断:让宿主写一份报告到桌面,用户直接发给作者。生成中按钮禁用,防止连点刷屏。 */
361
365
  function runDiagnose() {
362
- setNotice(null)
366
+ if (diagnosing) return
367
+ setDiagnosing(true)
368
+ setNotice('正在生成诊断报告,请稍等…')
369
+ setError(null)
363
370
  fetch(STARTUP_ROUTE + '/diagnose', { method: 'POST' })
364
371
  .then(function (r) { return r.json() })
365
372
  .then(function (j) {
366
373
  if (!j || j.ok !== true) throw new Error((j && j.error) || '生成失败')
367
- setNotice('诊断报告已生成到桌面:' + j.file)
374
+ setNotice('诊断报告已生成到桌面:' + j.file + '(报告里不含任何密钥/密码)')
368
375
  })
369
376
  .catch(function (e) {
370
- setNotice('生成诊断报告失败:' + String((e && e.message) || e))
377
+ setNotice(null)
378
+ setError('生成诊断报告失败:' + String((e && e.message) || e))
371
379
  })
380
+ .then(function () { setDiagnosing(false) })
372
381
  }
373
382
 
374
383
  /** 检查更新:问宿主(宿主去读 GitHub latest release,避开浏览器 CORS)。 */
@@ -409,27 +418,44 @@ window.__ModuleLoader__.load({
409
418
 
410
419
  /** 立即更新:让系统浏览器下载(走浏览器自己的代理),插件监听 Downloads 自动接住 */
411
420
  function runUpdate() {
412
- if (!update || !update.latest) return
421
+ if (!update || !update.latest || updating) return
422
+ __manualUpdateApplied = false
413
423
  setUpdating(true)
424
+ setWaitingDownload(true)
414
425
  setUpdateMsg('正在让浏览器下载 v' + update.latest + '(使用浏览器自己的代理)…')
415
426
  fetch(STARTUP_ROUTE + '/update-browser-download', { method: 'POST' })
416
427
  .then(function (r) { return r.json() })
417
428
  .then(function (j) {
418
429
  if (!j || j.ok !== true) throw new Error((j && j.error) || '无法启动浏览器下载')
419
- setUpdateMsg('已用 ' + (j.browser === 'chrome' ? 'Chrome' : '默认浏览器') + ' 打开下载,完成后会自动继续(最多等 10 分钟)…')
430
+ setUpdateMsg('已用 ' + (j.browser === 'chrome' ? 'Chrome' : '默认浏览器') + ' 打开下载,完成后会自动继续(最多等 30 分钟)。等太久可以点「打开下载文件夹」或「选择安装包」。')
420
431
  return fetch(STARTUP_ROUTE + '/update-watch-download?version=' + encodeURIComponent(update.latest))
421
432
  })
422
433
  .then(function (r) { return r.json() })
423
434
  .then(function (j) {
435
+ setWaitingDownload(false)
436
+ if (__manualUpdateApplied) return
424
437
  if (!j || j.ok !== true) throw new Error((j && j.error) || '没有等到下载文件')
425
438
  setUpdating(false)
426
439
  setUpdateMsg('已获取 v' + j.version + '(' + Math.round(j.size / 1048576) + ' MB)')
427
440
  confirmAndApply(j.version)
428
441
  })
429
442
  .catch(function (e) {
443
+ setWaitingDownload(false)
444
+ if (__manualUpdateApplied) return
430
445
  setUpdating(false)
431
- setUpdateMsg('浏览器下载没接住:' + String((e && e.message) || e) + '。可再点「立即更新」,或点「选择安装包」手动指定。')
446
+ setUpdateMsg('浏览器下载没接住:' + String((e && e.message) || e) + '。可以点「打开下载文件夹」找到 zip 后点「选择安装包」。')
447
+ })
448
+ }
449
+
450
+ /** 打开系统下载文件夹(等下载时的手动出口) */
451
+ function openDownloads() {
452
+ fetch(STARTUP_ROUTE + '/open-downloads', { method: 'POST' })
453
+ .then(function (r) { return r.json() })
454
+ .then(function (j) {
455
+ if (!j || j.ok !== true) throw new Error((j && j.error) || '打不开文件夹')
456
+ setUpdateMsg('已打开下载文件夹:' + j.dir + '。找到 dsh-boot-suite 的 zip 后,点「选择安装包」选它。')
432
457
  })
458
+ .catch(function (e) { setUpdateMsg('打开下载文件夹失败:' + String((e && e.message) || e)) })
433
459
  }
434
460
 
435
461
  /** 浏览器下载好 zip 后,从本地选择文件(完全不依赖宿主联网) */
@@ -454,6 +480,8 @@ window.__ModuleLoader__.load({
454
480
  .then(function (r) { return r.json() })
455
481
  .then(function (j) {
456
482
  if (!j || j.ok !== true) throw new Error((j && j.error) || '本地安装包无效')
483
+ __manualUpdateApplied = true
484
+ setWaitingDownload(false)
457
485
  setUpdating(false)
458
486
  setUpdateMsg('已载入本地安装包 v' + j.version + '(' + Math.round(j.size / 1048576) + ' MB)')
459
487
  confirmAndApply(j.version)
@@ -1017,7 +1045,7 @@ window.__ModuleLoader__.load({
1017
1045
  /* 版本与更新 */
1018
1046
  h('div', { className: '__ss_row', style: { marginTop: '10px' } },
1019
1047
  h('button', { className: '__ss_btn', disabled: checking, onClick: checkUpdate }, checking ? '检查中…' : '检查更新'),
1020
- h('button', { className: '__ss_btn', onClick: runDiagnose }, '生成诊断报告'),
1048
+ h('button', { className: '__ss_btn', disabled: diagnosing, onClick: runDiagnose }, diagnosing ? '生成中…' : '生成诊断报告'),
1021
1049
  h('span', {
1022
1050
  className: (update && update.ok && update.hasUpdate) ? '__ss_ok' : '__ss_hint'
1023
1051
  }, (function () {
@@ -1034,12 +1062,19 @@ window.__ModuleLoader__.load({
1034
1062
  ? h('button', { className: '__ss_btn __ss_btnPrimary', disabled: updating, onClick: runUpdate }, updating ? '更新中…' : '立即更新')
1035
1063
  : null,
1036
1064
  (update && update.ok && update.hasUpdate)
1037
- ? h('button', { className: '__ss_btn', disabled: updating, onClick: pickLocalPackage }, '选择安装包')
1065
+ ? h('button', { className: '__ss_btn', disabled: updating && !waitingDownload, onClick: pickLocalPackage }, '选择安装包')
1066
+ : null,
1067
+ (update && update.ok && update.hasUpdate && waitingDownload)
1068
+ ? h('button', { className: '__ss_btn', onClick: openDownloads }, '打开下载文件夹')
1038
1069
  : null
1039
1070
  ),
1040
1071
  (update && update.ok && update.hasUpdate)
1041
1072
  ? h('div', { className: '__ss_hint', style: { marginTop: '6px', lineHeight: '1.8' } },
1042
- '下载会优先用 Chrome(Edge + IDM 可能拦下载)。如果没自动继续,点「选择安装包」选 Downloads 里的 zip。')
1073
+ '下载会优先用 Chrome(Edge + IDM 可能拦下载)。如果没自动继续,点「打开下载文件夹」找到 zip,再点「选择安装包」。')
1074
+ : null,
1075
+ waitingDownload
1076
+ ? h('div', { className: '__ss_hint', style: { marginTop: '6px', lineHeight: '1.8', color: '#c98a2b' } },
1077
+ '正在等浏览器下载(最多 30 分钟)。下载完成后会自动继续;如果已经下好,点「打开下载文件夹」看看,或直接点「选择安装包」选那个 zip。')
1043
1078
  : null,
1044
1079
  h('div', { className: '__ss_hint', style: { marginTop: '6px', lineHeight: '1.8' } },
1045
1080
  '回滚保护:安装新版本前,桌面会自动多一个「紧急回滚-V当前版本」按钮。新版装不上、或装完 DSH 打不开时,完全退出 DSH(窗口+托盘)后双击它,就能回到更新前的版本;它只换插件版本,不动你的设置。更新成功后旧按钮会被自动替换,不会越积越多——确认新版正常后也不用手动删。'),
@@ -1552,7 +1587,7 @@ window.__ModuleLoader__.load({
1552
1587
  /* 每次改客户端就把这个号 +1。
1553
1588
  有了它在控制台一眼就能判断"浏览器里跑的是不是最新那份" ——
1554
1589
  排查问题时,分不清是代码问题还是旧包,会白猜一轮。 */
1555
- var BUILD = 'cw-34'
1590
+ var BUILD = 'cw-35'
1556
1591
 
1557
1592
  function log() {
1558
1593
  try {
@@ -601,11 +601,16 @@ function apply(ctx) {
601
601
  '<script src="' + ROUTE_BASE + '/splash.js"></script></body></html>'
602
602
  }
603
603
 
604
- /* 一键诊断:把环境信息写到桌面,让不会用 AI 的用户也能一键求助 */
604
+ /* 一键诊断:把环境信息写到桌面,让不会用 AI 的用户也能一键求助。
605
+ 同一时间只允许生成一份:按钮连点时后面的请求直接返回“正在生成中”,
606
+ 避免桌面被一堆同名报告刷屏。 */
607
+ let diagnoseBusy = false
605
608
  disposers.push(registerRoute({
606
609
  kind: 'exact',
607
610
  path: ROUTE_BASE + '/diagnose',
608
611
  handler: async (req, res) => {
612
+ if (diagnoseBusy) { sendJson(res, 429, { ok: false, error: '诊断报告正在生成中,请等几秒,不用重复点击。' }); return }
613
+ diagnoseBusy = true
609
614
  try {
610
615
  const lines = []
611
616
  const add = (s2) => lines.push(s2 == null ? '' : String(s2))
@@ -688,6 +693,8 @@ function apply(ctx) {
688
693
  sendJson(res, 200, { ok: true, file, desktop })
689
694
  } catch (err) {
690
695
  sendJson(res, 500, { ok: false, error: String((err && err.message) || err) })
696
+ } finally {
697
+ diagnoseBusy = false
691
698
  }
692
699
  }
693
700
  }))
@@ -1327,6 +1334,26 @@ function apply(ctx) {
1327
1334
  return out
1328
1335
  }
1329
1336
 
1337
+ /* 打开浏览器默认下载目录:等下载超时/找不到文件时,给用户一个直接可见的出口 */
1338
+ disposers.push(registerRoute({
1339
+ kind: 'exact',
1340
+ path: ROUTE_BASE + '/open-downloads',
1341
+ handler: async (req, res) => {
1342
+ if ((req.method || 'GET').toUpperCase() !== 'POST') { sendJson(res, 405, { ok: false, error: 'method not allowed' }); return }
1343
+ try {
1344
+ const dir = browserDownloadDirs()[0]
1345
+ try { fs.mkdirSync(dir, { recursive: true }) } catch (e) { /* 忽略 */ }
1346
+ if (process.platform === 'win32') spawn('explorer.exe', [dir], { detached: true, stdio: 'ignore' }).unref()
1347
+ else if (process.platform === 'darwin') spawn('open', [dir], { detached: true, stdio: 'ignore' }).unref()
1348
+ else spawn('xdg-open', [dir], { detached: true, stdio: 'ignore' }).unref()
1349
+ diag('open-downloads ' + dir)
1350
+ sendJson(res, 200, { ok: true, dir })
1351
+ } catch (err) {
1352
+ sendJson(res, 200, { ok: false, error: String((err && err.message) || err) })
1353
+ }
1354
+ }
1355
+ }))
1356
+
1330
1357
 
1331
1358
  /* 自动更新(浏览器代理路线):让系统默认浏览器去下载,插件只负责“接住”文件 */
1332
1359
  disposers.push(registerRoute({
@@ -1366,7 +1393,8 @@ function apply(ctx) {
1366
1393
  if (!/^[0-9][0-9A-Za-z.+-]{0,39}$/.test(version)) throw new Error('版本号不合法')
1367
1394
  const dirs = browserDownloadDirs()
1368
1395
  const since = (browserDownloadAt || Date.now()) - 120000
1369
- const deadline = Date.now() + 600000
1396
+ /* 30 分钟:国内网络下 24MB 的包偶尔要十几分钟,10 分钟太容易误判超时 */
1397
+ const deadline = Date.now() + 1800000
1370
1398
  diag('update-watch-download start dirs=' + dirs.join('|') + ' since=' + since)
1371
1399
  let found = null
1372
1400
  let bad = null
@@ -1375,7 +1403,8 @@ function apply(ctx) {
1375
1403
  try {
1376
1404
  if (!fs.existsSync(dir)) continue
1377
1405
  for (const f of fs.readdirSync(dir)) {
1378
- if (!/^dsh-boot-suite-win.*\.zip$/i.test(f)) continue
1406
+ /* 文件名放宽:Chrome 可能存成 “dsh-boot-suite-win-V2.13.12 (1).zip” 甚至带空格的名字 */
1407
+ if (!/dsh[-_ ]?boot[-_ ]?suite.*\.zip$/i.test(f)) continue
1379
1408
  const fp = path.join(dir, f)
1380
1409
  const st = fs.statSync(fp)
1381
1410
  if (!st.isFile() || st.size < 1024 * 1024) continue
@@ -1397,7 +1426,7 @@ function apply(ctx) {
1397
1426
  }
1398
1427
  if (!found) await new Promise((r) => setTimeout(r, 1500))
1399
1428
  }
1400
- if (!found) { diag('update-watch-download timeout'); sendJson(res, 200, { ok: false, error: '等待浏览器下载超时(10 分钟)。建议用 Chrome 重新点「立即更新」,或点「选择安装包」手动指定。' }); return }
1429
+ if (!found) { diag('update-watch-download timeout'); sendJson(res, 200, { ok: false, error: '等待浏览器下载超时(30 分钟)。可以点「打开下载文件夹」看看安装包是不是已经下好,再点「选择安装包」手动指定。' }); return }
1401
1430
  const raw = fs.readFileSync(found.file)
1402
1431
  if (!zipComplete(raw)) throw new Error('浏览器下载的安装包不完整(zip 校验失败),请重新下载')
1403
1432
  const outDir = path.join(DSH_HOME, 'boot-suite-updates')
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dsh-boot-suite",
3
- "version": "2.13.11",
4
- "description": "DeepSeek Harness 开机套件:冷启动开机动画 + 聊天壁纸。开机动画走设置页「启动动画」(两幕可选,默认档案暖白 → 鲸鱼娘扫描睁眼;支持导入自定义动画、三段自定义转场、本地动画删除、真声台词 + 界面音效);聊天壁纸走设置页「聊天背景」(自带一张默认背景图,开箱即有;静态底图(可铺满或完整显示)/ 循环视频 + 大字横幅,聊天正文文字随壁纸+纱的实际亮度自适应(纱浓度可调),侧边栏/标题栏跟随外观深浅;桌面窗口按钮颜色固定高对比,支持丢图即换、自定义视频目录、壁纸引擎项目文件夹、纱的浓度与镜头模糊调节,视频默认静音循环)。两个半侧各自独立:动画坏了不影响壁纸,壁纸坏了不影响动画。",
3
+ "version": "2.13.12",
4
+ "description": "DeepSeek Harness 开机套件:冷启动开机动画 + 聊天壁纸。开机动画走设置页「启动动画」(两幕可选,默认档案暖白 → 鲸鱼娘扫描睁眼;支持导入自定义动画、三段自定义转场、本地动画删除、真声台词 + 界面音效);聊天壁纸走设置页「聊天背景」(自带一张默认背景图,开箱即有;静态底图(可铺满或完整显示)/ 循环视频 + 大字横幅,聊天正文文字随壁纸+纱的实际亮度自适应(纱浓度可调),侧边栏/标题栏跟随外观深浅;桌面窗口按钮颜色固定高对比,支持丢图即换、自定义视频目录、壁纸引擎项目文件夹、纱的浓度与镜头模糊调节,视频默认静音循环;更新等待最长 30 分钟、可一键打开下载文件夹或手动选择安装包,诊断报告生成中会防连点)。两个半侧各自独立:动画坏了不影响壁纸,壁纸坏了不影响动画。",
5
5
  "keywords": [
6
6
  "dsh",
7
7
  "dsh-plugin",
@@ -132,7 +132,7 @@ check('没有 Config 导出(普通对象不是 schemastery schema)', mod.Con
132
132
 
133
133
  console.log('\n[2] apply(ctx):路由与注入')
134
134
  mod.apply(ctx)
135
- check('注册了 37 条路由(开机动画 26 + 聊天壁纸 11)', ROUTES.size === 37, [...ROUTES.keys()].join(' | '))
135
+ check('注册了 38 条路由(开机动画 27 + 聊天壁纸 11)', ROUTES.size === 38, [...ROUTES.keys()].join(' | '))
136
136
  check('注册了 1 个 tapIndex', TAPS.length === 1)
137
137
  check('用了 ctx.effect 管理生命周期(开机动画 1 + 壁纸 11)', effects === 12, String(effects))
138
138
 
@@ -427,6 +427,12 @@ console.log('\n[6g] 更新包完整性:只看 PK 头的半包不再被接受')
427
427
  check('坏包不落盘', !fs.existsSync(path.join(SMOKE_HOME, 'boot-suite-updates', '9.9.9.zip')))
428
428
  }
429
429
 
430
+ console.log('\n[6h] v2.13.12:打开下载文件夹路由存在且只接受 POST')
431
+ {
432
+ const getOpen = await call('exact /dsh-startup/open-downloads', 'GET')
433
+ check('GET /open-downloads 返回 405', getOpen.statusCode === 405, String(getOpen.statusCode))
434
+ }
435
+
430
436
  console.log('\n[7] XSS 边界:身份名称里的尖括号不会逃出内联脚本')
431
437
  await call('exact /dsh-startup/config', 'POST', JSON.stringify({ patch: { identity: '</script><img src=x onerror=alert(1)>' } }))
432
438
  const payload = inlinePayload(renderRows(before, collectRows()))
@@ -463,7 +463,7 @@ ok(intervals.every((t) => t.cleared) || intervals.length === 0, '旧的 interval
463
463
  intervals.map((t) => (t.cleared ? 'cleared' : 'live')).join(','))
464
464
 
465
465
  console.log('\n=== ⑦ v2.13.0 测试钩子 / BUILD ===')
466
- ok(mod.__BUILD === 'cw-34', '__BUILD = cw-34(排查浏览器跑的是不是新包)', String(mod.__BUILD))
466
+ ok(mod.__BUILD === 'cw-35', '__BUILD = cw-35(排查浏览器跑的是不是新包)', String(mod.__BUILD))
467
467
  ok(mod.__VIDEO_ID === 'dsh-cw-video', '__VIDEO_ID = dsh-cw-video', String(mod.__VIDEO_ID))
468
468
  ok(typeof mod.__startShuffle === 'function', '__startShuffle 导出')
469
469
  ok(typeof mod.__stopShuffle === 'function', '__stopShuffle 导出')