dsh-chat-embed 0.6.3 → 0.6.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/README.md CHANGED
@@ -179,20 +179,28 @@ node tools/shell-patch/patch-asar.cjs --help # 查看参数
179
179
 
180
180
  ### 补丁状态与自检(0.6.0 起)
181
181
 
182
- 设置菜单里的「跨重启保持登录」有四种状态,**只有未开启时才出现风险提示与操作按钮**:
182
+ 设置菜单里的「跨重启保持登录」有四种状态,**风险提示与「复制补丁说明」只在未生效时出现**:
183
183
 
184
- | 状态 | 含义 | 菜单里提供 |
184
+ | 状态 | 含义 | 菜单里额外提供 |
185
185
  | --- | --- | --- |
186
- | 已开启(`persist:` 分区) | 补丁生效,重启后仍登录 | —(已是最优) |
187
- | 未开启(内存分区) | 从没打过补丁 | 风险提示 + 复制补丁说明 + 检查补丁配置 |
186
+ | 已开启(`persist:` 分区) | 补丁生效,重启后仍登录 | —(已是最优,只留「检查补丁配置」) |
187
+ | 未开启(内存分区) | 从没打过补丁 | 风险提示 + 复制补丁说明 |
188
188
  | **已失效**(升级覆盖) | **打过补丁,但桌面壳升级覆盖了 `app.asar`** | 同上;这一状态专门告诉你"该重打了" |
189
189
  | 尚未检测 | 还没打开过聊天页 | 同上 |
190
190
 
191
- 「检查补丁配置」调用 Host 侧只读探针(`GET /dsh-chat-embed/patch-check`),报告安装目录、`app.asar` 是否可写、是否已打补丁、补丁点是否还在(壳版本是否兼容)、备份与待替换文件是否存在、`app.asar` 的大小与内容哈希;报告同时复制到剪贴板,方便直接贴出去。
191
+ 「检查补丁配置」是一行**开关样式**的控件,任何状态下都在。打开它不会在菜单里铺开报告——那会把面板撑宽——而是:
192
192
 
193
- **不需要自己装 Node.js**:探针会找出桌面版自带的 Node 运行时(`~/.dsh/dsh-runtimes/*/dependencies/node/`),并把一条完整可复制的命令写进报告——含正确的 Node 路径、补丁脚本路径和 `--resources` 安装目录(自定义/便携安装也能用)。
193
+ 1. 读取一次 Host 侧只读探针(见下);
194
+ 2. **打开一个新会话**,把已知事实(安装目录、可写性、补丁点、是否已打补丁、确切命令)预先写进输入框;
195
+ 3. 你点「发送」即可。agent 会调用 `dsh_chat_embed_patch_check` 工具核对,然后说明现状和该执行的命令。
194
196
 
195
- Host 侧还注册了只读工具 **`dsh_chat_embed_patch_check`**:可以直接问 agent「我的聊天为什么重启后要重新登录」,它会自己去查这些事实,而不是让你跑命令再贴输出。
197
+ 这条路径用的就是 DSH 自己「创建你的插件」入口的同一个 API(`uiWorkspace.startSession`),所以行为与原生一致:已有草稿时追加而不是覆盖。没有 `uiWorkspace` 的宿主会退化成把说明复制到剪贴板。
198
+
199
+ 背后是 Host 侧只读探针 `GET /dsh-chat-embed/patch-check`:安装目录、`app.asar` 是否可写、是否已打补丁、补丁点是否还在(壳版本是否兼容)、备份与待替换文件、大小与内容哈希。
200
+
201
+ **不需要自己装 Node.js**:探针会找出桌面版自带的 Node 运行时(`~/.dsh/dsh-runtimes/*/dependencies/node/`),并把一条完整可复制的命令写进草稿——含正确的 Node 路径、补丁脚本路径和 `--resources` 安装目录(自定义/便携安装也能用)。
202
+
203
+ Host 侧还注册了只读工具 **`dsh_chat_embed_patch_check`**:可以直接问 agent「我的聊天为什么重启后要重新登录」,它会自己去查这些事实,而不是让你跑命令再贴输出——上面第 2 步的草稿就是让它这么做的。
196
204
 
197
205
  补丁做两件事:`persist:` 固定分区(登录落盘)+ `cache:false`(不落 Chromium 缓存)。
