dsh-chat-embed 0.6.0 → 0.6.2
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 +7 -3
- package/client.js +75 -27
- package/docs/architecture.md +2 -2
- package/docs/publishing.md +4 -4
- package/index.js +86 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
| `locale/{en,zh}.json` | 插件管理器里显示的标题与简介 |
|
|
21
21
|
| `cordis.patch.yml` | 组合补丁:向 profile 插入 `id: chat-embed` 的 loader 行 |
|
|
22
22
|
| `scripts/verify-bundle.mjs` | 门禁:语法 / 清单 / loader 契约 / 服务注册表 / 显示资源 |
|
|
23
|
-
| `test/` | 纯 Node 契约测试(伪造 DOM +
|
|
23
|
+
| `test/` | 纯 Node 契约测试(伪造 DOM + 伪造桌面桥 + 伪造 asar),81 项 |
|
|
24
24
|
| `docs/architecture.md` | 内嵌契约、席位架构、壳补丁、升级复验清单(**接到项目先读这份**) |
|
|
25
25
|
| `tools/shell-patch/` | 可选的桌面壳补丁:持久分区(跨重启免登录)+ 关闭磁盘缓存 |
|
|
26
26
|
| `清理聊天缓存.cmd` / `restore-shell-patch.cmd` | 缓存回收 / 还原原版壳 |
|
|
@@ -190,6 +190,8 @@ node tools/shell-patch/patch-asar.cjs --help # 查看参数
|
|
|
190
190
|
|
|
191
191
|
「检查补丁配置」调用 Host 侧只读探针(`GET /dsh-chat-embed/patch-check`),报告安装目录、`app.asar` 是否可写、是否已打补丁、补丁点是否还在(壳版本是否兼容)、备份与待替换文件是否存在、`app.asar` 的大小与内容哈希;报告同时复制到剪贴板,方便直接贴出去。
|
|
192
192
|
|
|
193
|
+
**不需要自己装 Node.js**:探针会找出桌面版自带的 Node 运行时(`~/.dsh/dsh-runtimes/*/dependencies/node/`),并把一条完整可复制的命令写进报告——含正确的 Node 路径、补丁脚本路径和 `--resources` 安装目录(自定义/便携安装也能用)。
|
|
194
|
+
|
|
193
195
|
Host 侧还注册了只读工具 **`dsh_chat_embed_patch_check`**:可以直接问 agent「我的聊天为什么重启后要重新登录」,它会自己去查这些事实,而不是让你跑命令再贴输出。
|
|
194
196
|
|
|
195
197
|
补丁做两件事:`persist:` 固定分区(登录落盘)+ `cache:false`(不落 Chromium 缓存)。
|
|
@@ -212,7 +214,7 @@ dsh-chat-embed/
|
|
|
212
214
|
├── package.json # 包名/版本、dsh.bundle.patch、dsh.client、exports/files
|
|
213
215
|
├── cordis.patch.yml # 组合补丁:插入 id=chat-embed 的 loader 行
|
|
214
216
|
├── index.js # Host 半侧:补丁探针(HTTP + agent 工具)
|
|
215
|
-
├── client.js # Client 半侧(约
|
|
217
|
+
├── client.js # Client 半侧(约 3912 行,手写产物)
|
|
216
218
|
├── locale/{en,zh}.json # 插件管理器显示用 meta
|
|
217
219
|
├── icon.svg # 插件图标(插件管理器显示)
|
|
218
220
|
├── assets/
|
|
@@ -262,7 +264,7 @@ git clone <repo-url> dsh-chat-embed
|
|
|
262
264
|
cd dsh-chat-embed
|
|
263
265
|
|
|
264
266
|
# 无需安装依赖;直接跑门禁与测试
|
|
265
|
-
npm run check # 语法 + 清单 + 显示资源 + 服务注册表 +
|
|
267
|
+
npm run check # 语法 + 清单 + 显示资源 + 服务注册表 + 81 项测试
|
|
266
268
|
npm test # 只跑测试
|
|
267
269
|
```
|
|
268
270
|
|
|
@@ -313,6 +315,8 @@ plugin_manager { "action": "install_bundle", "target": "<本仓库克隆路径>"
|
|
|
313
315
|
|
|
314
316
|
| 版本 | 变化 |
|
|
315
317
|
| --- | --- |
|
|
318
|
+
| `0.6.2` | 修复设置菜单被长路径撑宽:菜单加 `max-width`、报告文本 `overflow-wrap:anywhere`、补丁命令单独占一行(便于整条复制) |
|
|
319
|
+
| `0.6.1` | 探针找出桌面版自带的 Node 运行时,自检报告与「复制补丁说明」直接给出可执行命令(含 `--resources`,支持自定义安装目录);无需用户另装 Node |
|
|
316
320
|
| `0.6.0` | 补丁可观测性:四态检测(未打 / 已开启 / **升级后失效** / 未检测)、未开启时的风险提示、菜单内「检查补丁配置」自检(Host 只读探针 `GET /dsh-chat-embed/patch-check`)、agent 工具 `dsh_chat_embed_patch_check`;Host 半侧改为读取真实 `app.asar`(`original-fs`,绕过 Electron 的 asar 目录拦截) |
|
|
317
321
|
| `0.5.4` | 语言切换即时生效:翻译器改为 locale 绑定的单一来源,监听 `locale/change` 重渲染(不再依赖未注入的 `props.t`);补语言切换测试 |
|
|
318
322
|
| `0.5.3` | 修复 `locale` 探针(`t` 作用域错误)并补测试;诊断读数在测试环境下的 `<html lang>` 兼容 |
|
package/client.js
CHANGED
|
@@ -103,7 +103,7 @@ window.__ModuleLoader__.load({
|
|
|
103
103
|
let openRightTabImpl = () => {}
|
|
104
104
|
|
|
105
105
|
/** Build identity, surfaced in the panel so a packaged app can be diagnosed. */
|
|
106
|
-
const BUILD = '0.6.
|
|
106
|
+
const BUILD = '0.6.2'
|
|
107
107
|
|
|
108
108
|
/** Where a re-applied instance finds the one it must take over from. */
|
|
109
109
|
const INSTANCE_KEY = '__DSH_CHAT_EMBED_INSTANCE__'
|
|
@@ -2433,23 +2433,36 @@ window.__ModuleLoader__.load({
|
|
|
2433
2433
|
* @param {(key: string) => string} t
|
|
2434
2434
|
* @returns {string}
|
|
2435
2435
|
*/
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
''
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2436
|
+
/**
|
|
2437
|
+
* The clipboard copy behind "copy the patch instructions".
|
|
2438
|
+
*
|
|
2439
|
+
* The Host's probe supplies the exact command — including the Node binary
|
|
2440
|
+
* that ships with the desktop app, so the user is not asked to install one.
|
|
2441
|
+
*
|
|
2442
|
+
* @param {(key: string) => string} t
|
|
2443
|
+
* @param {any} [probe]
|
|
2444
|
+
* @returns {string}
|
|
2445
|
+
*/
|
|
2446
|
+
function patchInstructions(t, probe) {
|
|
2447
|
+
const info = probe !== undefined && probe !== null ? probe : {}
|
|
2448
|
+
const command =
|
|
2449
|
+
typeof info.patchCommand === 'string' && info.patchCommand !== ''
|
|
2450
|
+
? info.patchCommand
|
|
2451
|
+
: 'node tools/shell-patch/patch-asar.cjs build'
|
|
2452
|
+
const lines = [t('persistLabel') + ':' + t('persistHint'), '']
|
|
2453
|
+
lines.push('1) 执行下面这条命令(Desk 自带的 Node 运行时;无需另装 Node):')
|
|
2454
|
+
lines.push(' ' + command)
|
|
2455
|
+
lines.push(' 脚本会自动探测安装目录并逐字节校验,生成 app.asar.new。')
|
|
2456
|
+
lines.push('')
|
|
2457
|
+
lines.push('2) 完全退出 DSH 后替换(任选其一):')
|
|
2458
|
+
lines.push(' - 运行 tools/shell-patch/watchdog.ps1,退出瞬间自动替换;')
|
|
2459
|
+
lines.push(' - 手动把 resources\\app.asar.new 改名为 app.asar。')
|
|
2460
|
+
lines.push('')
|
|
2461
|
+
lines.push('3) 重启 DSH,回到这里应显示「' + t('persistOn') + '」。')
|
|
2462
|
+
lines.push('')
|
|
2463
|
+
lines.push('回滚:restore-shell-patch.cmd(把 app.asar.orig 换回去)。')
|
|
2464
|
+
lines.push('Windows 之外的安装目录请用 --resources 指定。')
|
|
2465
|
+
return lines.join('\n')
|
|
2453
2466
|
}
|
|
2454
2467
|
|
|
2455
2468
|
/**
|
|
@@ -2461,6 +2474,9 @@ window.__ModuleLoader__.load({
|
|
|
2461
2474
|
*/
|
|
2462
2475
|
const patchCheck = { running: false, report: '' }
|
|
2463
2476
|
|
|
2477
|
+
/** One-shot cache of the Host probe, so both actions read the same facts. */
|
|
2478
|
+
const patchProbe = { loaded: false, value: undefined }
|
|
2479
|
+
|
|
2464
2480
|
/**
|
|
2465
2481
|
* Ask the Host half for the shell facts and render a copyable report.
|
|
2466
2482
|
* Nothing here writes: the button only inspects.
|
|
@@ -2468,11 +2484,15 @@ window.__ModuleLoader__.load({
|
|
|
2468
2484
|
* @param {(key: string) => string} t
|
|
2469
2485
|
* @returns {Promise<void>}
|
|
2470
2486
|
*/
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2487
|
+
/**
|
|
2488
|
+
* Ask the Host for the shell facts once per session. Both the copy action
|
|
2489
|
+
* and the self-check read it, so the instructions never disagree with the
|
|
2490
|
+
* report.
|
|
2491
|
+
*
|
|
2492
|
+
* @returns {Promise<any>} the probe, or undefined when the route is absent
|
|
2493
|
+
*/
|
|
2494
|
+
async function fetchProbe() {
|
|
2495
|
+
if (patchProbe.loaded) return patchProbe.value
|
|
2476
2496
|
try {
|
|
2477
2497
|
// The Host half serves this route; a relative fetch therefore works in
|
|
2478
2498
|
// both the desktop app and the Web GUI, under any deployment prefix.
|
|
@@ -2481,7 +2501,23 @@ window.__ModuleLoader__.load({
|
|
|
2481
2501
|
headers: { accept: 'application/json' },
|
|
2482
2502
|
cache: 'no-store',
|
|
2483
2503
|
})
|
|
2484
|
-
|
|
2504
|
+
patchProbe.value = response.ok ? await response.json() : undefined
|
|
2505
|
+
} catch (error) {
|
|
2506
|
+
patchProbe.value = undefined
|
|
2507
|
+
void error
|
|
2508
|
+
}
|
|
2509
|
+
patchProbe.loaded = true
|
|
2510
|
+
return patchProbe.value
|
|
2511
|
+
}
|
|
2512
|
+
|
|
2513
|
+
async function runPatchCheck(t) {
|
|
2514
|
+
if (patchCheck.running) return
|
|
2515
|
+
patchCheck.running = true
|
|
2516
|
+
notify()
|
|
2517
|
+
let report = ''
|
|
2518
|
+
try {
|
|
2519
|
+
patchProbe.loaded = false
|
|
2520
|
+
const probe = await fetchProbe()
|
|
2485
2521
|
report = formatPatchReport(t, probe)
|
|
2486
2522
|
} catch (error) {
|
|
2487
2523
|
report = formatPatchReport(t, undefined) + '\n' + t('copyFailed') + ': ' + messageOf(error)
|
|
@@ -2516,7 +2552,17 @@ window.__ModuleLoader__.load({
|
|
|
2516
2552
|
row('补丁点', probe.patchSite === true ? '存在' : probe.patchSite === false ? '不存在(壳版本不匹配)' : '未知')
|
|
2517
2553
|
row('已打补丁', probe.patched === true ? '是' : probe.patched === false ? '否' : '未知')
|
|
2518
2554
|
row('node', probe.node)
|
|
2555
|
+
if (probe.nodeBinary === undefined) {
|
|
2556
|
+
lines.push('(没有找到可用的 Node 运行时,需要先安装 Node.js 22 或更新)')
|
|
2557
|
+
}
|
|
2519
2558
|
row('说明', probe.detail)
|
|
2559
|
+
// The command is long and path-heavy: give it its own indented block so a
|
|
2560
|
+
// copy takes the command alone and the menu wraps instead of stretching.
|
|
2561
|
+
if (typeof probe.patchCommand === 'string' && probe.patchCommand !== '') {
|
|
2562
|
+
lines.push('')
|
|
2563
|
+
lines.push('补丁命令:')
|
|
2564
|
+
lines.push(probe.patchCommand)
|
|
2565
|
+
}
|
|
2520
2566
|
return lines.join('\n')
|
|
2521
2567
|
}
|
|
2522
2568
|
|
|
@@ -2735,14 +2781,14 @@ window.__ModuleLoader__.load({
|
|
|
2735
2781
|
'.dce-card-detail{font-family:var(--ds-font-family-code,ui-monospace,monospace);font-size:11px;color:var(--dsw-alias-label-tertiary,#9aa0a6);word-break:break-all}',
|
|
2736
2782
|
'.dce-card-actions{display:flex;gap:8px;justify-content:flex-end}',
|
|
2737
2783
|
'.dce-menu-scrim{position:absolute;inset:0;z-index:5}',
|
|
2738
|
-
'.dce-menu{position:absolute;top:34px;right:6px;z-index:6;min-width:196px;box-sizing:border-box;padding:6px;border:1px solid var(--dsw-alias-border-l1,rgba(127,127,127,.24));border-radius:10px;background:var(--dsw-alias-bg-layer-2,#fff);box-shadow:0 12px 32px rgba(0,0,0,.18);display:flex;flex-direction:column;gap:1px}',
|
|
2784
|
+
'.dce-menu{position:absolute;top:34px;right:6px;z-index:6;min-width:196px;max-width:min(420px,calc(100% - 12px));box-sizing:border-box;padding:6px;border:1px solid var(--dsw-alias-border-l1,rgba(127,127,127,.24));border-radius:10px;background:var(--dsw-alias-bg-layer-2,#fff);box-shadow:0 12px 32px rgba(0,0,0,.18);display:flex;flex-direction:column;gap:1px}',
|
|
2739
2785
|
'.dce-menu-title{font-size:11px;color:var(--dsw-alias-label-tertiary,#9aa0a6);padding:6px 8px 4px;letter-spacing:.04em}',
|
|
2740
2786
|
'.dce-menu-row{display:flex;align-items:center;justify-content:space-between;gap:14px;padding:5px 8px;border-radius:6px;font-size:13px;color:var(--dsw-alias-label-primary,#1f2329)}',
|
|
2741
2787
|
'.dce-menu-row:hover{background:var(--dsw-alias-interactive-bg-hover,rgba(127,127,127,.12))}',
|
|
2742
2788
|
'.dce-menu-label{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}',
|
|
2743
2789
|
'.dce-menu-value{flex:0 0 auto;font-size:12px;color:var(--dsw-alias-label-tertiary,#9aa0a6)}',
|
|
2744
2790
|
'.dce-menu-sep{margin:5px 0 3px;border-top:1px solid var(--dsw-alias-border-l1,rgba(127,127,127,.24))}',
|
|
2745
|
-
'.dce-menu-note{margin:4px 8px 6px;font-size:11.5px;line-height:1.5;color:var(--dsw-alias-label-tertiary,#9aa0a6);white-space:pre-wrap;word-break:break-word}',
|
|
2791
|
+
'.dce-menu-note{margin:4px 8px 6px;font-size:11.5px;line-height:1.5;color:var(--dsw-alias-label-tertiary,#9aa0a6);white-space:pre-wrap;overflow-wrap:anywhere;word-break:break-word;max-width:100%;box-sizing:border-box}',
|
|
2746
2792
|
'.dce-menu-note[data-persist-risk="true"]{color:var(--dsw-alias-label-warning,#c98b16)}',
|
|
2747
2793
|
'.dce-menu-note[data-patch-report="true"]{max-height:180px;overflow:auto;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11px}',
|
|
2748
2794
|
'.dce-menu-item{display:flex;align-items:center;justify-content:flex-start;gap:8px;width:100%;box-sizing:border-box;padding:6px 8px;border:0;border-radius:6px;background:transparent;color:inherit;font-size:13px;text-align:left;cursor:pointer}',
|
|
@@ -3417,7 +3463,7 @@ window.__ModuleLoader__.load({
|
|
|
3417
3463
|
className: 'dce-menu-item',
|
|
3418
3464
|
'data-patch-help': 'true',
|
|
3419
3465
|
onClick: () => {
|
|
3420
|
-
void copyText(patchInstructions(t))
|
|
3466
|
+
void fetchProbe().then((probe) => copyText(patchInstructions(t, probe)))
|
|
3421
3467
|
},
|
|
3422
3468
|
},
|
|
3423
3469
|
h('span', { className: 'dce-menu-label' }, t('persistCopy')),
|
|
@@ -3712,6 +3758,8 @@ window.__ModuleLoader__.load({
|
|
|
3712
3758
|
storePersistenceSeen,
|
|
3713
3759
|
livePartitionState,
|
|
3714
3760
|
patchInstructions,
|
|
3761
|
+
fetchProbe,
|
|
3762
|
+
patchProbe,
|
|
3715
3763
|
formatPatchReport,
|
|
3716
3764
|
runPatchCheck,
|
|
3717
3765
|
patchCheck,
|
package/docs/architecture.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
本文记录 `client.js` 里每一条**反直觉**的实现决策、支撑它的证据,以及升级桌面壳时必须重新验证的清单。
|
|
4
4
|
|
|
5
5
|
目标运行环境:**`@deepseek-ai/dsh-desktop 0.2.2-rc.1`(Electron / Chromium)**。
|
|
6
|
-
当前实现:**v0.6.
|
|
6
|
+
当前实现:**v0.6.2**(`client.js` 约 3912 行,82 项契约测试)。
|
|
7
7
|
|
|
8
8
|
> **架构一句话**:聊天页直接渲染在**席位的布局盒**里(`.dce-panel.dce-inline`,`width/height:100%`),几何完全交给 CSS;席位只有两个 —— 中央主面板(`main`)和右栏聊天 tab —— 二者互斥,**哪边点哪边接管**,切换时 guest 重建一次并回到上次会话。
|
|
9
9
|
|
|
@@ -330,7 +330,7 @@ Host 半侧另有两条只读通道,专门用来回答"为什么重启后要
|
|
|
330
330
|
|
|
331
331
|
| 通道 | 内容 |
|
|
332
332
|
| --- | --- |
|
|
333
|
-
| `GET /dsh-chat-embed/patch-check` | 安装目录、`app.asar`
|
|
333
|
+
| `GET /dsh-chat-embed/patch-check` | 安装目录、`app.asar` 是否可写、是否已打补丁、补丁点是否还存在、备份与待替换文件、大小与内容哈希、**可执行的补丁命令**(含自带 Node 运行时路径与 `--resources`) |
|
|
334
334
|
| agent 工具 `dsh_chat_embed_patch_check` | 同一份读数,供模型直接查询(只读,无需审批) |
|
|
335
335
|
|
|
336
336
|
> 读取 `app.asar` 必须走 Electron 的 `original-fs`:被补丁过的 `node:fs` 会把以 `.asar` 结尾的路径当成目录,`readFileSync` 直接 ENOENT。探针优先用 `original-fs`,普通 Node 下回落到 `node:fs`。
|
package/docs/publishing.md
CHANGED
|
@@ -5,13 +5,13 @@
|
|
|
5
5
|
## 版本与提交约定
|
|
6
6
|
|
|
7
7
|
- 同时改 `package.json` 的 `version` 与 `client.js` 的 `BUILD`,两者必须一致;
|
|
8
|
-
- 门禁必须绿:`npm run check`(`verify-bundle` +
|
|
8
|
+
- 门禁必须绿:`npm run check`(`verify-bundle` + 82 项契约测试);
|
|
9
9
|
- 代码注释/提交信息英文,文档中文。
|
|
10
10
|
|
|
11
11
|
## 发版步骤
|
|
12
12
|
|
|
13
13
|
```powershell
|
|
14
|
-
npm run check # 门禁:verify-bundle +
|
|
14
|
+
npm run check # 门禁:verify-bundle + 82 项测试(prepublishOnly 也会跑)
|
|
15
15
|
npm run pack:dist # 产出 dist\dsh-chat-embed-<版本>.tgz
|
|
16
16
|
gh release create v0.5.0 dist\dsh-chat-embed-0.5.0.tgz --title "v0.5.0" --notes-file dist\release-notes.md
|
|
17
17
|
```
|
|
@@ -31,8 +31,8 @@ Get-FileHash $env:TEMP\check\dsh-chat-embed-0.5.0.tgz -Algorithm SHA256
|
|
|
31
31
|
| --- | --- |
|
|
32
32
|
| 仓库 | <https://github.com/poluce/dsh-chat-embed> |
|
|
33
33
|
| Release | <https://github.com/poluce/dsh-chat-embed/releases> |
|
|
34
|
-
| 分发包 | `dsh-chat-embed-0.6.
|
|
35
|
-
| npm | `dsh-chat-embed@0.6.
|
|
34
|
+
| 分发包 | `dsh-chat-embed-0.6.2.tgz`(16 个文件,约 70 kB) |
|
|
35
|
+
| npm | `dsh-chat-embed@0.6.2`(<https://www.npmjs.com/package/dsh-chat-embed>,维护者 `poluce_auto`) |
|
|
36
36
|
| 商店图标 | `assets/icon-180.png`(180×180 PNG) |
|
|
37
37
|
| 商店截图 | `assets/screenshots/` + 仓库根 `screenshots.json`(6 张) |
|
|
38
38
|
|
package/index.js
CHANGED
|
@@ -18,10 +18,11 @@
|
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
20
|
import { createHash } from 'node:crypto'
|
|
21
|
-
import { accessSync, constants, existsSync, readFileSync, statSync } from 'node:fs'
|
|
21
|
+
import { accessSync, constants, existsSync, readdirSync, readFileSync, statSync } from 'node:fs'
|
|
22
22
|
import { homedir } from 'node:os'
|
|
23
23
|
import { dirname, join } from 'node:path'
|
|
24
24
|
import { createRequire } from 'node:module'
|
|
25
|
+
import { fileURLToPath } from 'node:url'
|
|
25
26
|
|
|
26
27
|
export const name = 'dsh-chat-embed'
|
|
27
28
|
|
|
@@ -153,6 +154,72 @@ function readAsarEntry(file, entry) {
|
|
|
153
154
|
return buffer.toString('utf8', offset, offset + Number(node.size))
|
|
154
155
|
}
|
|
155
156
|
|
|
157
|
+
/**
|
|
158
|
+
* Locate a Node binary that can run the patch tool.
|
|
159
|
+
*
|
|
160
|
+
* DSH runs every profile on its own packaged runtime, so the archive can be
|
|
161
|
+
* patched without the user installing Node. The order is: the runtime this
|
|
162
|
+
* Host is already running on, the managed runtime directory, then a system
|
|
163
|
+
* `node` on PATH.
|
|
164
|
+
*
|
|
165
|
+
* @returns {string | undefined} an absolute path, or undefined when none exists
|
|
166
|
+
*/
|
|
167
|
+
function findNodeBinary() {
|
|
168
|
+
const candidates = []
|
|
169
|
+
// A Node-hosted dsh reports its own executable.
|
|
170
|
+
if (typeof process.execPath === 'string' && process.execPath !== '' && /node(.exe)?$/i.test(process.execPath)) {
|
|
171
|
+
candidates.push(process.execPath)
|
|
172
|
+
}
|
|
173
|
+
// The managed runtime the desktop app installs beside its own data.
|
|
174
|
+
const home = process.env.DSH_HOME !== undefined && process.env.DSH_HOME !== '' ? process.env.DSH_HOME : join(homedir(), '.dsh')
|
|
175
|
+
const runtimes = join(home, 'dsh-runtimes')
|
|
176
|
+
try {
|
|
177
|
+
for (const entry of readdirSync(runtimes)) {
|
|
178
|
+
const base = join(runtimes, entry, 'dependencies', 'node', 'bin')
|
|
179
|
+
candidates.push(join(base, process.platform === 'win32' ? 'node.exe' : 'node'))
|
|
180
|
+
}
|
|
181
|
+
} catch (error) {
|
|
182
|
+
void error
|
|
183
|
+
}
|
|
184
|
+
if (process.platform === 'win32' && process.env.ProgramFiles !== undefined) {
|
|
185
|
+
candidates.push(join(process.env.ProgramFiles, 'nodejs', 'node.exe'))
|
|
186
|
+
} else {
|
|
187
|
+
candidates.push('/usr/local/bin/node')
|
|
188
|
+
candidates.push('/usr/bin/node')
|
|
189
|
+
}
|
|
190
|
+
for (const candidate of candidates) {
|
|
191
|
+
if (typeof candidate === 'string' && candidate !== '' && existsSync(candidate)) return candidate
|
|
192
|
+
}
|
|
193
|
+
return undefined
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* @param {string} node absolute path to a Node binary
|
|
198
|
+
* @param {string} script absolute path to patch-asar.cjs
|
|
199
|
+
* @param {string} [resources] install directory, so a non-standard location works
|
|
200
|
+
* @returns {string} a copy-pasteable command line
|
|
201
|
+
*/
|
|
202
|
+
function patchCommand(node, script, resources) {
|
|
203
|
+
const quote = (value) => (value.includes(' ') ? '"' + value + '"' : value)
|
|
204
|
+
const parts = [quote(node), quote(script), 'build']
|
|
205
|
+
// The tool only searches the usual install locations. When the Host found the
|
|
206
|
+
// shell somewhere else (a portable or custom install), it must say where.
|
|
207
|
+
if (typeof resources === 'string' && resources !== '') parts.push('--resources', quote(resources))
|
|
208
|
+
return parts.join(' ')
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Locate the patch tool inside this package, so the reported command points at
|
|
213
|
+
* a file that actually exists on the user's disk.
|
|
214
|
+
*
|
|
215
|
+
* @returns {string | undefined}
|
|
216
|
+
*/
|
|
217
|
+
function findPatchScript() {
|
|
218
|
+
const here = dirname(fileURLToPath(import.meta.url))
|
|
219
|
+
const candidate = join(here, 'tools', 'shell-patch', 'patch-asar.cjs')
|
|
220
|
+
return existsSync(candidate) ? candidate : undefined
|
|
221
|
+
}
|
|
222
|
+
|
|
156
223
|
/**
|
|
157
224
|
* Inspect the desktop shell and the optional patch. Read-only: no file here is
|
|
158
225
|
* written, so this is safe to call from a diagnostics button.
|
|
@@ -222,6 +289,13 @@ function inspectShell(resources) {
|
|
|
222
289
|
} catch (error) {
|
|
223
290
|
report.detail = '解析 app.asar 失败:' + firstLine(error)
|
|
224
291
|
}
|
|
292
|
+
// The user needs a Node binary to run the patch tool. The desktop app ships
|
|
293
|
+
// its own runtime, so nobody has to install one — report the exact command
|
|
294
|
+
// instead of telling them to install Node first.
|
|
295
|
+
report.patchScript = findPatchScript()
|
|
296
|
+
report.nodeBinary = findNodeBinary()
|
|
297
|
+
report.patchCommand =
|
|
298
|
+
report.nodeBinary === undefined ? undefined : patchCommand(report.nodeBinary, report.patchScript, resolved)
|
|
225
299
|
return report
|
|
226
300
|
}
|
|
227
301
|
|
|
@@ -332,4 +406,14 @@ export function apply(ctx) {
|
|
|
332
406
|
}
|
|
333
407
|
|
|
334
408
|
/** Exposed for tests and for the patch checker to reuse without a Context. */
|
|
335
|
-
export const internals = {
|
|
409
|
+
export const internals = {
|
|
410
|
+
inspectShell,
|
|
411
|
+
detectResources,
|
|
412
|
+
readAsarEntry,
|
|
413
|
+
readRealFile,
|
|
414
|
+
resourcesCandidates,
|
|
415
|
+
findNodeBinary,
|
|
416
|
+
findPatchScript,
|
|
417
|
+
patchCommand,
|
|
418
|
+
PROBE_PATH,
|
|
419
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-chat-embed",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.2",
|
|
4
4
|
"author": "poluce <fishsummer@126.com>",
|
|
5
5
|
"description": "Multi-tab AI chat embed for the DeepSeek Harness desktop app: DeepSeek, 豆包, ChatGPT, MiniMax, Kimi, Gemini and Grok, each signed in separately.",
|
|
6
6
|
"type": "module",
|