dsh-wisp 1.42.0 → 1.44.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/README.md +84 -3
- package/cordis.patch.yml +5 -0
- package/lib/client.js +192 -15
- package/lib/client.template.js +192 -15
- package/lib/index.js +118 -16
- package/package.json +1 -1
- package/verify-wisp.mjs +313 -19
package/lib/client.template.js
CHANGED
|
@@ -51,7 +51,7 @@ window.__ModuleLoader__.load({
|
|
|
51
51
|
var exports = module.exports
|
|
52
52
|
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' })
|
|
53
53
|
|
|
54
|
-
const VERSION = '1.
|
|
54
|
+
const VERSION = '1.44.0'
|
|
55
55
|
|
|
56
56
|
/* 每一版一句话。**必须写当前版本** —— 预检里有一条断言盯着它,
|
|
57
57
|
忘了更新就会红,不会静默过期。 */
|
|
@@ -86,6 +86,12 @@ const WHATS_NEW = {
|
|
|
86
86
|
'1.40.0': '她的透明处真的点不到了:按轮廓命中,站在按钮上也不挡你点它。',
|
|
87
87
|
'1.41.0': '检查更新更耐得住:一个源连不上不再把另一个拖死,失败时 doctor() 会说清是哪个源、为什么。',
|
|
88
88
|
'1.42.0': '查更新失败时她会直接说清是哪一种:没通道 / 连不上 / 宿主没应答。',
|
|
89
|
+
'1.43.0': '一批"真的用鼠标点过一遍"才发现的问题:拨完开关面板不再消失、大小能调回出厂值、触摸与指针离开不再留姿势、静止档真的会停。',
|
|
90
|
+
'1.43.1': '检查更新多了一条兜底通道(问插件管理器),并且失败时会直接说出"为什么拿不到"。',
|
|
91
|
+
'1.43.2': '台词只说人话;查不成的原因排进「关于她」弹窗,不再往句子里塞英文碎片。',
|
|
92
|
+
'1.44.0': '查更新改成客户端自己抓(bundle 插件没有 host RPC 座位)—— 这才是它一直缺的那条路。',
|
|
93
|
+
'1.43.4': '把查更新失败时的诊断临时写进窗口标题,供定位用。',
|
|
94
|
+
'1.43.3': '把"没接上宿主"和"宿主没有网络通道"分成两句 —— 断在哪一环,一句话就看得出来。',
|
|
89
95
|
}
|
|
90
96
|
|
|
91
97
|
/* 更新这件事**这个插件自己做不到**,如实写在代码里,免得以后有人以为能:
|
|
@@ -95,6 +101,11 @@ const WHATS_NEW = {
|
|
|
95
101
|
所以这里只做三件真能做的事:报版本、说她更新了、把包名复制给你去 DSH 里装。 */
|
|
96
102
|
const SEEN_VERSION_KEY = 'dsh-wisp:seen-version:v1'
|
|
97
103
|
const UPDATE_HINT = '在 DSH 的插件列表里按包名安装:dsh-wisp'
|
|
104
|
+
/* 但"按包名安装"只在 npm 上已经有这一版时成立。npm 暂存(或还没发)而 GitHub
|
|
105
|
+
已经更新时,照那句话装回来的是**旧版** —— 她得改口,并复制仓库地址而不是包名。
|
|
106
|
+
她本来就知道这个版本号是从哪个源读到的(updateCheck.from / sources)。 */
|
|
107
|
+
const UPDATE_REPO = 'https://github.com/969246694/dsh-wisp'
|
|
108
|
+
const UPDATE_HINT_GITHUB = 'npm 上还是旧版,用这个地址装 dsh-wisp:' + UPDATE_REPO
|
|
98
109
|
|
|
99
110
|
/* ---- sprites: 256x384 WebP, transparent, inlined by build.mjs ---- */
|
|
100
111
|
const SPRITES = __SPRITES_LITERAL__
|
|
@@ -411,6 +422,11 @@ const UPDATE_HINT = '在 DSH 的插件列表里按包名安装:dsh-wisp'
|
|
|
411
422
|
「宿主那边没应答」是三件事,含糊地说成"没查到"就没人能追下去 ——
|
|
412
423
|
而用户看到的恰恰只有那一句。 */
|
|
413
424
|
updateNoChannel: ['这个壳里我没有联网的通道,查不了更新。', '这里没有能出去的网络通道。'],
|
|
425
|
+
/* 两种"没通道"必须分开说 —— 它们是链路上**不同的一环**断了:
|
|
426
|
+
· 没接上宿主半包(客户端这一侧连 `host.call` 都没有)
|
|
427
|
+
· 宿主跑了,但它拿不到 `web` 服务
|
|
428
|
+
合并成一句话的代价是:谁也说不清断在哪一环(这个坑真踩过)。 */
|
|
429
|
+
updateNoSeat: ['我没接上宿主半包,查不了更新。', '宿主那边没接上,查不了更新。'],
|
|
414
430
|
updateNoHost: ['宿主那边没应答,更新没查成。', '问不到宿主,更新没查成。'],
|
|
415
431
|
milestone: [
|
|
416
432
|
'今天第 {n} 轮了。', '{n} 轮了,还不错。', '已经 {n} 轮了,你挺能撑。',
|
|
@@ -470,6 +486,7 @@ const UPDATE_HINT = '在 DSH 的插件列表里按包名安装:dsh-wisp'
|
|
|
470
486
|
streak: ['{n} days in a row.', 'Day {n} together.'],
|
|
471
487
|
updateFailed: ["The update check didn't work.", "Couldn't reach the update sources."],
|
|
472
488
|
updateNoChannel: ["This shell gives me no network channel, so I can't check for updates.", 'No way out to the network from here.'],
|
|
489
|
+
updateNoSeat: ["I'm not connected to my host half, so I can't check for updates.", 'No host half attached.'],
|
|
473
490
|
updateNoHost: ["The host side didn't answer — the update check failed.", 'No answer from the host.'],
|
|
474
491
|
balanceChecking: ['One moment…', 'Let me check.'],
|
|
475
492
|
balance: ['Balance: {n}.', "You've got {n}.", '{n} on the account.'],
|
|
@@ -891,6 +908,8 @@ body[data-ds-dark-theme] .wisp-layer{--wisp-say-fg:#eaf2f8;--wisp-say-bg:rgba(20
|
|
|
891
908
|
.wisp-root[data-motion="off"]{--wisp-amp:0}
|
|
892
909
|
/* 静止档:连呼吸和 z 都停 —— 她说"我不想让她动"时,是最彻底的静止。 */
|
|
893
910
|
.wisp-root[data-motion="off"] .wisp-body,.wisp-root[data-motion="off"] .wisp-zzz{animation:none}
|
|
911
|
+
/* 但"不动"不等于"冻住":动画一关,z 会停在满不透明的初始态,看起来像一个卡住的字 */
|
|
912
|
+
.wisp-root[data-motion="off"] .wisp-zzz{opacity:.5}
|
|
894
913
|
.wisp-root[data-accent="press"] .wisp-motion{animation:wisp-press .2s ease-out}
|
|
895
914
|
.wisp-root[data-accent="land"] .wisp-motion{animation:wisp-land .56s cubic-bezier(.2,1.15,.35,1)}
|
|
896
915
|
.wisp-root[data-accent="pop-a"] .wisp-motion{animation:wisp-pop-a .38s ease-out}
|
|
@@ -976,7 +995,7 @@ body[data-ds-dark-theme] .wisp-layer{--wisp-say-fg:#eaf2f8;--wisp-say-bg:rgba(20
|
|
|
976
995
|
.wisp-credits{font-size:11px;opacity:.8;margin:6px 0 0}
|
|
977
996
|
.wisp-dialog-action{display:block;width:100%;margin:4px 0 2px;padding:7px 10px;border:1px solid var(--dsw-alias-border-l1,var(--wisp-say-line));border-radius:8px;background:none;color:inherit;font:inherit;cursor:pointer}
|
|
978
997
|
.wisp-dialog-action:hover{background:var(--dsw-alias-bg-layer-2,rgba(120,170,255,.2))}
|
|
979
|
-
@media (prefers-reduced-motion:reduce){.wisp-body{animation:none!important}.wisp-motion{animation:none!important}.wisp-lean{transform:none!important;transition:none!important}.wisp-menu,.wisp-submenu,.wisp-switch,.wisp-switch i,.wisp-slider-knob,.wisp-menu-item,.wisp-corner{animation:none!important;transition:none!important}}`
|
|
998
|
+
@media (prefers-reduced-motion:reduce){.wisp-body{animation:none!important}.wisp-motion{animation:none!important}.wisp-zzz{animation:none!important;opacity:.5}.wisp-lean{transform:none!important;transition:none!important}.wisp-menu,.wisp-submenu,.wisp-switch,.wisp-switch i,.wisp-slider-knob,.wisp-menu-item,.wisp-corner{animation:none!important;transition:none!important}}`
|
|
980
999
|
|
|
981
1000
|
const installStyles = (styles) => {
|
|
982
1001
|
if (styles && typeof styles.insert === 'function') {
|
|
@@ -1601,7 +1620,11 @@ body[data-ds-dark-theme] .wisp-layer{--wisp-say-fg:#eaf2f8;--wisp-say-bg:rgba(20
|
|
|
1601
1620
|
|
|
1602
1621
|
/* 结构:body(情绪动画)> motion(一次性动作)> lean(姿势)> shine/img,
|
|
1603
1622
|
外加一个兄弟节点 hit(命中层)。 */
|
|
1604
|
-
|
|
1623
|
+
/* 光晕(脚底那圈光)挂在 motion 层、**不在 lean 层**:它是地面上的东西,
|
|
1624
|
+
不该跟着她的侧身一起歪 —— 挂在 lean 里的时候,她一倾斜光圈也跟着转,
|
|
1625
|
+
看起来像地板歪了。但按下/落地那一下的压扁它该跟着(那是"她压在地上"),
|
|
1626
|
+
所以留在 motion 层而不是提到 body 层。 */
|
|
1627
|
+
motionEl.appendChild(shine)
|
|
1605
1628
|
leanEl.appendChild(img)
|
|
1606
1629
|
motionEl.appendChild(leanEl)
|
|
1607
1630
|
bodyEl.appendChild(motionEl)
|
|
@@ -2316,6 +2339,10 @@ body[data-ds-dark-theme] .wisp-layer{--wisp-say-fg:#eaf2f8;--wisp-say-bg:rgba(20
|
|
|
2316
2339
|
let wanderHandle = null
|
|
2317
2340
|
const syncWander = () => {
|
|
2318
2341
|
if (wanderHandle) { clock.cancel(wanderHandle); wanderHandle = null }
|
|
2342
|
+
/* 溜达也是"动作":`motion: off` 和 prefers-reduced-motion 都该让她待在原地。
|
|
2343
|
+
之前只关了 CSS 动画,她照样会自己滑走 —— 那是最容易被忽略的一种"没停下来",
|
|
2344
|
+
因为看起来只是一次正常的挪动。(CSS 关不掉 JS 写进去的坐标。) */
|
|
2345
|
+
if (cfg.motion === 'off' || reduced()) return
|
|
2319
2346
|
if (!cfg.wander || cfg.wanderRange <= 0 || paused) return
|
|
2320
2347
|
wanderHandle = clock.every(cfg.wanderMs, () => {
|
|
2321
2348
|
if (destroyed || hidden || mood !== 'idle' || start !== null) return
|
|
@@ -2424,8 +2451,17 @@ body[data-ds-dark-theme] .wisp-layer{--wisp-say-fg:#eaf2f8;--wisp-say-bg:rgba(20
|
|
|
2424
2451
|
const dx = e.clientX - start.x, dy = e.clientY - start.y
|
|
2425
2452
|
if (!moved && Math.sqrt(dx * dx + dy * dy) > 4) moved = true
|
|
2426
2453
|
if (!moved) return
|
|
2454
|
+
const beforeX = coords.x, beforeY = coords.y
|
|
2427
2455
|
coords.x = clamp(origin.x + dx, 0, maxX())
|
|
2428
2456
|
coords.y = clamp(origin.y + dy, 0, maxY())
|
|
2457
|
+
/* 被边界夹住时她其实**没有动** —— 这时候再按指针位移去倾,就是"原地歪",
|
|
2458
|
+
看起来像坏了。位置没变就把姿势归零(指针继续拖也不影响,因为下一帧
|
|
2459
|
+
只要真的动了就会重新算)。 */
|
|
2460
|
+
if (coords.x === beforeX && coords.y === beforeY) {
|
|
2461
|
+
lastDragX = e.clientX
|
|
2462
|
+
setTilt(0)
|
|
2463
|
+
return
|
|
2464
|
+
}
|
|
2429
2465
|
/* 拖动时她朝**运动的反方向**倾:被拎着走的东西,顶是往后倒的。
|
|
2430
2466
|
只看"这一次事件走了多远"就等价于速度,不用存历史、不用积分 ——
|
|
2431
2467
|
而且它天然跟着指针事件的频率走,慢拖就是小角度。 */
|
|
@@ -2512,6 +2548,10 @@ body[data-ds-dark-theme] .wisp-layer{--wisp-say-fg:#eaf2f8;--wisp-say-bg:rgba(20
|
|
|
2512
2548
|
if (destroyed || paused || hidden || start !== null) return
|
|
2513
2549
|
if (mood === 'sleep') return
|
|
2514
2550
|
if (cfg.motion === 'off' || reduced()) return
|
|
2551
|
+
/* 触摸屏上"指针"就是手指:手指划过时让她整只歪过去是错的 ——
|
|
2552
|
+
那既不是"靠近"也不是"路过",而且手指一抬起姿势就冻在那儿。
|
|
2553
|
+
所以触摸一律归零(真正该有的反馈是"按下压一下",那是另一条通道)。 */
|
|
2554
|
+
if (e.pointerType === 'touch') { setTilt(0); return }
|
|
2515
2555
|
if (typeof e.clientX !== 'number' || typeof e.clientY !== 'number') return
|
|
2516
2556
|
const dx = e.clientX - (coords.x + boxW() / 2)
|
|
2517
2557
|
const dy = e.clientY - (coords.y + boxH() / 2)
|
|
@@ -2522,9 +2562,16 @@ body[data-ds-dark-theme] .wisp-layer{--wisp-say-fg:#eaf2f8;--wisp-say-bg:rgba(20
|
|
|
2522
2562
|
}
|
|
2523
2563
|
window.addEventListener('pointermove', leanToPointer, { passive: true })
|
|
2524
2564
|
/* 指针移出窗口、或者你切走应用之后,不会再有点事件来把它归零 ——
|
|
2525
|
-
没有这一下,她会一直朝着最后那个位置歪着。
|
|
2565
|
+
没有这一下,她会一直朝着最后那个位置歪着。
|
|
2566
|
+
`blur` 只覆盖"切走应用",**指针移出窗口不会触发它** —— 补一个文档级
|
|
2567
|
+
mouseleave(指针离开文档时才发),两条一起才真的覆盖到。 */
|
|
2526
2568
|
const resetTilt = () => setTilt(0)
|
|
2527
2569
|
window.addEventListener('blur', resetTilt)
|
|
2570
|
+
try {
|
|
2571
|
+
if (typeof document !== 'undefined' && document.addEventListener) {
|
|
2572
|
+
document.addEventListener('mouseleave', resetTilt)
|
|
2573
|
+
}
|
|
2574
|
+
} catch (e) { /* 没有 document:她最多保留最后一次侧身,不影响其它功能 */ }
|
|
2528
2575
|
|
|
2529
2576
|
/* 键盘入口:Enter / Space / 菜单键 / Shift+F10 打开菜单。
|
|
2530
2577
|
Space 默认会滚动页面,Enter 在表单里会提交 —— 都要拦。 */
|
|
@@ -2564,7 +2611,11 @@ body[data-ds-dark-theme] .wisp-layer{--wisp-say-fg:#eaf2f8;--wisp-say-bg:rgba(20
|
|
|
2564
2611
|
只认**带修饰键**的滚轮:她占地不小,直接吞掉普通滚轮等于在她身上把页面滚动禁掉。
|
|
2565
2612
|
带修饰键是「缩放」的通行手势,此时 preventDefault 才是帮忙而不是捣乱。
|
|
2566
2613
|
步长 0.25,上下限由 CONFIG_SPEC 统一夹紧(0.4–8),不在这里再写一遍。 */
|
|
2567
|
-
|
|
2614
|
+
/* 步长必须是"默认值可达"的:size 的区间是 0.4–8,默认 2.5。
|
|
2615
|
+
原来键盘/滚轮步长 0.25,可达值只有 0.4 + 0.25k —— **2.5 不在格点上**
|
|
2616
|
+
(最近的是 2.4 / 2.65),于是用户只要动过一次滑块,就再也调不回出厂大小。
|
|
2617
|
+
0.1 让 2.5 正好落在格点上(0.4 + 21×0.1)。预检里有一条守着这个不变式。 */
|
|
2618
|
+
const WHEEL_STEP = 0.1
|
|
2568
2619
|
const onWheel = (ev) => {
|
|
2569
2620
|
if (!ev.ctrlKey && !ev.metaKey) return // 普通滚轮原样放行
|
|
2570
2621
|
if (typeof ev.preventDefault === 'function') ev.preventDefault()
|
|
@@ -2875,12 +2926,17 @@ body[data-ds-dark-theme] .wisp-layer{--wisp-say-fg:#eaf2f8;--wisp-say-bg:rgba(20
|
|
|
2875
2926
|
子面板里的开关点完必须留在原地 —— 拨个开关面板就消失是最烦人的交互之一。 */
|
|
2876
2927
|
const refreshSurfaces = () => {
|
|
2877
2928
|
const keep = flyoutName
|
|
2929
|
+
/* 重画之前先记住"这个面板是点开的还是悬停开的":openMenu 里的 closeFlyout()
|
|
2930
|
+
会把标记清掉,不记下来,拨一次开关后面板就从"钉住"降级成"悬停开启" ——
|
|
2931
|
+
而重画出来的 DOM 里鼠标并没有真的悬停过,宽限期一到面板就自己关了。
|
|
2932
|
+
用户看到的现象就是:拨个开关,面板消失(而这正是这段注释上面说不能有的)。 */
|
|
2933
|
+
const keepPinned = flyoutPinned
|
|
2878
2934
|
const keepIndex = flyoutRow && flyoutRow.dataset ? Number(flyoutRow.dataset.rowIndex) : -1
|
|
2879
2935
|
openMenu(menuAtX, menuAtY, false) // 重画菜单(会先关掉面板)
|
|
2880
2936
|
if (!keep || !menuEl) return
|
|
2881
2937
|
const row = menuEl.querySelectorAll('.wisp-menu-item')
|
|
2882
2938
|
.find((el) => el.dataset && el.dataset.group === keep)
|
|
2883
|
-
if (row) openFlyout(keep, row, Number.isFinite(keepIndex) && keepIndex >= 0 ? keepIndex : Number(row.dataset.rowIndex))
|
|
2939
|
+
if (row) openFlyout(keep, row, Number.isFinite(keepIndex) && keepIndex >= 0 ? keepIndex : Number(row.dataset.rowIndex), keepPinned)
|
|
2884
2940
|
}
|
|
2885
2941
|
|
|
2886
2942
|
const openFlyout = (name, rowEl, index, pinned) => {
|
|
@@ -3160,7 +3216,7 @@ body[data-ds-dark-theme] .wisp-layer{--wisp-say-fg:#eaf2f8;--wisp-say-bg:rgba(20
|
|
|
3160
3216
|
])
|
|
3161
3217
|
}
|
|
3162
3218
|
|
|
3163
|
-
const aboutDialog = () => {
|
|
3219
|
+
const aboutDialog = (diagRes) => {
|
|
3164
3220
|
const note = WHATS_NEW[VERSION]
|
|
3165
3221
|
/* 菜单里那条「版本与更新」已经收掉了(版本号现在直接显示在菜单头部),
|
|
3166
3222
|
所以检查更新必须有新的入口 —— 放在这里,上下文正合适。 */
|
|
@@ -3180,9 +3236,12 @@ body[data-ds-dark-theme] .wisp-layer{--wisp-say-fg:#eaf2f8;--wisp-say-bg:rgba(20
|
|
|
3180
3236
|
para('她还能陪你专注计时:菜单里开一段 25 或 45 分钟,期间她保持"干活中",戳她一下是问还剩多久,到点她替你收工。'),
|
|
3181
3237
|
para('这一版:' + (note || VERSION)),
|
|
3182
3238
|
para('形象与设定参考社区项目 Neko3000/deepseek-whalechan(设定文档 CC BY-NC-SA 4.0,原作者 B站 ZipZipPipe、上善无形)。'),
|
|
3239
|
+
/* 上一次检查更新失败时,把宿主看到的东西摆在最显眼的位置 —— 台词只有一句,
|
|
3240
|
+
而"为什么拿不到"必须能被看到、被复制、被追问。 */
|
|
3241
|
+
diagRes ? para('上次查更新为什么没成:\n' + describeDiag(diagRes)) : null,
|
|
3183
3242
|
checkBtn,
|
|
3184
3243
|
dialogFoot('非官方同人作品,与 DeepSeek 官方无关', 'v' + VERSION),
|
|
3185
|
-
])
|
|
3244
|
+
].filter(Boolean))
|
|
3186
3245
|
}
|
|
3187
3246
|
|
|
3188
3247
|
/* 菜单的结构 ------------------------------------------------------------
|
|
@@ -3475,7 +3534,7 @@ body[data-ds-dark-theme] .wisp-layer{--wisp-say-fg:#eaf2f8;--wisp-say-bg:rgba(20
|
|
|
3475
3534
|
if (focused && focused.dataset && focused.dataset.slider === '1'
|
|
3476
3535
|
&& (ev.key === 'ArrowLeft' || ev.key === 'ArrowRight')) {
|
|
3477
3536
|
if (typeof ev.preventDefault === 'function') ev.preventDefault()
|
|
3478
|
-
const step = ev.shiftKey ? 1 : 0.
|
|
3537
|
+
const step = ev.shiftKey ? 1 : 0.1
|
|
3479
3538
|
applyConfig({ size: cfg.size + (ev.key === 'ArrowRight' ? step : -step) })
|
|
3480
3539
|
/* 只更新滑块自身,不重画整张菜单 —— 焦点和展开状态都不动 */
|
|
3481
3540
|
try {
|
|
@@ -3692,6 +3751,9 @@ body[data-ds-dark-theme] .wisp-layer{--wisp-say-fg:#eaf2f8;--wisp-say-bg:rgba(20
|
|
|
3692
3751
|
|
|
3693
3752
|
host 是 shell 注入的名字,不是全局:拿不到就如实说"查不了",不许抛。 */
|
|
3694
3753
|
let updateCheck = { state: 'idle', at: 0 }
|
|
3754
|
+
/* 上一次真正复制给用户的提示语(doctor 里能看到)—— "她让你装的是哪个" 必须可查,
|
|
3755
|
+
否则"按包名安装"和"用 GitHub 地址装"这两条路到底走了哪条就没人说得清。 */
|
|
3756
|
+
let lastUpdateHint = UPDATE_HINT
|
|
3695
3757
|
|
|
3696
3758
|
const compareVersions = (a, b) => {
|
|
3697
3759
|
const parse = (v) => String(v).split('-')[0].split('.').map((n) => {
|
|
@@ -3717,12 +3779,58 @@ body[data-ds-dark-theme] .wisp-layer{--wisp-say-fg:#eaf2f8;--wisp-say-bg:rgba(20
|
|
|
3717
3779
|
return null
|
|
3718
3780
|
}
|
|
3719
3781
|
|
|
3782
|
+
/* 客户端**自己的**通道 —— 这才是这个插件真正的联网方式。
|
|
3783
|
+
|
|
3784
|
+
事实(从平台的 dsh-client-modules 加载器读出来的):bundle 插件的客户端半包
|
|
3785
|
+
是用 <script> 加载的**普通页面脚本**,平台不注入陷阱、不替换全局 —— 所以
|
|
3786
|
+
`window.fetch` 对它可用。而 `host.call` 那个 RPC 座位只发给**动态插件**
|
|
3787
|
+
(runner 里 `hasHostHalf: definition.hostCode !== undefined`),我们这个形态
|
|
3788
|
+
从来没有过它。
|
|
3789
|
+
|
|
3790
|
+
以前代码里写着"浏览器半包的 fetch 是陷阱",那是把**预检替身**的契约当成了
|
|
3791
|
+
平台契约 —— 整个更新检查因此在错误的路上走了很久(真机上永远"没接上宿主")。 */
|
|
3792
|
+
const NPM_LATEST_URL = 'https://registry.npmjs.org/dsh-wisp/latest'
|
|
3793
|
+
const GITHUB_RAW_URL = 'https://raw.githubusercontent.com/969246694/dsh-wisp/main/package.json'
|
|
3794
|
+
const clientVersionAt = (url) => {
|
|
3795
|
+
try {
|
|
3796
|
+
const fetcher = window.fetch
|
|
3797
|
+
if (typeof fetcher !== 'function') return Promise.resolve(null)
|
|
3798
|
+
return fetcher.call(window, url, { cache: 'no-store' })
|
|
3799
|
+
.then((res) => (res && res.ok === true ? res.json() : null))
|
|
3800
|
+
.then((data) => (data && typeof data.version === 'string' ? data.version : null))
|
|
3801
|
+
.catch(() => null)
|
|
3802
|
+
} catch (e) { return Promise.resolve(null) }
|
|
3803
|
+
}
|
|
3804
|
+
|
|
3805
|
+
/* 客户端侧的完整检查:两个源各取一次,取更高的那个(与宿主侧同一套规则)。 */
|
|
3806
|
+
const clientSideCheck = () => {
|
|
3807
|
+
updateCheck = { state: 'checking', at: nowMs() }
|
|
3808
|
+
return Promise.all([clientVersionAt(NPM_LATEST_URL), clientVersionAt(GITHUB_RAW_URL)])
|
|
3809
|
+
.then(([npmVersion, githubVersion]) => {
|
|
3810
|
+
const sources = {
|
|
3811
|
+
npm: npmVersion ? { ok: true, version: npmVersion } : { ok: false, reason: 'fetch-failed' },
|
|
3812
|
+
github: githubVersion ? { ok: true, version: githubVersion } : { ok: false, reason: 'fetch-failed' },
|
|
3813
|
+
}
|
|
3814
|
+
const cands = [['npm', npmVersion], ['github', githubVersion]].filter((p) => typeof p[1] === 'string')
|
|
3815
|
+
if (cands.length === 0) {
|
|
3816
|
+
updateCheck = { state: 'failed', reason: 'fetch-failed', sources, diag: null, at: nowMs() }
|
|
3817
|
+
publishDiagToTitle(updateCheck)
|
|
3818
|
+
return updateCheck
|
|
3819
|
+
}
|
|
3820
|
+
let best = cands[0]
|
|
3821
|
+
for (const c of cands) if (compareVersions(best[1], c[1]) < 0) best = c
|
|
3822
|
+
updateCheck = compareVersions(VERSION, best[1]) < 0
|
|
3823
|
+
? { state: 'available', latest: best[1], from: best[0], sources, at: nowMs() }
|
|
3824
|
+
: { state: 'current', latest: best[1], from: best[0], sources, at: nowMs() }
|
|
3825
|
+
return updateCheck
|
|
3826
|
+
})
|
|
3827
|
+
}
|
|
3828
|
+
|
|
3720
3829
|
const checkForUpdate = () => {
|
|
3721
3830
|
const seat = hostSeat()
|
|
3722
|
-
|
|
3723
|
-
|
|
3724
|
-
|
|
3725
|
-
}
|
|
3831
|
+
/* 没有 host 座位不是失败 —— 那是**这个形态的插件的常态**(bundle 插件没有 host RPC)。
|
|
3832
|
+
客户端自己抓:这才是它本来该走的路。 */
|
|
3833
|
+
if (seat === null) return clientSideCheck()
|
|
3726
3834
|
updateCheck = { state: 'checking', at: nowMs() }
|
|
3727
3835
|
return Promise.resolve()
|
|
3728
3836
|
.then(() => seat.call('checkUpdate', { current: VERSION }))
|
|
@@ -3735,8 +3843,11 @@ body[data-ds-dark-theme] .wisp-layer{--wisp-say-fg:#eaf2f8;--wisp-say-bg:rgba(20
|
|
|
3735
3843
|
state: 'failed',
|
|
3736
3844
|
reason: (res && res.reason) || 'no-result',
|
|
3737
3845
|
sources: (res && res.sources) || null,
|
|
3846
|
+
/* 宿主看到的诊断也要留下来 —— 失败台词会把它念出来,doctor 里也能查。 */
|
|
3847
|
+
diag: (res && res.diag) || null,
|
|
3738
3848
|
at: nowMs(),
|
|
3739
3849
|
}
|
|
3850
|
+
publishDiagToTitle(updateCheck)
|
|
3740
3851
|
} else if (compareVersions(VERSION, res.latest) < 0) {
|
|
3741
3852
|
/* from / sources 一起留着:两个源不一致时(npm 暂存、GitHub 已更新),
|
|
3742
3853
|
"这个数字是哪儿来的"比数字本身更需要能查。 */
|
|
@@ -3838,17 +3949,73 @@ body[data-ds-dark-theme] .wisp-layer{--wisp-say-fg:#eaf2f8;--wisp-say-bg:rgba(20
|
|
|
3838
3949
|
})
|
|
3839
3950
|
}
|
|
3840
3951
|
|
|
3952
|
+
/* 版本号来自 GitHub,而 npm 上那份**不是同一个号**(通常是 npm 暂存或还没发)——
|
|
3953
|
+
这时候"按包名安装"会把人送回旧版。 */
|
|
3954
|
+
const githubOnlyNewer = (res) => {
|
|
3955
|
+
if (!res || res.state !== 'available' || res.from !== 'github') return false
|
|
3956
|
+
const npm = res.sources && res.sources.npm
|
|
3957
|
+
return !!(npm && npm.ok === true && npm.version !== res.latest)
|
|
3958
|
+
}
|
|
3959
|
+
|
|
3841
3960
|
const updateLine = (res) => {
|
|
3842
3961
|
if (res.state === 'available') return pick(LINES.updateAvailable) + res.latest
|
|
3843
3962
|
if (res.state === 'current') return pick(LINES.updateCurrent)
|
|
3844
3963
|
/* 失败要说清是哪一种:没有通道 / 连不上 / 宿主没应答。
|
|
3845
3964
|
`no-web-service` 是"这个壳根本没有联网入口",不是"网络不好"——
|
|
3846
3965
|
以前两者共用一句"没查到更新",于是诊断只能靠猜。 */
|
|
3847
|
-
|
|
3966
|
+
/* 失败要说清是哪一种 —— 它们是链路上**不同的一环**断了:
|
|
3967
|
+
`unsupported`:客户端这侧连 `host.call` 都没有(第一环);
|
|
3968
|
+
`no-web-service`:宿主跑了,但它拿不到 `web` 服务(第三环);
|
|
3969
|
+
`fetch-failed`:服务拿到了,两个源都连不上(第四环)。 */
|
|
3970
|
+
if (res.state === 'unsupported') return pick(LINES.updateNoSeat)
|
|
3971
|
+
if (res.reason === 'no-web-service') return pick(LINES.updateNoChannel)
|
|
3848
3972
|
if (res.reason === 'call-failed') return pick(LINES.updateNoHost)
|
|
3849
3973
|
return pick(LINES.updateFailed)
|
|
3850
3974
|
}
|
|
3851
3975
|
|
|
3976
|
+
/* 技术的部分**不进台词**(台词是给人读的一句话,塞进去就是一串英文碎片),
|
|
3977
|
+
但必须看得见 —— 失败时把宿主实际看到的排进「关于她」弹窗:
|
|
3978
|
+
取服务的方式、拿到的东西是什么形状、兜底通道为什么也不行。
|
|
3979
|
+
这是"她说查不了"和"我知道为什么"之间的那一步。 */
|
|
3980
|
+
/* 诊断的**临时出口**:把"宿主看见了什么"写进窗口标题。
|
|
3981
|
+
Agent 侧读一下窗口标题就能拿到(不用截图、不用控制台、不用用户复制)——
|
|
3982
|
+
这台机器上的断点已经卡了两轮,值得开一个临时通道把它钉死。
|
|
3983
|
+
120 秒后自动还原;定位到问题后这一条会撤掉。 */
|
|
3984
|
+
let diagTitleTimer = null
|
|
3985
|
+
const publishDiagToTitle = (res) => {
|
|
3986
|
+
try {
|
|
3987
|
+
if (typeof document === 'undefined' || !document || typeof document.title !== 'string') return
|
|
3988
|
+
const diag = (res && res.diag) || {}
|
|
3989
|
+
const short = [
|
|
3990
|
+
'wisp-diag',
|
|
3991
|
+
String((res && res.state) || '?'),
|
|
3992
|
+
String((res && res.reason) || '-'),
|
|
3993
|
+
'via=' + String(diag.via || '-'),
|
|
3994
|
+
'how=' + String(diag.how || '-'),
|
|
3995
|
+
'why=' + String(diag.why || '-'),
|
|
3996
|
+
'mgr=' + String(diag.manager || '-'),
|
|
3997
|
+
].join(' ')
|
|
3998
|
+
const original = document.title
|
|
3999
|
+
document.title = short
|
|
4000
|
+
if (diagTitleTimer !== null) clock.cancel(diagTitleTimer)
|
|
4001
|
+
diagTitleTimer = clock.timeout(() => {
|
|
4002
|
+
try { document.title = original } catch (e) { /* ignore */ }
|
|
4003
|
+
diagTitleTimer = null
|
|
4004
|
+
}, 120000)
|
|
4005
|
+
} catch (e) { /* 标题写不了不该影响任何别的功能 */ }
|
|
4006
|
+
}
|
|
4007
|
+
|
|
4008
|
+
const describeDiag = (res) => {
|
|
4009
|
+
const diag = res && res.diag
|
|
4010
|
+
if (!diag || typeof diag !== 'object') return '这次连诊断信息都没有拿到。'
|
|
4011
|
+
const lines = ['通道:' + String(diag.via || '未知')]
|
|
4012
|
+
if (diag.how) lines.push('取服务的方式:' + String(diag.how))
|
|
4013
|
+
if (diag.why) lines.push('看到的东西:' + String(diag.why))
|
|
4014
|
+
if (diag.registry) lines.push('插件管理器的 registry:' + String(diag.registry))
|
|
4015
|
+
if (diag.manager) lines.push('兜底通道:' + String(diag.manager))
|
|
4016
|
+
return lines.join('\n')
|
|
4017
|
+
}
|
|
4018
|
+
|
|
3852
4019
|
/* 把包名复制到剪贴板,便于在 DSH 的「按插件包名安装」里粘贴。
|
|
3853
4020
|
没有剪贴板权限(或不是安全上下文)时**静默降级**成只说话 —— 不该因此报错。 */
|
|
3854
4021
|
const copyText = (text) => {
|
|
@@ -3868,6 +4035,7 @@ body[data-ds-dark-theme] .wisp-layer{--wisp-say-fg:#eaf2f8;--wisp-say-bg:rgba(20
|
|
|
3868
4035
|
用户点这一项想知道的就是这两件事。 */
|
|
3869
4036
|
const showUpdate = () => {
|
|
3870
4037
|
const note = WHATS_NEW[VERSION]
|
|
4038
|
+
let hint = UPDATE_HINT
|
|
3871
4039
|
const copied = copyText('dsh-wisp')
|
|
3872
4040
|
/* 一步到位:先把"我是谁、这一版做了什么"说出来,同时去查有没有新版;
|
|
3873
4041
|
查回来再用一句话覆盖掉 —— 一次只冒一个气泡,所以是"说完结果就结束"。 */
|
|
@@ -3878,9 +4046,18 @@ body[data-ds-dark-theme] .wisp-layer{--wisp-say-fg:#eaf2f8;--wisp-say-bg:rgba(20
|
|
|
3878
4046
|
started.then((res) => {
|
|
3879
4047
|
if (destroyed) return
|
|
3880
4048
|
if (res.state === 'checking') return
|
|
4049
|
+
/* 新版只在 GitHub 上 → 改口 + 把仓库地址放进剪贴板(包名会装回旧版)。 */
|
|
4050
|
+
if (githubOnlyNewer(res)) {
|
|
4051
|
+
hint = UPDATE_HINT_GITHUB
|
|
4052
|
+
lastUpdateHint = hint
|
|
4053
|
+
copyText(UPDATE_REPO)
|
|
4054
|
+
}
|
|
3881
4055
|
flashHappy(base + updateLine(res))
|
|
4056
|
+
/* 查不成的时候:把"为什么"摆到弹窗里(台词只留一句人话)。 */
|
|
4057
|
+
if (res.state === 'failed') aboutDialog(res)
|
|
3882
4058
|
})
|
|
3883
4059
|
}
|
|
4060
|
+
lastUpdateHint = hint
|
|
3884
4061
|
return { version: VERSION, note: note || null, copied, checking: true }
|
|
3885
4062
|
}
|
|
3886
4063
|
|
|
@@ -4040,7 +4217,7 @@ body[data-ds-dark-theme] .wisp-layer{--wisp-say-fg:#eaf2f8;--wisp-say-bg:rgba(20
|
|
|
4040
4217
|
version: VERSION,
|
|
4041
4218
|
whatsNew: WHATS_NEW[VERSION] ?? null,
|
|
4042
4219
|
seenBefore: readStored(SEEN_VERSION_KEY),
|
|
4043
|
-
hint:
|
|
4220
|
+
hint: lastUpdateHint,
|
|
4044
4221
|
canSelfUpdate: false,
|
|
4045
4222
|
/* 上一次检查的结果(idle / checking / current / available / failed / unsupported) */
|
|
4046
4223
|
lastCheck: { ...updateCheck },
|
package/lib/index.js
CHANGED
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
export const name = 'wisp'
|
|
33
33
|
|
|
34
34
|
/** Stamped into every balance request. Must match package.json — the preflight guards it. */
|
|
35
|
-
export const VERSION = '1.
|
|
35
|
+
export const VERSION = '1.44.0'
|
|
36
36
|
|
|
37
37
|
/**
|
|
38
38
|
* `web` and `deepseekAccount` are optional on purpose. A required inject that
|
|
@@ -50,8 +50,23 @@ export const DEFAULT_SOURCES = {
|
|
|
50
50
|
/** How long ONE source may take before it is reported as a timeout. */
|
|
51
51
|
const CHECK_TIMEOUT_MS = 8000
|
|
52
52
|
|
|
53
|
+
/** 这个包自己的名字 —— 兜底通道(问插件管理器)按包名查。 */
|
|
54
|
+
const UPDATE_PACKAGE_NAME = 'dsh-wisp'
|
|
55
|
+
|
|
56
|
+
/** 一眼看懂一个"服务"到底是什么(诊断用;永远不抛)。 */
|
|
57
|
+
function describeService(value) {
|
|
58
|
+
if (value === undefined) return 'undefined'
|
|
59
|
+
if (value === null) return 'null'
|
|
60
|
+
const type = typeof value
|
|
61
|
+
if (type !== 'object' && type !== 'function') return type
|
|
62
|
+
try {
|
|
63
|
+
const keys = Object.keys(value).slice(0, 6).join(',')
|
|
64
|
+
return type + '{' + keys + '}' + (typeof value.fetch === 'function' ? '+fetch' : '-fetch')
|
|
65
|
+
} catch (error) { return type + '(unreadable)' }
|
|
66
|
+
}
|
|
67
|
+
|
|
53
68
|
/**
|
|
54
|
-
* Resolve the `web` service LATE, on every call.
|
|
69
|
+
* Resolve the `web` service LATE, on every call — and say HOW, and what was found.
|
|
55
70
|
*
|
|
56
71
|
* `inject.optional: ['web']` hands us `ctx.web`, but only when the service was
|
|
57
72
|
* already provided at the moment this fiber materialized — and this half is
|
|
@@ -60,20 +75,75 @@ const CHECK_TIMEOUT_MS = 8000
|
|
|
60
75
|
* which would disable the update check forever. `ctx.get('web')` looks the
|
|
61
76
|
* service up at call time instead, so a startup race cannot wedge it.
|
|
62
77
|
*
|
|
63
|
-
*
|
|
78
|
+
* 真实运行里两种路都拿不到时,**必须能说清是哪一种**:`ctx.get` 有没有?
|
|
79
|
+
* 拿到的东西是什么形状?—— 没有这一层,"没有联网的通道"是一句无法追问的话。
|
|
80
|
+
*
|
|
81
|
+
* @returns `{ service, how, why }` — never throws.
|
|
82
|
+
* how: 'ctx.get' | 'ctx.web' | 'none' 拿到了没有、从哪拿的
|
|
83
|
+
* why: 一句话说明"看到了什么"(进了 doctor 与她的失败台词)
|
|
64
84
|
*/
|
|
65
85
|
export function resolveWebService(ctx) {
|
|
86
|
+
let late
|
|
87
|
+
let getThrew = null
|
|
66
88
|
try {
|
|
67
|
-
if (ctx && typeof ctx.get === 'function')
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
89
|
+
if (ctx && typeof ctx.get === 'function') late = ctx.get('web')
|
|
90
|
+
else getThrew = 'ctx.get 不是函数(' + describeService(ctx && ctx.get) + ')'
|
|
91
|
+
} catch (error) { getThrew = 'ctx.get 抛错:' + String(error && error.message ? error.message : error).slice(0, 60) }
|
|
92
|
+
if (late && typeof late.fetch === 'function') {
|
|
93
|
+
return { service: late, how: 'ctx.get', why: 'ctx.get("web")→' + describeService(late) }
|
|
94
|
+
}
|
|
95
|
+
let declared
|
|
96
|
+
let webThrew = null
|
|
97
|
+
try { declared = ctx && ctx.web } catch (error) { webThrew = String(error && error.message ? error.message : error).slice(0, 60) }
|
|
98
|
+
if (declared && typeof declared.fetch === 'function') {
|
|
99
|
+
return { service: declared, how: 'ctx.web', why: 'ctx.web→' + describeService(declared) }
|
|
100
|
+
}
|
|
101
|
+
const parts = []
|
|
102
|
+
parts.push('ctx.get("web")→' + describeService(late))
|
|
103
|
+
if (getThrew) parts.push(getThrew)
|
|
104
|
+
parts.push('ctx.web→' + describeService(declared))
|
|
105
|
+
if (webThrew) parts.push('ctx.web 读取抛错:' + webThrew)
|
|
106
|
+
/* 拿到了对象但没有 fetch 也要如实说 —— "有服务但没这个方法"和"根本没有服务"是两件事 */
|
|
107
|
+
const service = (late && typeof late === 'object') ? late : ((declared && typeof declared === 'object') ? declared : undefined)
|
|
108
|
+
if (service) {
|
|
109
|
+
return { service, how: 'partial', why: parts.join('; ') + '(无 fetch)' }
|
|
110
|
+
}
|
|
111
|
+
return { service: undefined, how: 'none', why: parts.join('; ') }
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* 兜底通道:向插件管理器问"这个包现在是什么版本"。
|
|
116
|
+
*
|
|
117
|
+
* 它本来就是**去 registry 问这个包**的那个人(安装前先 inspect),所以这条路
|
|
118
|
+
* 走的是应用自己那条网络出口、且尊重用户配的镜像源 —— 比我们自己去抓 packument
|
|
119
|
+
* 更接近"安装时会装到哪一版"。`web` 服务拿不到时,这是唯一还活着的更新通道。
|
|
120
|
+
*
|
|
121
|
+
* @returns `{ ok, version, registry }` 或 `{ ok: false, reason, detail }` — never throws.
|
|
122
|
+
*/
|
|
123
|
+
export async function readManagedVersion(ctx, name, timeoutMs) {
|
|
124
|
+
let manager
|
|
72
125
|
try {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
} catch (error) {
|
|
76
|
-
|
|
126
|
+
if (!ctx || typeof ctx.get !== 'function') return { ok: false, reason: 'no-ctx-get' }
|
|
127
|
+
manager = ctx.get('pluginManager')
|
|
128
|
+
} catch (error) {
|
|
129
|
+
return { ok: false, reason: 'manager-unreachable', detail: String(error && error.message ? error.message : error).slice(0, 80) }
|
|
130
|
+
}
|
|
131
|
+
if (!manager || typeof manager.inspect !== 'function') {
|
|
132
|
+
return { ok: false, reason: 'no-plugin-manager', detail: describeService(manager) }
|
|
133
|
+
}
|
|
134
|
+
try {
|
|
135
|
+
const info = await withTimeout(manager.inspect(name), timeoutMs)
|
|
136
|
+
if (info && info.status === 'accepted' && typeof info.version === 'string') {
|
|
137
|
+
return { ok: true, version: info.version, registry: info.registry ?? null }
|
|
138
|
+
}
|
|
139
|
+
return {
|
|
140
|
+
ok: false,
|
|
141
|
+
reason: 'inspect-refused',
|
|
142
|
+
detail: info && info.reason ? String(info.reason).slice(0, 100) : describeService(info),
|
|
143
|
+
}
|
|
144
|
+
} catch (error) {
|
|
145
|
+
return { ok: false, reason: 'inspect-failed', detail: String(error && error.message ? error.message : error).slice(0, 100) }
|
|
146
|
+
}
|
|
77
147
|
}
|
|
78
148
|
|
|
79
149
|
/**
|
|
@@ -84,7 +154,10 @@ export function resolveWebService(ctx) {
|
|
|
84
154
|
* @returns `{ ok, version }` or `{ ok: false, reason }` — never throws.
|
|
85
155
|
*/
|
|
86
156
|
export async function readVersion(web, url, signal) {
|
|
87
|
-
if (!web
|
|
157
|
+
if (!web) return { ok: false, reason: 'no-web-service' }
|
|
158
|
+
/* "有服务但没有 fetch"和"根本没有服务"是两件事 —— 台词的下一步动作不同
|
|
159
|
+
(一个是应用/平台的问题,一个是插件注入的问题),所以别合并成一句。 */
|
|
160
|
+
if (typeof web.fetch !== 'function') return { ok: false, reason: 'web-has-no-fetch', detail: describeService(web) }
|
|
88
161
|
try {
|
|
89
162
|
const res = await web.fetch({ url }, signal)
|
|
90
163
|
if (!res || typeof res.statusCode !== 'number') return { ok: false, reason: 'bad-result' }
|
|
@@ -305,9 +378,38 @@ export function registerHandlers(ctx, options = {}) {
|
|
|
305
378
|
const dispose = seat.handle('checkUpdate', async (args) => {
|
|
306
379
|
const current = args && typeof args.current === 'string' ? args.current : null
|
|
307
380
|
/* 服务**在调用时**解析,不用闭包里那个可能早就定格的 ctx.web(见 resolveWebService)。 */
|
|
308
|
-
const
|
|
309
|
-
|
|
310
|
-
|
|
381
|
+
const resolved = resolveWebService(ctx)
|
|
382
|
+
if (resolved.service) {
|
|
383
|
+
const verdict = await readPublishedVersion(resolved.service, urls, undefined, CHECK_TIMEOUT_MS)
|
|
384
|
+
return { ...verdict, current, diag: { via: 'web', how: resolved.how, why: resolved.why }, checkedAt: new Date().toISOString() }
|
|
385
|
+
}
|
|
386
|
+
/* 没有可用的 web 服务时的兜底:问插件管理器"这个包现在是什么版本"。
|
|
387
|
+
它走的是应用自己那条网络出口(安装前就要去 registry 问),所以这条路
|
|
388
|
+
是"安装时会装到哪一版"的权威答案 —— 而且不依赖插件能否拿到 web。 */
|
|
389
|
+
const managed = await readManagedVersion(ctx, UPDATE_PACKAGE_NAME, CHECK_TIMEOUT_MS)
|
|
390
|
+
const sources = { npm: managed.ok
|
|
391
|
+
? { ok: true, version: managed.version }
|
|
392
|
+
: { ok: false, reason: managed.reason, detail: managed.detail } }
|
|
393
|
+
if (managed.ok) {
|
|
394
|
+
return {
|
|
395
|
+
ok: true,
|
|
396
|
+
latest: managed.version,
|
|
397
|
+
from: 'npm',
|
|
398
|
+
sources,
|
|
399
|
+
current,
|
|
400
|
+
diag: { via: 'pluginManager', how: resolved.how, why: resolved.why, registry: managed.registry ?? null },
|
|
401
|
+
checkedAt: new Date().toISOString(),
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
sources.github = { ok: false, reason: 'no-web-service', detail: resolved.why }
|
|
405
|
+
return {
|
|
406
|
+
ok: false,
|
|
407
|
+
reason: 'no-web-service',
|
|
408
|
+
sources,
|
|
409
|
+
current,
|
|
410
|
+
diag: { via: 'none', how: resolved.how, why: resolved.why, manager: managed.reason + (managed.detail ? ':' + managed.detail : '') },
|
|
411
|
+
checkedAt: new Date().toISOString(),
|
|
412
|
+
}
|
|
311
413
|
})
|
|
312
414
|
/* The balance handler. `ctx.deepseekAccount` is undefined unless the platform
|
|
313
415
|
provides it — readBalance() turns that into `no-account-service` rather than
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-wisp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.44.0",
|
|
4
4
|
"description": "DeepSeek娘 — an unofficial floating desktop companion for the DeepSeek Harness UI. Zero dependencies; sprites inlined as data URIs; schedules on the Client timer service instead of the trapped browser timer globals.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|