198
206
  没有补丁也能用,只是每次重启要重新登录。细节与风险见 [已知限制](#已知限制)。
@@ -214,7 +222,7 @@ dsh-chat-embed/
214
222
  ├── package.json # 包名/版本、dsh.bundle.patch、dsh.client、exports/files
215
223
  ├── cordis.patch.yml # 组合补丁:插入 id=chat-embed 的 loader 行
216
224
  ├── index.js # Host 半侧:补丁探针(HTTP + agent 工具)
217
- ├── client.js # Client 半侧(约 3912 行,手写产物)
225
+ ├── client.js # Client 半侧(约 3985 行,手写产物)
218
226
  ├── locale/{en,zh}.json # 插件管理器显示用 meta
219
227
  ├── icon.svg # 插件图标(插件管理器显示)
220
228
  ├── assets/
@@ -264,7 +272,7 @@ git clone <repo-url> dsh-chat-embed
264
272
  cd dsh-chat-embed
265
273
 
266
274
  # 无需安装依赖;直接跑门禁与测试
267
- npm run check # 语法 + 清单 + 显示资源 + 服务注册表 + 81 项测试
275
+ npm run check # 语法 + 清单 + 显示资源 + 服务注册表 + 85 项测试
268
276
  npm test # 只跑测试
269
277
  ```
270
278
 
@@ -285,7 +293,7 @@ plugin_manager { "action": "install_bundle", "target": "<本仓库克隆路径>"
285
293
 
286
294
  ## 已知限制
287
295
 
288
- 1. **跨重启登录需要壳补丁**:壳默认给内存分区(`dsh-sidebar-browser-<uuid>`,无 `persist:`,每次启动随机),登录态不落盘。`tools/shell-patch/` 可修;客户端升级会覆盖 `app.asar`,需要重打。
296
+ 1. **跨重启登录需要壳补丁**:壳默认给内存分区(`dsh-sidebar-browser-<uuid>`,无 `persist:`,每次启动随机),登录态不落盘。`tools/shell-patch/` 可修;客户端升级会覆盖 `app.asar`,需要重打——菜单里的「检查补丁配置」会告诉你现在是哪种情况、命令是什么。
289
297
  2. **左右互斥**:右栏聊天依赖会话视图;中央打开聊天面板时右栏不可用。同一时刻只有一处显示聊天页,切换会重建一次(自动回到上次会话,不用重新登录)。
290
298
  3. **中心面板会被切走**:选另一个主面板时中央聊天页随 DOM 卸载,回来要重建;右栏 tab 带 `keepMounted`,切标签/切会话不卸载。
291
299
  4. **内存**:每点亮一个标签 = 一个渲染进程;7 个全开会明显吃内存。
@@ -305,7 +313,7 @@ plugin_manager { "action": "install_bundle", "target": "<本仓库克隆路径>"
305
313
  | 标签变红(`data-failed`) | 该标签租约或导航失败:点它看状态行 `detail`,再「重新加载」 |
306
314
  | 显示「已连接」但页面空白 | 网络或站点风控;「重新加载」或「外开」 |
307
315
  | 出现「使用环境异常」 | UA 归一化失效(壳升级后 UA 结构变了),查 `normalizedUserAgent` |
308
- | 重启后要重新登录 | 未打壳补丁,或补丁被客户端升级覆盖:重跑 `patch-asar.cjs build` |
316
+ | 重启后要重新登录 | 未打壳补丁,或补丁被客户端升级覆盖:菜单「检查补丁配置」→ 发送,让 agent 核对并给出命令 |
309
317
  | 磁盘占用变大 | 执行 `清理聊天缓存.cmd`(只删缓存,保留登录);或确认补丁含 `cache:false` |
310
318
  | 标签文字/顺序要改 | 只改 `PROVIDERS`,`npm run check` + 重装 |
311
319
 
@@ -315,6 +323,7 @@ plugin_manager { "action": "install_bundle", "target": "<本仓库克隆路径>"
315
323
 
316
324
  | 版本 | 变化 |
317
325
  | --- | --- |
326
+ | `0.6.5` | 设置菜单标题与诊断卡片显示自身版本号;「检查补丁配置」改为一次性开关:不再内联渲染报告,而是打开新会话并把已知事实预填进输入框(借用 DSH「创建你的插件」的 `uiWorkspace.startSession` 用法) |
318
327
  | `0.6.3` | 自检报告改为一次性:关闭设置菜单即清除,重新打开不再显示上次的报告 |
319
328
  | `0.6.2` | 修复设置菜单被长路径撑宽:菜单加 `max-width`、报告文本 `overflow-wrap:anywhere`、补丁命令单独占一行(便于整条复制) |
320
329
  | `0.6.1` | 探针找出桌面版自带的 Node 运行时,自检报告与「复制补丁说明」直接给出可执行命令(含 `--resources`,支持自定义安装目录);无需用户另装 Node |
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.3'
106
+ const BUILD = '0.6.5'
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__'
@@ -264,9 +264,20 @@ window.__ModuleLoader__.load({
264
264
  'Experimental: the patch rewrites the app.asar of the desktop shell and needs write access to its install folder. A shell update replaces it, so the patch has to be applied again.',
265
265
  persistCopy: 'Copy the patch instructions',
266
266
  persistCheck: 'Check the patch setup',
267
- persistCheckRunning: 'Checking…',
268
- persistReportCopied: 'Self-check copied',
269
- persistReportTitle: 'Shell patch self-check',
267
+ persistCheckHint: 'Opens a new session and drafts the check for you',
268
+ patchPromptPlugin: 'Plugin',
269
+ patchPrompt:
270
+ 'Check the DSH desktop shell patch that keeps the embedded chat logins across restarts, and tell me what to do.\n\nKnown so far:\n{facts}\n\nCall the dsh_chat_embed_patch_check tool to confirm the facts, then:\n1. say in a sentence or two what the situation is;\n2. if the patch is missing or was lost, give me the exact command to run;\n3. do not modify any file.',
271
+ patchYes: 'yes',
272
+ patchNo: 'no',
273
+ patchPresent: 'present',
274
+ patchMissing: 'missing (shell version mismatch)',
275
+ patchUnknown: 'unknown',
276
+ patchWritable: 'writable',
277
+ patchSiteLabel: 'patch site',
278
+ patchApplied: 'already patched',
279
+ patchCommandLabel: 'Patch command:',
280
+ patchDetail: 'Note',
270
281
  copyDiag: 'Copy diagnostics',
271
282
  copied: 'Copied',
272
283
  copyFailed: 'Copy failed',
@@ -345,9 +356,20 @@ window.__ModuleLoader__.load({
345
356
  persistRisk: '实验性功能:补丁会改写桌面壳的 app.asar,需要安装目录的写权限;桌面壳升级会覆盖补丁,需要重新打一次。',
346
357
  persistCopy: '复制补丁说明',
347
358
  persistCheck: '检查补丁配置',
348
- persistCheckRunning: '正在检查…',
349
- persistReportCopied: '自检报告已复制',
350
- persistReportTitle: '壳补丁自检报告',
359
+ persistCheckHint: '打开新会话,把检查内容写好,点发送即可',
360
+ patchPromptPlugin: '插件',
361
+ patchPrompt:
362
+ '帮我检查 DSH 桌面版「跨重启保持登录」的补丁状态,并告诉我该做什么。\n\n已知信息:\n{facts}\n\n请先调用 dsh_chat_embed_patch_check 工具核对上面的信息,然后:\n1. 用一两句话说明现在是什么情况;\n2. 如果补丁没打或已失效,给出我可以直接执行的命令;\n3. 不要修改任何文件。',
363
+ patchYes: '是',
364
+ patchNo: '否',
365
+ patchPresent: '存在',
366
+ patchMissing: '不存在(壳版本不匹配)',
367
+ patchUnknown: '未知',
368
+ patchWritable: '可写',
369
+ patchSiteLabel: '补丁点',
370
+ patchApplied: '已打补丁',
371
+ patchCommandLabel: '补丁命令:',
372
+ patchDetail: '说明',
351
373
  copyDiag: '复制诊断信息',
352
374
  copied: '已复制',
353
375
  copyFailed: '复制失败',
@@ -2472,7 +2494,7 @@ window.__ModuleLoader__.load({
2472
2494
  *
2473
2495
  * @type {{ running: boolean, report: string }}
2474
2496
  */
2475
- const patchCheck = { running: false, report: '' }
2497
+ const patchCheck = { running: false }
2476
2498
 
2477
2499
  /** One-shot cache of the Host probe, so both actions read the same facts. */
2478
2500
  const patchProbe = { loaded: false, value: undefined }
@@ -2510,69 +2532,77 @@ window.__ModuleLoader__.load({
2510
2532
  return patchProbe.value
2511
2533
  }
2512
2534
 
2513
- async function runPatchCheck(t) {
2514
- if (patchCheck.running) return
2535
+ /**
2536
+ * Hand the self-check to a Session instead of rendering it into the menu.
2537
+ *
2538
+ * A report inside the settings menu made the panel unusable: absolute paths
2539
+ * and a full command line stretched it across the surface, and the result was
2540
+ * a one-shot answer kept in panel state. A Session is the right place — the
2541
+ * agent can call the patch-check tool, read the reply and explain it, and the
2542
+ * user confirms by pressing send.
2543
+ *
2544
+ * @param {(key: string) => string} t
2545
+ * @returns {Promise<void>}
2546
+ */
2547
+ async function openPatchCheckSession(t) {
2548
+ const workspace = typeof ctx.get === 'function' ? ctx.get('uiWorkspace') : undefined
2549
+ if (workspace === undefined || workspace === null || typeof workspace.startSession !== 'function') {
2550
+ // No Session service: fall back to the clipboard so the click still does
2551
+ // something useful instead of failing silently.
2552
+ void copyText(patchInstructions(t, patchProbe.value))
2553
+ return
2554
+ }
2515
2555
  patchCheck.running = true
2516
2556
  notify()
2517
- let report = ''
2518
2557
  try {
2519
- patchProbe.loaded = false
2520
2558
  const probe = await fetchProbe()
2521
- report = formatPatchReport(t, probe)
2559
+ const prompt = buildPatchPrompt(t, probe)
2560
+ patchProbe.loaded = false
2561
+ workspace.startSession(undefined, { prompt, whenDraftExists: 'append' })
2522
2562
  } catch (error) {
2523
- report = formatPatchReport(t, undefined) + '\n' + t('copyFailed') + ': ' + messageOf(error)
2563
+ void copyText(patchInstructions(t, patchProbe.value) + '\n' + t('copyFailed') + ': ' + messageOf(error))
2524
2564
  }
2525
2565
  patchCheck.running = false
2526
- patchCheck.report = report
2527
2566
  notify()
2528
- void copyText(report)
2529
2567
  }
2530
2568
 
2531
2569
  /**
2570
+ * The text dropped into the new Session's draft: what is already known, so
2571
+ * the agent starts from facts instead of asking the user to run something.
2572
+ *
2532
2573
  * @param {(key: string) => string} t
2533
2574
  * @param {any} probe
2534
2575
  * @returns {string}
2535
2576
  */
2536
- function formatPatchReport(t, probe) {
2537
- const lines = [t('persistReportTitle')]
2538
- lines.push(t('persistLabel') + ': ' + persistenceText(t, persistenceState()))
2539
- if (probe === undefined || probe === null) {
2540
- lines.push(t('persistHint'))
2541
- return lines.join('\n')
2542
- }
2543
- const row = (label, value) => {
2544
- if (value === undefined || value === null || value === '') return
2545
- lines.push(label + ': ' + String(value))
2546
- }
2547
- row('resources', probe.resources)
2548
- row('app.asar', probe.asar)
2549
- row('app.asar.orig', probe.backup === true ? '有' : '无')
2550
- row('app.asar.new', probe.staged === true ? '有(待替换)' : '无')
2551
- row('可写', probe.writable === true ? '是' : '否')
2552
- row('补丁点', probe.patchSite === true ? '存在' : probe.patchSite === false ? '不存在(壳版本不匹配)' : '未知')
2553
- row('已打补丁', probe.patched === true ? '是' : probe.patched === false ? '否' : '未知')
2554
- row('node', probe.node)
2555
- if (probe.nodeBinary === undefined) {
2556
- lines.push('(没有找到可用的 Node 运行时,需要先安装 Node.js 22 或更新)')
2557
- }
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)
2577
+ function buildPatchPrompt(t, probe) {
2578
+ const facts = []
2579
+ facts.push(t('patchPromptPlugin') + ': dsh-chat-embed ' + BUILD)
2580
+ facts.push(t('persistLabel') + ': ' + persistenceText(t, persistenceState()))
2581
+ if (probe !== undefined && probe !== null) {
2582
+ const row = (label, value) => {
2583
+ if (value === undefined || value === null || value === '') return
2584
+ facts.push(label + ': ' + String(value))
2585
+ }
2586
+ row('resources', probe.resources)
2587
+ row('app.asar', probe.asar)
2588
+ row('app.asar.orig', probe.backup === true ? t('patchYes') : t('patchNo'))
2589
+ row(t('patchWritable'), probe.writable === true ? t('patchYes') : t('patchNo'))
2590
+ row(
2591
+ t('patchSiteLabel'),
2592
+ probe.patchSite === true ? t('patchPresent') : probe.patchSite === false ? t('patchMissing') : t('patchUnknown'),
2593
+ )
2594
+ row(
2595
+ t('patchApplied'),
2596
+ probe.patched === true ? t('patchYes') : probe.patched === false ? t('patchNo') : t('patchUnknown'),
2597
+ )
2598
+ if (typeof probe.patchCommand === 'string' && probe.patchCommand !== '') {
2599
+ facts.push('')
2600
+ facts.push(t('patchCommandLabel'))
2601
+ facts.push(probe.patchCommand)
2602
+ }
2603
+ if (typeof probe.detail === 'string' && probe.detail !== '') facts.push(t('patchDetail') + ': ' + probe.detail)
2565
2604
  }
2566
- return lines.join('\n')
2567
- }
2568
-
2569
- /**
2570
- * Drop the one-shot self-check answer. Closing the settings menu calls this:
2571
- * a report belongs to the click that asked for it, not to the panel state.
2572
- */
2573
- function resetPatchCheck() {
2574
- patchCheck.report = ''
2575
- patchCheck.running = false
2605
+ return fill(t('patchPrompt'), { facts: facts.join('\n') })
2576
2606
  }
2577
2607
 
2578
2608
  /** @returns {{ state: string, detail: string, seats: any[] }} */
@@ -2785,13 +2815,15 @@ window.__ModuleLoader__.load({
2785
2815
  '.dce-guest-layer[data-active="true"]{display:flex}',
2786
2816
  '.dce-guest{display:inline-flex;flex:1 1 auto;width:100%;height:100%;border:0;background:transparent}',
2787
2817
  '.dce-card{position:absolute;inset:0;z-index:2;display:flex;flex-direction:column;gap:10px;box-sizing:border-box;width:auto;max-width:460px;height:max-content;margin:auto;padding:20px 22px;border:1px solid var(--dsw-alias-border-l1,rgba(127,127,127,.24));border-radius:12px;background:var(--dsw-alias-bg-layer-2,#fff);box-shadow:0 12px 32px rgba(0,0,0,.12);font-size:13px}',
2788
- '.dce-card-title{font-size:14px;font-weight:600}',
2818
+ '.dce-card-title{display:flex;align-items:baseline;gap:8px;font-size:14px;font-weight:600}',
2789
2819
  '.dce-card-body{color:var(--dsw-alias-label-secondary,#5f6368);line-height:1.6}',
2790
2820
  '.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}',
2791
2821
  '.dce-card-actions{display:flex;gap:8px;justify-content:flex-end}',
2792
2822
  '.dce-menu-scrim{position:absolute;inset:0;z-index:5}',
2793
2823
  '.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}',
2794
- '.dce-menu-title{font-size:11px;color:var(--dsw-alias-label-tertiary,#9aa0a6);padding:6px 8px 4px;letter-spacing:.04em}',
2824
+ '.dce-menu-title{display:flex;align-items:baseline;justify-content:space-between;gap:10px;font-size:11px;color:var(--dsw-alias-label-tertiary,#9aa0a6);padding:6px 8px 4px;letter-spacing:.04em}',
2825
+ '.dce-menu-version{flex:0 0 auto;font-size:10.5px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;letter-spacing:0;opacity:.8}',
2826
+ '.dce-card-version{margin-left:auto;font-size:11px;font-weight:400;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;color:var(--dsw-alias-label-tertiary,#9aa0a6);opacity:.8}',
2795
2827
  '.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)}',
2796
2828
  '.dce-menu-row:hover{background:var(--dsw-alias-interactive-bg-hover,rgba(127,127,127,.12))}',
2797
2829
  '.dce-menu-label{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}',
@@ -2799,7 +2831,6 @@ window.__ModuleLoader__.load({
2799
2831
  '.dce-menu-sep{margin:5px 0 3px;border-top:1px solid var(--dsw-alias-border-l1,rgba(127,127,127,.24))}',
2800
2832
  '.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}',
2801
2833
  '.dce-menu-note[data-persist-risk="true"]{color:var(--dsw-alias-label-warning,#c98b16)}',
2802
- '.dce-menu-note[data-patch-report="true"]{max-height:180px;overflow:auto;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11px}',
2803
2834
  '.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}',
2804
2835
  '.dce-menu-item:hover{background:var(--dsw-alias-interactive-bg-hover,rgba(127,127,127,.12))}',
2805
2836
  '.dce-right-title{display:inline-flex;align-items:center;gap:6px;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}',
@@ -3229,7 +3260,6 @@ window.__ModuleLoader__.load({
3229
3260
  */
3230
3261
  const closeMenu = () => {
3231
3262
  setMenuOpen(false)
3232
- resetPatchCheck()
3233
3263
  }
3234
3264
  const [copyState, setCopyState] = React.useState('')
3235
3265
  const barTimerRef = React.useRef(0)
@@ -3436,7 +3466,12 @@ window.__ModuleLoader__.load({
3436
3466
  ? h(
3437
3467
  'div',
3438
3468
  { className: 'dce-menu', role: 'menu' },
3439
- h('div', { className: 'dce-menu-title' }, t('menuTitle')),
3469
+ h(
3470
+ 'div',
3471
+ { className: 'dce-menu-title' },
3472
+ h('span', null, t('menuTitle')),
3473
+ h('span', { className: 'dce-menu-version', 'data-build': BUILD }, 'v' + BUILD),
3474
+ ),
3440
3475
  providersInOrder().map((entry) => {
3441
3476
  const on = isEnabled(entry.id)
3442
3477
  const lastOne = on && enabledIds.length <= 1
@@ -3488,21 +3523,28 @@ window.__ModuleLoader__.load({
3488
3523
  h('span', { className: 'dce-menu-label' }, t('persistCopy')),
3489
3524
  ),
3490
3525
  h(
3491
- 'button',
3492
- {
3493
- type: 'button',
3494
- className: 'dce-menu-item',
3495
- 'data-patch-check': 'true',
3496
- disabled: patchCheck.running,
3497
- onClick: () => {
3498
- void runPatchCheck(t)
3526
+ 'div',
3527
+ { className: 'dce-menu-row' },
3528
+ h('span', { className: 'dce-menu-label', title: t('persistCheckHint') }, t('persistCheck')),
3529
+ h(
3530
+ 'button',
3531
+ {
3532
+ type: 'button',
3533
+ role: 'switch',
3534
+ className: 'dce-switch',
3535
+ 'data-on': patchCheck.running ? 'true' : 'false',
3536
+ 'aria-checked': patchCheck.running ? 'true' : 'false',
3537
+ 'data-patch-check': 'true',
3538
+ 'aria-label': t('persistCheck'),
3539
+ title: t('persistCheckHint'),
3540
+ disabled: patchCheck.running,
3541
+ onClick: () => {
3542
+ closeMenu()
3543
+ void openPatchCheckSession(t)
3544
+ },
3499
3545
  },
3500
- },
3501
- h('span', { className: 'dce-menu-label' }, patchCheck.running ? t('persistCheckRunning') : t('persistCheck')),
3546
+ ),
3502
3547
  ),
3503
- patchCheck.report === ''
3504
- ? null
3505
- : h('div', { className: 'dce-menu-note', 'data-patch-report': 'true' }, patchCheck.report),
3506
3548
  h(
3507
3549
  'button',
3508
3550
  {
@@ -3599,7 +3641,12 @@ window.__ModuleLoader__.load({
3599
3641
  ? h(
3600
3642
  'div',
3601
3643
  { className: 'dce-card', role: 'dialog' },
3602
- h('div', { className: 'dce-card-title' }, t('diagTitle')),
3644
+ h(
3645
+ 'div',
3646
+ { className: 'dce-card-title' },
3647
+ h('span', null, t('diagTitle')),
3648
+ h('span', { className: 'dce-card-version', 'data-build': BUILD }, 'v' + BUILD),
3649
+ ),
3603
3650
  h(
3604
3651
  'div',
3605
3652
  { className: 'dce-card-detail' },
@@ -3779,10 +3826,9 @@ window.__ModuleLoader__.load({
3779
3826
  patchInstructions,
3780
3827
  fetchProbe,
3781
3828
  patchProbe,
3782
- formatPatchReport,
3783
- runPatchCheck,
3784
3829
  patchCheck,
3785
- resetPatchCheck,
3830
+ openPatchCheckSession,
3831
+ buildPatchPrompt,
3786
3832
  setLogging,
3787
3833
  clearLog,
3788
3834
  readLogEntries,
@@ -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.3**(`client.js` 约 3912 行,83 项契约测试)。
6
+ 当前实现:**v0.6.5**(`client.js` 约 3985 行,85 项契约测试)。
7
7
 
8
8
  > **架构一句话**:聊天页直接渲染在**席位的布局盒**里(`.dce-panel.dce-inline`,`width/height:100%`),几何完全交给 CSS;席位只有两个 —— 中央主面板(`main`)和右栏聊天 tab —— 二者互斥,**哪边点哪边接管**,切换时 guest 重建一次并回到上次会话。
9
9
 
@@ -331,6 +331,8 @@ Host 半侧另有两条只读通道,专门用来回答"为什么重启后要
331
331
  | 通道 | 内容 |
332
332
  | --- | --- |
333
333
  | `GET /dsh-chat-embed/patch-check` | 安装目录、`app.asar` 是否可写、是否已打补丁、补丁点是否还存在、备份与待替换文件、大小与内容哈希、**可执行的补丁命令**(含自带 Node 运行时路径与 `--resources`) |
334
+
335
+ 客户端拿到探针结果后不再内联渲染,而是通过 `uiWorkspace.startSession(undefined, { prompt, whenDraftExists: 'append' })` 打开一个新会话,把事实与授权边界写进草稿;用户点发送即可。这与 DSH 自身「创建你的插件」入口用的是同一个 API,`whenDraftExists` 的取值语义在 `dsh-client-ui-conversation` 的 `requestDraftInitialization` 里定义(`replace` / `keep` / `append`)。
334
336
  | agent 工具 `dsh_chat_embed_patch_check` | 同一份读数,供模型直接查询(只读,无需审批) |
335
337
 
336
338
  > 读取 `app.asar` 必须走 Electron 的 `original-fs`:被补丁过的 `node:fs` 会把以 `.asar` 结尾的路径当成目录,`readFileSync` 直接 ENOENT。探针优先用 `original-fs`,普通 Node 下回落到 `node:fs`。
@@ -5,13 +5,13 @@
5
5
  ## 版本与提交约定
6
6
 
7
7
  - 同时改 `package.json` 的 `version` 与 `client.js` 的 `BUILD`,两者必须一致;
8
- - 门禁必须绿:`npm run check`(`verify-bundle` + 83 项契约测试);
8
+ - 门禁必须绿:`npm run check`(`verify-bundle` + 85 项契约测试);
9
9
  - 代码注释/提交信息英文,文档中文。
10
10
 
11
11
  ## 发版步骤
12
12
 
13
13
  ```powershell
14
- npm run check # 门禁:verify-bundle + 83 项测试(prepublishOnly 也会跑)
14
+ npm run check # 门禁:verify-bundle + 85 项测试(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.3.tgz`(16 个文件,约 70 kB) |
35
- | npm | `dsh-chat-embed@0.6.3`(<https://www.npmjs.com/package/dsh-chat-embed>,维护者 `poluce_auto`) |
34
+ | 分发包 | `dsh-chat-embed-0.6.5.tgz`(16 个文件,约 70 kB) |
35
+ | npm | `dsh-chat-embed@0.6.5`(<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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-chat-embed",
3
- "version": "0.6.3",
3
+ "version": "0.6.5",
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",