Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
0e929d5
修复上传与回答中的文档预览 (#434)
Fairy-happy Sep 17, 2026
589679f
feat: built-in image generation plugin (#447)
Fairy-happy Sep 17, 2026
a1a688f
feat(mobile): require a desktop PIN for internet pairing (#426)
Fairy-happy Sep 17, 2026
1383895
fix(diagnostics): 停用 unclean-exit 误报弹窗 (#448)
Fairy-happy Sep 17, 2026
d226532
fix(ppt): keep template previews inside gallery cells (#449)
Fairy-happy Sep 17, 2026
3493a95
fix(ppt): validate literal newline escapes before export (#453)
Fairy-happy Sep 17, 2026
6e6d5d3
修复 Windows CI 清理临时目录时的 EBUSY 误报
Sep 17, 2026
9b2d2cf
feat(ppt): 在 V0.9.1 上加入 Green Pulse 可编辑配图模板 (#465)
Fairy-happy Sep 18, 2026
ae95c0d
feat(enterprise): add BiSheng gateway login on 0.9.1 without sending …
Fairy-happy Sep 20, 2026
0aa9d1b
合并 main,保留 V0.9.1 能力并对齐 Safe Mode 与企业登录线。
Sep 21, 2026
e3a5309
fix(patch): 修正合入 main 后 dsh 补丁 hunk 行数
Sep 21, 2026
5c28af9
Merge branch 'main' into V0.9.2
Sep 21, 2026
859a221
合并 V0.9.1:把生图、PPT、配对 PIN 与文档预览带入 0.9.2,并保留企业登录。
Sep 21, 2026
0f72373
feat(enterprise): 展示视觉模型标识并精简 Token 用量 (#484)
Fairy-happy Sep 21, 2026
bcfecc5
fix(enterprise): 补齐登录元数据、桌面唤回与旧设置迁移 (#494)
Fairy-happy Sep 21, 2026
918b676
fix(models): show display names in model switch notices (#482)
SuperstructureJH Sep 21, 2026
e293f97
fix(enterprise): keep the Learn more pill readable in dark theme
Sep 21, 2026
d25c53f
fix(enterprise): drop account from the English nav label
Sep 21, 2026
294e929
fix(enterprise): 将了解企业版链接改为官网企业部署页 (#495)
Fairy-happy Sep 21, 2026
dfeb83b
feat(enterprise): 在 V0.9.2 加入企业插件市场 (#519)
SuperstructureJH Sep 22, 2026
f5b7587
feat(enterprise): 优化企业账号与模型列表布局 (#527)
SuperstructureJH Sep 22, 2026
2bc6df2
fix(enterprise): point Learn more at the deployment page
Sep 22, 2026
b28c1dd
fix(test): drop the enterprise packer shebang so Windows CI can load it
Sep 22, 2026
8a5a364
fix(startup): make recovery independent of profile links (#504)
yaojin3616 Sep 22, 2026
b236ad8
fix(startup): retry stale host peer subpaths and keep the original fa…
yaojin3616 Sep 22, 2026
440d8cf
fix(startup): keep recovery proportional to the fault it found
yaojin3616 Sep 22, 2026
bfc7ea5
chore: guard the client anchor lookup and the fallback's packaging
yaojin3616 Sep 22, 2026
39e4a64
fix(enterprise): hide the trusted-source prompt after sign-in
Sep 22, 2026
10eb4fa
Merge branch 'V0.9.2' of github.com:dataelement/dsh-desktop into V0.9.2
Sep 22, 2026
60c8fc2
合并 main 到 V0.9.2:接住人设前缀迁移,消除对 main 的合并冲突
yaojin3616 Sep 22, 2026
aed1834
Merge remote-tracking branch 'origin/V0.9.2' into V0.9.2
yaojin3616 Sep 22, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,50 @@ DSH Desktop 是 Electron 宿主,复用 Harness runtime 和 Web UI。保持这
- 不把用户真实配置、会话、凭据或机器绝对路径加入代码/fixture。诊断用户环境时先保留证据;清空 Profile、删除插件或重置设置不能作为默认排障步骤。
- 安装、下载、激活、实际加载和发布是不同状态,代码及交付说明必须区分。

### 启动目录与插件解析拓扑

启动目录、Profile、插件目录和宿主安装目录承担不同职责,不能因为开发环境恰好能解析就合并这些边界:

| 位置 | 职责 | 不得依赖的隐含行为 |
| --- | --- | --- |
| `<userData>/launch-root` | Harness 子进程的中立 `cwd`,隔离从工作区继承的上下文 | 不要求是 Git 仓库,不存放或解析插件、依赖和用户项目 |
| `$DSH_HOME/profiles/<profile>` | Profile manifest、组合配置及该 Profile 可见的插件入口 | 不从仓库根目录或调用者 `cwd` 偶然找到包 |
| `$DSH_HOME/profiles/node_modules` | 普通 Profile 共享的宿主依赖闭包与解析接缝 | 不是插件源码、generation 或安装包的权威副本 |
| Profile plugin / `.generations/<id>` / 本地链接目标 | 插件自身代码和插件私有依赖 | 不复制 React、Cordis、Harness 等应与宿主共享的单例 |
| Desktop 安装目录 | 当前运行版本携带的 Harness、宿主插件和受控依赖 | 不作为所有缺包的通用搜索目录,不覆盖插件的普通第三方依赖 |

- 普通 Profile 先从 Profile 解析插件;本地 `file:` 包、symlink 和 Windows junction 必须保持可加载。解析后即使 Node 使用插件的物理路径,插件声明的宿主 peer 仍应连接到当前 Desktop 携带的兼容实现。
- 依赖解析顺序是“插件自身/正常 Node 解析 → 明确的宿主依赖 fallback”。宿主 fallback 只允许覆盖约定的宿主包命名空间(当前为 `@deepseek-ai/*`),且只在正常解析得到 `ERR_MODULE_NOT_FOUND` 时启用;不得用全局 `NODE_PATH`、改变 `cwd` 或把安装目录作为任意包的兜底。
- Safe Mode 是例外但不是第二套模糊规则:它只加载安装包自带的包,可以显式使用宿主 anchor,并且不能依赖普通 Profile 的共享 fallback。普通 Profile 不得套用 Safe Mode 的“所有 bare package 都从宿主解析”语义。
- fallback 必须遵守 `exports`、子路径和当前实际运行安装目录,不能硬编码开发仓库的 `node_modules` 或机器绝对路径;应用升级后不得继续引用旧安装 generation。
- 加载失败须保留最内层的包名、父模块和解析阶段。外层插件首次查找失败、fallback 失败和插件内部 peer 缺失不能互相覆盖,不能把“插件内部缺少宿主依赖”误报成“插件不存在”。
- 修改启动入口、loader、Profile 投影、generation 或依赖闭包时,至少验证:安装包内置插件、普通市场插件、本地软链接插件、缺少宿主 peer、插件自带普通依赖、Safe Mode 无 `profiles/node_modules`;涉及 Windows 时补 junction 与物理路径验证。
- 单元测试中 `import` 成功只证明源码路径可达。交付前还要按影响范围验证实际 Harness 子进程、打包资源清单及安装后的应用;已安装旧版本未重建时不得声称修复已在正式版生效。

### 后端与客户端启动协议

按下面的阶段判断加载是否成功,不把一个阶段的成功替代后续阶段的验收。具体 API 和脚本形式以锁文件对应的 Harness 实现为准;升级时重新核对协议,不能只机械保留旧补丁。

| 阶段 | 必须成立的条件 | 可定位的失败证据 |
| --- | --- | --- |
| Profile 组合 | manifest、patch、启用状态和活动插件入口一致 | Profile 名称、组合来源、插件归属及实际路径 |
| 后端加载 | 插件入口可导入,依赖可解析,所需服务满足并完成激活 | import / apply / activate 阶段及原始 cause |
| 客户端发现 | 从对应插件的同一有效解析基准读取 `dsh.client`,生成模块图和脚本路由 | package、client 入口、解析 anchor、graph 中的模块及 revision |
| HTML 引导 | 注册队列先于 bootstrap 脚本执行,bootstrap 注册先于模块系统创建 | HTML 标签顺序、脚本 URL、HTTP 状态、响应类型及浏览器异常 |
| 客户端激活 | 模块系统按依赖图实例化插件,所需服务和 UI 完成挂载 | 模块注册、依赖/服务状态及实际界面 |

- 后端导入、客户端 bundle 发现及 preset 解析必须遵循同一 Profile/宿主归属规则;修改任一路径时检查其余消费者。Safe Mode 的宿主 anchor 必须传到客户端发现和 preset,不能仅保证后端启动。
- 当前客户端引导顺序为:创建 `window.__ModuleLoader__` 注册队列 → 执行包含 `@deepseek-ai/dsh-client-modules/client.js` 的 bootstrap 脚本并注册模块 → 在 shell 消费前提供 `__DSH_BOOT__` 模块图 → 调用 `create()` 建立模块系统 → 按模块图加载和激活应用插件。应用脚本的 preload 可以提前发起下载;下载、执行、注册和激活是不同状态,不得用下载顺序推断执行顺序。
- 不得随意给 bootstrap 添加 `async`、延后队列初始化或改变 HTML 注入位置。优化合包、缓存或脚本调度时,必须保持上述先后关系以及 graph、revision、脚本响应的一致性,覆盖冷缓存与重启/更新后的缓存场景。
- `Harness is ready`、HTML 200、脚本 200 分别只证明对应阶段。完整客户端验收还须确认 bootstrap 注册、模块系统创建及实际 UI 挂载;不得把脚本标签存在或字符串匹配作为加载成功的唯一证据。
- `HTML did not preload ...` 只表明调用 `create()` 时缺少 bootstrap 注册。诊断时依次区分 graph 漏项、HTML 漏标签、请求失败、错误响应、执行异常和顺序错误;不能仅凭这一行归因于插件不兼容、缓存或某个版本回归。

### 加载链路变更的提交要求

- 实现前写清:改动属于上表哪一阶段、读取哪个目录、采用哪个解析基准、影响普通 Profile 还是 Safe Mode、失败时如何保留原始诊断与恢复入口。不能用在开发仓库中导入成功来推断正式安装包可用。
- 回归优先走真实子进程和临时 Profile,分别覆盖正常、缺包及错误路径;客户端改动验证认证后的 HTML、bootstrap 响应及执行注册,再补真实浏览器/UI 验收。相关入口包括 `test/harness-node-entry.test.ts`、`test/plugin-startup-failure.test.ts`、`test/safe-mode-host-resolved.test.ts`、`test/safe-mode-runtime.test.ts` 和 `test/desktop-plugin-closure.test.ts`,按实际改动选择并扩展行为覆盖。
- 交付说明列出已验证阶段和未验证阶段;规则中的验收要求不代表现有测试或 CI 已全部覆盖。声称版本回归须有完整版本/安装产物、Profile 状态与启动路径的对照,单独替换一个 loader 文件的实验不能替代整应用版本对比。

## 4. 验证与交付

以下命令均在仓库根目录运行:
Expand Down
3 changes: 3 additions & 0 deletions build/dsh-desktop.patch.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@
- id: dsh-desktop-client-ui
name: dsh-desktop-client-ui

- id: dsh-desktop-enterprise
name: dsh-desktop-enterprise

# DSH Desktop owns the fixed-target dsh-market installer and its lightweight
# settings entry. After dshmarket is installed and Harness restarts, the
# community plugin replaces the placeholder with its complete market UI.
Expand Down
2 changes: 2 additions & 0 deletions build/harness-node-entry.mjs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import childProcess from 'node:child_process'
import { syncBuiltinESMExports } from 'node:module'
import { pathToFileURL } from 'node:url'
import { registerHostModuleFallback } from './host-module-fallback.mjs'
import { enforceWindowsChildProcessHide } from './windows-child-process-hide.mjs'

// On macOS Harness runs inside an Electron utility process (TCC responsibility
Expand Down Expand Up @@ -83,6 +84,7 @@ if (!dshEntryPath) {
process.stdout.write(`[harness-node] loading=${dshEntryPath}\n`)
process.argv = [process.execPath, dshEntryPath, ...dshArguments]
try {
registerHostModuleFallback(dshEntryPath)
// Harness 0.1.5 gates its CLI behind `if (import.meta.main)` and exports
// `runCli`. This file imports the entry rather than being it, so that guard
// is false here and a plain import would load the module, run nothing, and
Expand Down
50 changes: 50 additions & 0 deletions build/host-module-fallback.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { registerHooks } from 'node:module'
import { pathToFileURL } from 'node:url'

const HOST_PACKAGE_PREFIX = '@deepseek-ai/'

/**
* Resolution failures the installation anchor can legitimately answer.
*
* `ERR_MODULE_NOT_FOUND` is the plugin having no copy at all.
* `ERR_PACKAGE_PATH_NOT_EXPORTED` is the copy it found being older than the
* subpath its importer needs — observed in the field as
* `@deepseek-ai/dsh-subprocess-local` importing `dsh-subprocess/control` from a
* stale `profiles/node_modules` copy. Retrying it is safe: the retry only
* succeeds when the host copy really exports that subpath.
*/
const HOST_RETRYABLE = new Set(['ERR_MODULE_NOT_FOUND', 'ERR_PACKAGE_PATH_NOT_EXPORTED'])

/**
* Let linked Profile plugins consume the Harness packages carried by Desktop.
*
* Node resolves a symlinked plugin's imports from the plugin's physical source
* directory. That directory normally has no node_modules of its own, while the
* peer packages intentionally live beside the bundled DSH entry. Preserve the
* plugin's normal resolution first and only retry missing @deepseek-ai packages
* from the installation anchor; unrelated dependencies must remain plugin-owned.
*/
export function registerHostModuleFallback(dshEntryPath) {
const hostEntryUrl = pathToFileURL(dshEntryPath).href
return registerHooks({
resolve(specifier, context, nextResolve) {
try {
return nextResolve(specifier, context)
} catch (error) {
if (!specifier.startsWith(HOST_PACKAGE_PREFIX) || !HOST_RETRYABLE.has(error?.code)) {
throw error
}
try {
return nextResolve(specifier, { ...context, parentURL: hostEntryUrl })
} catch (hostError) {
// Only the first failure names the importing plugin, and crash
// reports lose `code` to Harness's wrapper — so the message that
// carries `imported from <plugin path>` is the one to keep.
error.message += `\n host fallback from ${hostEntryUrl} also failed: ${hostError.message}`
error.cause ??= hostError
throw error
}
}
}
})
}
6 changes: 4 additions & 2 deletions build/safe-mode.html
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@
.plugin-copy { display:flex; flex-direction:column; min-width:0; }
.plugin-title { display:flex; flex-wrap:wrap; align-items:baseline; gap:4px; }
.plugin-name { overflow-wrap: anywhere; font-family: ui-monospace,SFMono-Regular,Menlo,Consolas,monospace; font-size: 13px; font-weight: 600; }
.plugin-status { color:var(--danger); font-size:11px; font-weight:650; }
.plugin-status { color:var(--muted); font-size:11px; font-weight:650; }
.plugin-status.danger { color:var(--danger); }
.plugin-status.warning { color:var(--warning); }
.plugin-action { margin-top:4px; font-size:11px; font-weight:620; }
.issue-group { border-top:1px solid var(--line); background:var(--surface); }
Expand Down Expand Up @@ -299,7 +300,8 @@ <h1 id="heading"></h1>
}
if (plugin.statusLabel) {
const status = document.createElement('span'); status.className = 'plugin-status'; status.textContent = String(plugin.statusLabel)
if (plugin.statusTone === 'warning') status.classList.add('warning')
if (plugin.statusTone === 'danger') status.classList.add('danger')
else if (plugin.statusTone === 'warning') status.classList.add('warning')
else if (plugin.statusTone === 'success') status.classList.add('success')
title.appendChild(status)
}
Expand Down
57 changes: 57 additions & 0 deletions docs/STATUS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
## Enterprise account label — 2026-09-22

- The identity label now reads “当前账号 / Current account”, matching the existing display-name, username, and ID fallback order.
- PR #527 review follow-up; account selection and authentication behavior stay as implemented. Full regression: 149 files / 1,249 tests PASS; diff-check PASS. New package: NOT_RUN.

## Enterprise account layout — 2026-09-22

- Based on `V0.9.2@dfeb83b31b9755aeccd119cc3c813d67ecd2f7dc`, with installed Harness `0.1.5-rc.2`.
- The account row groups a fixed account icon, identity and sign-out action. The enterprise website link sits beside the page title; refresh stays beside the model section heading.
- Model rows use spacing to separate entries. Provider and vision metadata sit below model names; green indicators identify models with available routing and quota. Provider extraction preserves model paths such as `kimi/kimi-k3`. Existing quota values and hover percentages remain available.
- Verification on the updated baseline: offline `npm ci` and root postinstall, typecheck, production build, 149 test files / 1,249 tests and diff-check passed. Actual React component checks with fixture APIs covered light/dark themes, narrow layouts, refresh, website and sign-out actions.
- The r4 macOS ARM64 development package was built and signature/archive-verified on the earlier `294e9290` baseline. A new package and authenticated enterprise end-to-end acceptance on `dfeb83b3`: `NOT_RUN`.

# Model switch display names

## 2026-09-22 — Signed test-build compatibility

- PR #519 acceptance plugin fixture now declares `desktop_min: "0.9.1"`. Catalog and installation regression checks use Desktop `0.9.2-test.1`, and the physical isolated Profile Loader smoke uses that same prerelease version for installation and failed-update rollback.
- Standard SemVer comparison remains in effect: `0.9.2-test.1` satisfies `0.9.1` and falls below `0.9.2`. Packaging guidance records the minimum for this feature's acceptance plugins. This change updates repository fixtures and guidance; deployed enterprise catalog artifacts are unchanged.
- Verification: full `npm test` PASS (149 files / 1,247 tests, 30.67s), `npm run typecheck`, `npm run build`, `git diff --check` PASS. Existing real-enterprise/native acceptance limitations remain.

## 2026-09-22 — PR #519 review corrections

- Market compatibility now receives Electron `app.getVersion()` through the authenticated enterprise Broker; catalog UI and install enforce the same SemVer verdict. Missing/invalid versions fail compatibility checks.
- Signing keys are persisted on first use and held across sync/restart; a changed advertised key blocks online installation. Administrator-provided `DSH_DESKTOP_MARKET_PUBLIC_KEY` supplies an independent pin. Default TOFU trust starts at the confirmed enterprise service; see the documented trust boundary.
- Stop and expiry invalidate in-flight/queued operations immediately and serialize document/Loader cleanup with installation. Catalog traversal is bounded to 100 pages and validates totals. Enterprise runtime dependencies are explicitly declared.
- Baseline remains `upstream/V0.9.2@294e9290262dd68f402fa526cfb6aeacd571f627`; Harness `0.1.5-rc.2`. Fresh `npm ci` / repository postinstall, `npm run typecheck`, `npm run build`, and `git diff --check`: PASS. Full `npm test`: 149 files / 1,247 tests PASS (30.83s). npm reports skipped dependency lifecycle scripts under the local npm policy; repository postinstall completed.
- A physical dependency closure outside the repository passes child-process import, real Loader installation, disable/enable, failed-update rollback and stop tests. This uses locally signed test fixtures, not a production enterprise service.
- Real Harness startup using bundled Node `24.9.0`, fresh DSH_HOME, fresh launch directory and the production patch configuration: PASS on `darwin-arm64`; authenticated `/api/enterprise.market.state` responds successfully. Electron Broker is absent in this headless probe, so desktop version is empty and enterprise is disconnected.
- Real enterprise login/install, current distributable/native UI, Windows native path/file-lock/generation acceptance: `NOT_RUN` (no acceptance environment). Windows invalid-path validation tests ran on macOS. PR remains pending these acceptance gates. Old generation-directory reclamation is follow-up work.

## 2026-09-22 — Enterprise plugin market on V0.9.2

- Baseline: remote `upstream/V0.9.2@294e9290262dd68f402fa526cfb6aeacd571f627`; worktree `.worktrees/dsh-desktop-v092-enterprise-market`, branch `codex/v092-enterprise-plugin-market`; installed Harness `0.1.5-rc.2`.
- Added the authenticated “From enterprise / 来自企业” plugins tab with catalog pagination, install/update, enable/disable and uninstall actions. Locale registration follows the current product language.
- Added the main-process market Broker boundary with restricted endpoints, account binding, bounded downloads and one 401 refresh retry. Enterprise credentials remain in Electron main.
- Ported signed policy enforcement, offline artifact validation, generation isolation, real Cordis Loader activation, update rollback and the offline bundle packaging tool from the local enterprise trial.
- Verification: `npm ci` and postinstall completed; `npm run typecheck`, `npm run build`, `git diff --check` passed; full `npm test`: 149 files / 1,240 tests passed. Behavioral coverage includes account changes, late UI responses, per-plugin actions, English localization, Broker requests, signed leases, real Loader and rollback.
- Current-branch native UI, real enterprise backend login/plugin installation, new distributable package and Windows native acceptance: `NOT_RUN`. The previously demonstrated r2 trial package remains a separate artifact.
- Feature and backend requirements: [enterprise-plugin-market.zh.md](enterprise-plugin-market.zh.md).


2026-09-20: implemented and verified with focused automated checks.

- Baseline: `dataelement/dsh-desktop:V0.9.1` at `9b2d2cf17e8f25f3c3cc1af297a25760cb0731f0`, Harness `0.1.5-rc.2`. The remote `v0.9.0` branch was absent when verified.
- The model-switch producer resolves adapter model names and commits them into the notice content and summary. Request configuration continues to use provider/model IDs. Saved notices retain the names recorded at the switch.
- The conversation row shows “已切换模型” / “Model changed” and the saved name transition. Its expanded explanation uses the selected UI language. Other context producers retain their existing rendering.
- Cross-provider switches use provider display names. Equal labels include route IDs for disambiguation. Missing metadata falls back to stable route IDs so an unavailable old catalog entry can still lead to a usable new model. Existing notices retain their saved summaries.
- The runtime correction is shipped through the repository's pinned Harness dependency distribution in `patches/`, updating both the package entry and standalone module. The chat bundle contains the corresponding localized renderer.

Verification:

- `npm ci --ignore-scripts --offline --no-audit --no-fund` followed by `npx --no-install patch-package`: PASS from a fresh dependency tree.
- `npx vitest run test/model-switch-display.test.ts test/model-selection-search-patch.test.ts test/model-reasoning-efforts-patch.test.ts test/local-path-links.test.ts`: 29 tests passed, including 8 new runtime/browser-bundle cases.
- `npm run typecheck`: PASS.
- `npm run build`: PASS.
- Native Desktop login, real provider calls, and installed-app UI acceptance: `NOT_RUN`.
Loading
Loading