@deepseek-ai/dsh-client-ui-plugin-manager 0.1.7-alpha.2 → 0.1.7-rc.1

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.i18n.yaml CHANGED
@@ -2,5 +2,5 @@
2
2
  # side as of the last confirmed-consistent state. Both languages carry equal authority;
3
3
  # after editing either side, bring the other along and re-record with:
4
4
  # pnpm run verify-translation-pairing --write packages/client/ui-plugin-manager/README.md
5
- README.md: a7e212adfc0b0ee03e4e8aa77fdf12cfa52205bf
6
- README.zh.md: be30b1af3624e6c5fa40fa6caf98f6d3e5bee323
5
+ README.md: ae6bae12c8fead7ba4a4f3529a16b8a5225f9e3b
6
+ README.zh.md: 4ee4871f69dbcf4e14cdaa931c041631780a6e38
package/README.md CHANGED
@@ -25,7 +25,7 @@ Use the **Plugins** entry in the Web sidebar to manage the profile's installed b
25
25
  <a id="use-this-package"></a>
26
26
  ## Use this package
27
27
 
28
- Select **Plugins** in the sidebar. The page reads the inventory and the bundles through `api-remotes` when first opened; a Host without a managed profile shows the page as unavailable. **Official** comes first and lists the bundles the installation ships for switching on — off until switched on, without an uninstall, and tagged **Beta** where the feature is one — followed by the official plugins that registered a configuration page; **Installed** lists the bundles the profile holds. Cards are listed by name, so switching a bundle on or off does not move its card. A dependency without a bundle patch is not a plugin and is not listed unless the profile selects it, in which case it carries a problem tag. Global configuration remains in the Settings **Plugins** section.
28
+ Select **Plugins** in the sidebar. The page reads the inventory and the bundles through `api-remotes` when first opened; a Host without a managed profile shows the page as unavailable. **Official** comes first and lists the bundles the installation ships for switching on — off until switched on, without an uninstall, and tagged **Experimental** for experimental features — followed by the official plugins that registered a configuration page; **Installed** lists the bundles the profile holds. Cards are listed by name, so switching a bundle on or off does not move its card. A dependency without a bundle patch is not a plugin and is not listed unless the profile selects it, in which case it carries a problem tag. Global configuration remains in the Settings **Plugins** section.
29
29
 
30
30
  Installed bundles and their plugin rows display their own title and description in the current UI language on cards and detail pages. Each field falls back from exported locale `meta` to the accessible `package.json` at that plugin address; the final title is the full package or module name, with no package description when both sources omit it. See [Plugin display metadata](../../../docs/cookbook/adding-a-package.md#plugin-display-metadata) for the author format. Bundle cards, details, and component rows display the image declared by their own `package.json.icon`; absent or undecodable images retain the default artwork. Installation previews still use registry or manifest information.
31
31
 
@@ -39,6 +39,8 @@ The Host sends GET requests to `https://registry.npmjs.org/-/ping` and `https://
39
39
 
40
40
  During preparation and download, **Cancel install** asks the Host to stop the run and waits for confirmation. Loading the bundle cannot be cancelled. Closing with ×, Escape, or the backdrop hides the dialog immediately and requests cancellation when possible. **View installation** reopens the same task with its output; another installation cannot start while its outcome is pending or unconfirmed. Confirmed cancellation returns to the spec and shows a toast; the manifest and lockfile are restored, while downloaded files can remain. A lost installation response triggers a request to recover the result; **Check installation status** and reconnect retry that request. If the Host has no active request, **Installation result unavailable** allows returning to editing after checking the plugin list. A cancellation sent before acceptance waits and retries automatically; a failed cancellation can be retried manually. Hidden tasks report their outcome through toasts without reopening the dialog.
41
41
 
42
+ When the Host attributes a network failure or timeout to a GitHub address and offers npmmirror, the dialog says **Cannot access GitHub**, or **GitHub connection timed out** for a timeout, and offers **Use mainland China mirror** or **Cancel**. Choosing the mirror returns to an empty package-name field and remembers the selected registry without starting another installation. Other failures retain their existing diagnostics and actions. The mirror supplies registry packages and dependencies, not the GitHub repository itself.
43
+
42
44
  ### Switching a bundle
43
45
 
44
46
  A bundle's page shows its full package name under the title, the spec that installs it elsewhere. A bundle's switch changes its layer selection. A profile with HMR recomposes before the operation completes; one without HMR, and a bundle a higher layer overrides, say so in a toast. A bundle the Host cannot read carries a problem tag and its reason on its page and cannot be switched on; one that provides the management components stays locked. The Host answers with error codes, which the page's dictionary words; pnpm's and the Loader's own diagnostics are shown as they are. The page excludes built-in profile bundles from cards and counts even when the profile holds them as dependencies or the Host reports an error. The Host inventory remains complete; the Settings Plugins section's Plugin list tab inspects their plugins.
@@ -63,7 +65,7 @@ The bundle's patch must declare the row under that id, and the registration exis
63
65
 
64
66
  ### Detail page extension points
65
67
 
66
- A plugin with something to say about a bundle, a row, or an official plugin it does not own contributes to that object's page through three list slots the page declares: `plugins.detail.actions` for a control at the head of the page, before the page's own switch and uninstall; `plugins.detail.badge` for a tag beside the title, after the version, beta, and problem tags; and `plugins.detail.section` for a section under the page's own content — after the rows on a bundle's page, after the configuration on a row's or an official plugin's page. Every entry is rendered with the page's `subject`: `{ kind: 'bundle', pkg }`, `{ kind: 'row', pkg, row }`, or `{ kind: 'item', id }`, where `pkg` and `row` carry the name, version, installed and enabled facts, and the row list a contribution decides on. An entry renders null for a subject it has nothing for and draws its own section chrome; the page orders entries by `order`.
68
+ A plugin with something to say about a bundle, a row, or an official plugin it does not own contributes to that object's page through three list slots the page declares: `plugins.detail.actions` for a control at the head of the page, before the page's own switch and uninstall; `plugins.detail.badge` for a tag beside the title, after the version, experimental, and problem tags; and `plugins.detail.section` for a section under the page's own content — after the rows on a bundle's page, after the configuration on a row's or an official plugin's page. Every entry is rendered with the page's `subject`: `{ kind: 'bundle', pkg }`, `{ kind: 'row', pkg, row }`, or `{ kind: 'item', id }`, where `pkg` and `row` carry the name, version, installed and enabled facts, and the row list a contribution decides on. An entry renders null for a subject it has nothing for and draws its own section chrome; the page orders entries by `order`.
67
69
 
68
70
  ```tsx ignore-check
69
71
  ctx.slots.inject('plugins.detail.section', () => ctx.slots.register({
@@ -99,7 +101,7 @@ The browser plugin registers the `plugins` sidebar entry and its `main` panel th
99
101
 
100
102
  Custom item pages use the Host entry id as their registration id; row pages use the bundle package and row id. The page owner supplies `form.state` and `form.mutate(operations, expectedRevision)` when the entry exposes editable Config fields. A custom page owns its draft and validation display, and may reuse `ConfigField` from ui-primitives. Bundle-wide pages can contain several entries and have no single form.
101
103
 
102
- The page's `main` registration declares `plugins.item`, `plugins.bundle.config`, and `plugins.row.config` as its children, so the slots exist while the page does and a registrant's `ctx.slots.inject` waits for them. `configLedgerSource` projects the three ledgers into one observable — the official items in ledger order with their labels resolved in the active locale, and the bundle and row keys — cached until a ledger or the locale moves; the page binds it as `useConfigLedger` beside the store and never names a configurable plugin itself. Which page is open is page-local state: the cards, a bundle, an official plugin, or a row of a bundle. A registration lives with the browser half that made it. `dsh-client-modules` attaches a package's browser half to the Loader row whose specifier is the bare package name, so every page a bundle registers, for itself or for any of its rows, goes away when that row is switched off; a sub-plugin whose page must outlive the other rows ships as its own package. Official packages whose names begin with `@deepseek-ai/dsh-experimental-` display the Beta marker.
104
+ The page's `main` registration declares `plugins.item`, `plugins.bundle.config`, and `plugins.row.config` as its children, so the slots exist while the page does and a registrant's `ctx.slots.inject` waits for them. `configLedgerSource` projects the three ledgers into one observable — the official items in ledger order with their labels resolved in the active locale, and the bundle and row keys — cached until a ledger or the locale moves; the page binds it as `useConfigLedger` beside the store and never names a configurable plugin itself. Which page is open is page-local state: the cards, a bundle, an official plugin, or a row of a bundle. A registration lives with the browser half that made it. `dsh-client-modules` attaches a package's browser half to the Loader row whose specifier is the bare package name, so every page a bundle registers, for itself or for any of its rows, goes away when that row is switched off; a sub-plugin whose page must outlive the other rows ships as its own package. Official packages whose names begin with `@deepseek-ai/dsh-experimental-` display the Experimental marker.
103
105
 
104
106
  `plugins.bundle.config` supplies bundle detail configuration, keyed by npm package name. `plugins.bundle.activation` offers bundle-owned guidance after explicit enablement from the list, with callbacks to dismiss it or open the bundle details. It does not appear merely because an enabled bundle was listed.
105
107
 
package/README.zh.md CHANGED
@@ -25,7 +25,7 @@ kind: "package-reference"
25
25
  <a id="use-this-package"></a>
26
26
  ## 使用本包
27
27
 
28
- 在侧栏选择**插件**。页面首次打开时通过 `api-remotes` 读取清单与组合包;没有受管 profile 的 Host 上页面显示为不可用。**官方**排在前面,列出安装随附、供开启的组合包——开启前保持关闭、没有卸载、属于 beta 功能的带 **Beta** 标签——其后是注册了配置页的官方插件;**已安装**列出 profile 持有的组合包。卡片按名称排序,启停组合包不会挪动它的卡片。没有组合包 patch 的依赖不是插件,除非 profile 选中了它才会带异常标签列出。全局配置仍在设置的**插件**分区中编辑。
28
+ 在侧栏选择**插件**。页面首次打开时通过 `api-remotes` 读取清单与组合包;没有受管 profile 的 Host 上页面显示为不可用。**官方**排在前面,列出安装随附、供开启的组合包——开启前保持关闭、没有卸载、属于实验性功能的带**实验性**标签——其后是注册了配置页的官方插件;**已安装**列出 profile 持有的组合包。卡片按名称排序,启停组合包不会挪动它的卡片。没有组合包 patch 的依赖不是插件,除非 profile 选中了它才会带异常标签列出。全局配置仍在设置的**插件**分区中编辑。
29
29
 
30
30
  已安装的组合包及其插件行在卡片和详情页中,按当前界面语言显示各自的标题与描述。每个字段先读取导出的 locale `meta`,缺失时回退到该插件地址下可访问的 `package.json`;标题最终使用完整包名或模块名,两处都没有描述时不提供包描述。作者格式见[插件展示元信息](../../../docs/cookbook/adding-a-package.zh.md#plugin-display-metadata)。组合包卡片、详情和组件行显示各自 `package.json.icon` 声明的图片;未声明或无法解码时保留默认插画。安装预览仍使用注册表或 manifest 信息。
31
31
 
@@ -39,6 +39,8 @@ Host 通过普通 fetch 代理向 `https://registry.npmjs.org/-/ping` 和 `https
39
39
 
40
40
  准备和下载期间,**取消安装**会请求 Host 停止运行并等待确认。加载组合包的阶段不可取消。点击 ×、按 Escape 或点击遮罩会立即隐藏对话框,并在可以取消时请求取消。**查看安装任务**会重新打开同一任务并保留输出;结果待定或尚未确认时不能发起另一项安装。确认取消后回到 spec 输入界面并显示 toast;manifest 与 lockfile 已恢复,已下载文件可能保留。安装响应丢失后会请求恢复结果;**核对安装状态**和重连会重试该请求。Host 已无活动请求时,**未能获取安装结果**允许检查插件列表后返回编辑。早于接收确认的取消请求会等待并自动重试;取消失败可手动重试。隐藏的任务通过 toast 通知结果,不会重新弹出对话框。
41
41
 
42
+ Host 将网络失败或超时归因于 GitHub 地址,且提供 npmmirror 时,对话框显示**无法访问 GitHub**,超时时显示**连接 GitHub 超时**,提供**改用国内镜像**和**取消**。选择镜像后回到空的包名输入框,记住所选安装源,不自动开始下一次安装。其他失败保留原有诊断和操作。镜像提供注册表中的包及依赖,不代替 GitHub 仓库下载。
43
+
42
44
  ### 切换一个组合包
43
45
 
44
46
  组合包页面在标题下方显示完整包名,也就是在别处安装它所需的 spec。组合包开关改变其层选择。启用了 HMR 的 profile 在操作完成前重组;没有 HMR 的 profile,以及被更高层覆盖的组合包,会以 toast 说明。Host 读不了的组合包带异常标签,其页面给出原因,且不能打开;提供管理组件的组合包保持锁定。Host 以错误码作答,由页面字典措辞;pnpm 与 Loader 自己的诊断原样显示。页面从卡片与数量中排除内置 profile 组合包,即使 profile 将它们列为依赖或 Host 报告了异常。Host 清单仍保留完整数据;设置中「插件」分区的「插件列表」标签页负责查看它们的插件。
@@ -63,7 +65,7 @@ ctx.slots.inject('plugins.row.config', () => ctx.slots.register({
63
65
 
64
66
  ### 详情页扩展点
65
67
 
66
- 对某个不属于自己的组合包、行或官方插件有话要说的插件,通过本页声明的三个 list slot 向该对象的页面贡献内容:`plugins.detail.actions` 在页头放一个控件,位于页面自己的开关和卸载之前;`plugins.detail.badge` 在标题旁放一个标签,位于版本、Beta 和异常标签之后;`plugins.detail.section` 在页面自身内容之下放一个区块——组合包页在组件列表之后,行页和官方插件页在配置之后。每个条目都以页面的 `subject` 渲染:`{ kind: 'bundle', pkg }`、`{ kind: 'row', pkg, row }` 或 `{ kind: 'item', id }`,其中 `pkg` 与 `row` 携带包名、版本、是否已安装、是否启用以及行列表这些供贡献者判断的事实。条目对无话可说的 subject 返回 null,自绘区块外观;页面按 `order` 排列条目。
68
+ 对某个不属于自己的组合包、行或官方插件有话要说的插件,通过本页声明的三个 list slot 向该对象的页面贡献内容:`plugins.detail.actions` 在页头放一个控件,位于页面自己的开关和卸载之前;`plugins.detail.badge` 在标题旁放一个标签,位于版本、实验性和异常标签之后;`plugins.detail.section` 在页面自身内容之下放一个区块——组合包页在组件列表之后,行页和官方插件页在配置之后。每个条目都以页面的 `subject` 渲染:`{ kind: 'bundle', pkg }`、`{ kind: 'row', pkg, row }` 或 `{ kind: 'item', id }`,其中 `pkg` 与 `row` 携带包名、版本、是否已安装、是否启用以及行列表这些供贡献者判断的事实。条目对无话可说的 subject 返回 null,自绘区块外观;页面按 `order` 排列条目。
67
69
 
68
70
  ```tsx ignore-check
69
71
  ctx.slots.inject('plugins.detail.section', () => ctx.slots.register({
@@ -99,7 +101,7 @@ Host 入口通过生成的 Remote 接口暴露 `pluginRegistryProbe.fastest()`
99
101
 
100
102
  自定义条目页以 Host 条目 id 作为注册 id;行页面使用 bundle 包名和行 id。当条目提供可编辑 Config 字段时,页面宿主传入 `form.state` 和 `form.mutate(operations, expectedRevision)`。自定义页面负责草稿和校验提示,并可复用 ui-primitives 的 `ConfigField`。整个 bundle 的页面可以包含多个条目,因此没有单一表单。
101
103
 
102
- 页面的 `main` 注册把 `plugins.item`、`plugins.bundle.config` 与 `plugins.row.config` 声明为子 slot,因此它们与页面同生,注册方的 `ctx.slots.inject` 会等到它们出现。`configLedgerSource` 把三份账本投影成一个可观察对象——按账本顺序排列、标签按当前语言解析的官方条目,以及组合包与行的键——在账本或语言变化前保持缓存;页面把它作为 `useConfigLedger` 绑在 store 旁边,自身从不点名任何可配置插件。打开的是哪一页是页面本地状态:卡片、某个组合包、某个官方插件,或组合包的某一行。注册与做出它的浏览器半侧同生共死。`dsh-client-modules` 只把一个包的浏览器半侧挂在说明符恰为包名的那一行 Loader 行上,所以组合包为自己或任一行注册的页面,都会在那一行被关闭时一起消失;需要在其他行关闭时仍保留页面的子插件,应作为独立的包发布。 名称以 `@deepseek-ai/dsh-experimental-` 开头的官方包显示 Beta 标记。
104
+ 页面的 `main` 注册把 `plugins.item`、`plugins.bundle.config` 与 `plugins.row.config` 声明为子 slot,因此它们与页面同生,注册方的 `ctx.slots.inject` 会等到它们出现。`configLedgerSource` 把三份账本投影成一个可观察对象——按账本顺序排列、标签按当前语言解析的官方条目,以及组合包与行的键——在账本或语言变化前保持缓存;页面把它作为 `useConfigLedger` 绑在 store 旁边,自身从不点名任何可配置插件。打开的是哪一页是页面本地状态:卡片、某个组合包、某个官方插件,或组合包的某一行。注册与做出它的浏览器半侧同生共死。`dsh-client-modules` 只把一个包的浏览器半侧挂在说明符恰为包名的那一行 Loader 行上,所以组合包为自己或任一行注册的页面,都会在那一行被关闭时一起消失;需要在其他行关闭时仍保留页面的子插件,应作为独立的包发布。 名称以 `@deepseek-ai/dsh-experimental-` 开头的官方包显示实验性标记。
103
105
 
104
106
  `plugins.bundle.config` 以 npm 包名为 key,提供 Bundle 详情配置。`plugins.bundle.activation` 在用户从列表显式启用后提供 Bundle 自有引导,并传入关闭引导和打开详情的回调。仅列出已启用的 Bundle 不会触发引导。
105
107
 
package/lib/client.js CHANGED
@@ -152,6 +152,7 @@ window.__ModuleLoader__.load({
152
152
  "stop-profile": "reasonStopProfile",
153
153
  "bundle-in-use": "reasonBundleInUse",
154
154
  "stale-approval": "reasonStaleApproval",
155
+ "incompatible-version": "reasonIncompatibleVersionUnnamed",
155
156
  "operation-error": "reasonOperationError"
156
157
  };
157
158
  /** The sentence a failed action opens with, by what was being done. */
@@ -163,13 +164,18 @@ window.__ModuleLoader__.load({
163
164
  rowDisable: "failedRowDisable"
164
165
  };
165
166
  /**
166
- * What a management error reads as: the code's sentence, or, for an
167
- * operation error, the Host's diagnostic as it is.
168
- * @param error - the Host's code and its diagnostic, when it has one.
167
+ * What a management error reads as: the code's sentence, one sentence per
168
+ * package an incompatibility names, or, for an operation error, the Host's diagnostic as it is.
169
+ * @param error - the Host's code, its diagnostic, and the packages an incompatibility names.
169
170
  * @param t - the manager's translate seat.
170
171
  * @returns the sentence.
171
172
  */
172
173
  function managementText(error, t) {
174
+ if (error.code === "incompatible-version" && error.incompatible !== void 0 && error.incompatible.length > 0) return error.incompatible.map((plugin) => t("reasonIncompatibleVersion", {
175
+ plugin: `${plugin.name}@${plugin.version}`,
176
+ runtime: plugin.runtimeVersion,
177
+ peers: Object.entries(plugin.peers).map(([name, range]) => `${name} ${range}`).join(", ")
178
+ })).join(" ");
173
179
  if (error.code !== "operation-error") return t(CODE_KEYS[error.code]);
174
180
  return error.diagnostic === void 0 || error.diagnostic === "" ? t("reasonOperationError") : error.diagnostic;
175
181
  }
@@ -227,7 +233,8 @@ window.__ModuleLoader__.load({
227
233
  case "failed": {
228
234
  const reason = notice.code === void 0 ? notice.reason : managementText({
229
235
  code: notice.code,
230
- diagnostic: notice.reason
236
+ diagnostic: notice.reason,
237
+ ...notice.incompatible === void 0 ? {} : { incompatible: notice.incompatible }
231
238
  }, t);
232
239
  return t(FAILED_KEYS[notice.action], { reason: reason === "" ? t("reasonOperationError") : reason });
233
240
  }
@@ -265,14 +272,27 @@ window.__ModuleLoader__.load({
265
272
  function isInstallPending(phase) {
266
273
  return phase === "starting" || phase === "running" || phase === "cancelling" || phase === "applying" || phase === "unconfirmed";
267
274
  }
275
+ /**
276
+ * Offer the configured mainland mirror after a confirmed GitHub connection failure.
277
+ * @param install - the installation and the Host's failure attribution.
278
+ * @returns the offered mirror URL, or undefined when this recovery does not apply.
279
+ */
280
+ function githubRecoveryRegistry(install) {
281
+ if (install.phase !== "failed" || install.failure?.failedAt !== "spec-host" || install.failure.kind !== "network" && install.failure.kind !== "timeout") return void 0;
282
+ const host = install.subject?.host?.toLowerCase().split(":")[0];
283
+ if (host !== "github.com" && !host?.endsWith(".github.com")) return void 0;
284
+ return offeredRegistries(install.registries).find((registry) => registry !== null && new URL(registry).hostname === "registry.npmmirror.com");
285
+ }
268
286
  /** A refused answer or a change the Host could not apply, carrying what it said and, for a refusal, its code. */
269
287
  var RemoteAnswerError = class extends Error {
270
288
  reason;
271
289
  code;
272
- constructor(reason, code) {
290
+ incompatible;
291
+ constructor(reason, code, incompatible) {
273
292
  super(reason);
274
293
  this.reason = reason;
275
294
  this.code = code;
295
+ this.incompatible = incompatible;
276
296
  this.name = "RemoteAnswerError";
277
297
  }
278
298
  };
@@ -281,6 +301,7 @@ window.__ModuleLoader__.load({
281
301
  return {
282
302
  reason: error?.diagnostic ?? "",
283
303
  ...error === void 0 ? {} : { code: error.code },
304
+ ...error?.incompatible === void 0 ? {} : { incompatible: error.incompatible },
284
305
  ...kind === void 0 ? {} : { kind },
285
306
  ...failedAt === void 0 ? {} : { failedAt },
286
307
  ...pendingBuilds === void 0 || pendingBuilds.length === 0 ? {} : { pendingBuilds }
@@ -289,10 +310,12 @@ window.__ModuleLoader__.load({
289
310
  /** The notice a thrown failure becomes: a refusal keeps its code, anything else its words. */
290
311
  function failedNotice(error, subject, seq) {
291
312
  const code = error instanceof RemoteAnswerError ? error.code : void 0;
313
+ const incompatible = error instanceof RemoteAnswerError ? error.incompatible : void 0;
292
314
  return {
293
315
  kind: "failed",
294
316
  reason: reasonOf(error),
295
317
  ...code === void 0 ? {} : { code },
318
+ ...incompatible === void 0 ? {} : { incompatible },
296
319
  ...subject,
297
320
  seq
298
321
  };
@@ -374,13 +397,14 @@ window.__ModuleLoader__.load({
374
397
  };
375
398
  /** The dialog back at its spec: the run and its outcome forgotten, the spec and the registries kept. */
376
399
  function specAgain(install) {
377
- const { open, spec, registries, registry } = install;
400
+ const { open, spec, registries, registry, mirrorRecovery } = install;
378
401
  return {
379
402
  ...IDLE_INSTALL,
380
403
  open,
381
404
  spec,
382
405
  registries,
383
- registry
406
+ registry,
407
+ ...mirrorRecovery === void 0 ? {} : { mirrorRecovery }
384
408
  };
385
409
  }
386
410
  /**
@@ -530,6 +554,22 @@ window.__ModuleLoader__.load({
530
554
  registryOpen: true
531
555
  } });
532
556
  },
557
+ useGithubMirror: () => {
558
+ const install = this.getSnapshot().install;
559
+ const mirror = githubRecoveryRegistry(install);
560
+ if (mirror === void 0) return;
561
+ const registry = {
562
+ kind: "offered",
563
+ registry: mirror
564
+ };
565
+ this.registryMemory.set(registry);
566
+ this.patch({ install: {
567
+ ...specAgain(install),
568
+ spec: "",
569
+ mirrorRecovery: true,
570
+ registry
571
+ } });
572
+ },
533
573
  approveBuildsAndRetry: () => {
534
574
  this.approveBuildsAndRetry();
535
575
  },
@@ -1074,7 +1114,7 @@ window.__ModuleLoader__.load({
1074
1114
  if (!answer.ok) throw new RemoteAnswerError(answer.error.message);
1075
1115
  const result = answer.value;
1076
1116
  switch (result.application) {
1077
- case "failed": throw new RemoteAnswerError(result.error?.diagnostic ?? "", result.error?.code);
1117
+ case "failed": throw new RemoteAnswerError(result.error?.diagnostic ?? "", result.error?.code, result.error?.incompatible);
1078
1118
  case "cancelled":
1079
1119
  this.patch({ notice: {
1080
1120
  kind: "cancelled",
@@ -1996,6 +2036,10 @@ window.__ModuleLoader__.load({
1996
2036
  */
1997
2037
  function failureText(failure, t, install) {
1998
2038
  if (failure === null) return t("installFailureGeneric");
2039
+ if (failure.code === "incompatible-version") return managementText({
2040
+ code: failure.code,
2041
+ ...failure.incompatible === void 0 ? {} : { incompatible: failure.incompatible }
2042
+ }, t);
1999
2043
  if (failure.kind === "build-blocked" && !failure.pendingBuilds?.length) return t("installFailureBuildBlockedManual");
2000
2044
  const host = install?.subject?.host;
2001
2045
  if (failure.failedAt === "spec-host" && host !== void 0) return t("installFailureNetworkHost", { host });
@@ -2037,7 +2081,7 @@ window.__ModuleLoader__.load({
2037
2081
  * and failed screens over the same subject card. A failed run that left
2038
2082
  * install scripts undecided shows them for approval in place of plain retry.
2039
2083
  */
2040
- function InstallDialog({ install, t, onClose, onEditSpec, onRun, onCancel, onReconcile, onToggleDetails, onEnableNow, onApproveBuilds, onToggleRegistry, onChooseRegistry, onChangeRegistry }) {
2084
+ function InstallDialog({ install, t, onClose, onEditSpec, onRun, onCancel, onReconcile, onToggleDetails, onEnableNow, onApproveBuilds, onToggleRegistry, onChooseRegistry, onChangeRegistry, onUseGithubMirror }) {
2041
2085
  const errorId = (0, react.useId)();
2042
2086
  const guideId = (0, react.useId)();
2043
2087
  const approvalId = (0, react.useId)();
@@ -2069,6 +2113,23 @@ window.__ModuleLoader__.load({
2069
2113
  document.removeEventListener("keydown", onKeyDown, true);
2070
2114
  };
2071
2115
  }, [registryShown, onToggleRegistry]);
2116
+ if (githubRecoveryRegistry(install) !== void 0) return (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
2117
+ open: install.open,
2118
+ onClose,
2119
+ title: t(install.failure?.kind === "timeout" ? "installGithubTimeoutTitle" : "installGithubFailedTitle"),
2120
+ closeLabel: t("close"),
2121
+ description: t("installGithubFailedDescription"),
2122
+ footer: (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [(0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
2123
+ variant: "outline",
2124
+ onClick: onClose,
2125
+ children: t("cancel")
2126
+ }), (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
2127
+ variant: "primary",
2128
+ autoFocus: true,
2129
+ onClick: onUseGithubMirror,
2130
+ children: t("installUseGithubMirror")
2131
+ })] })
2132
+ });
2072
2133
  if (phase === "idle" || phase === "checking") {
2073
2134
  const checking = phase === "checking";
2074
2135
  const empty = install.spec.trim() === "";
@@ -2083,7 +2144,7 @@ window.__ModuleLoader__.load({
2083
2144
  onClose,
2084
2145
  title: t("installTitle"),
2085
2146
  closeLabel: t("close"),
2086
- description: t("installDescription"),
2147
+ ...install.mirrorRecovery ? {} : { description: t("installDescription") },
2087
2148
  className: PluginManagerPage_module_css_default.installDialog,
2088
2149
  contentClassName: PluginManagerPage_module_css_default.installContent,
2089
2150
  footer: (0, react_jsx_runtime.jsxs)(_deepseek_ai_dsh_client_ui_primitives.Button, {
@@ -2101,10 +2162,11 @@ window.__ModuleLoader__.load({
2101
2162
  className: PluginManagerPage_module_css_default.installField,
2102
2163
  children: (0, react_jsx_runtime.jsx)("input", {
2103
2164
  type: "text",
2165
+ autoFocus: install.mirrorRecovery === true,
2104
2166
  value: install.spec,
2105
2167
  placeholder: t("installSpecPlaceholder"),
2106
2168
  disabled: checking,
2107
- "aria-label": t("installSpecLabel"),
2169
+ "aria-label": t(install.mirrorRecovery ? "installPackageLabel" : "installSpecLabel"),
2108
2170
  "aria-invalid": install.inputError !== null,
2109
2171
  "aria-describedby": install.inputError === null ? void 0 : errorId,
2110
2172
  onChange: (event) => {
@@ -2824,7 +2886,8 @@ window.__ModuleLoader__.load({
2824
2886
  onApproveBuilds: props.approveBuildsAndRetry,
2825
2887
  onToggleRegistry: props.toggleRegistryOptions,
2826
2888
  onChooseRegistry: props.chooseRegistry,
2827
- onChangeRegistry: props.changeRegistry
2889
+ onChangeRegistry: props.changeRegistry,
2890
+ onUseGithubMirror: props.useGithubMirror
2828
2891
  }),
2829
2892
  state.confirm === null ? null : (0, react_jsx_runtime.jsx)(ConfirmDialog, {
2830
2893
  name: packageText(state.packages.find((pkg) => pkg.name === state.confirm?.packageName) ?? { name: state.confirm.packageName }, resolveText).title,
@@ -2865,7 +2928,7 @@ window.__ModuleLoader__.load({
2865
2928
  bundlesTitle: "已安装",
2866
2929
  officialTitle: "官方",
2867
2930
  statusProblem: "异常",
2868
- statusBeta: "Beta",
2931
+ statusBeta: "实验性",
2869
2932
  reasonLabel: "原因",
2870
2933
  metadataError: "包元信息错误:{error}",
2871
2934
  versionTag: "v{version}",
@@ -2935,6 +2998,11 @@ window.__ModuleLoader__.load({
2935
2998
  installingTitle: "插件安装中…",
2936
2999
  installedTitle: "已安装",
2937
3000
  installFailedTitle: "插件安装失败",
3001
+ installGithubFailedTitle: "无法访问 GitHub",
3002
+ installGithubTimeoutTitle: "连接 GitHub 超时",
3003
+ installGithubFailedDescription: "请尝试其他安装来源。",
3004
+ installUseGithubMirror: "改用国内镜像",
3005
+ installPackageLabel: "插件包名",
2938
3006
  installEdit: "编辑",
2939
3007
  installEditAria: "返回编辑",
2940
3008
  installCancelAndEdit: "取消安装并返回编辑",
@@ -3026,6 +3094,8 @@ window.__ModuleLoader__.load({
3026
3094
  reasonStopProfile: "这个 profile 没有启用 HMR,正在使用的包要停止后用 dsh plugin 卸载",
3027
3095
  reasonBundleInUse: "其他配置仍在使用这个组合包的组件,请先停用它们",
3028
3096
  reasonStaleApproval: "待允许的安装脚本列表已变化,请重新安装以刷新",
3097
+ reasonIncompatibleVersion: "{plugin} 与 DSH {runtime} 不兼容(要求 {peers}),运行它可能导致崩溃或数据丢失。请安装与当前 DSH 兼容的插件版本。",
3098
+ reasonIncompatibleVersionUnnamed: "这个插件与当前 DSH 版本不兼容,运行它可能导致崩溃或数据丢失",
3029
3099
  reasonOperationError: "Host 报告了一个错误"
3030
3100
  };
3031
3101
  /** English dictionary checked against the Chinese key set. */
@@ -3045,7 +3115,7 @@ window.__ModuleLoader__.load({
3045
3115
  bundlesTitle: "Installed",
3046
3116
  officialTitle: "Official",
3047
3117
  statusProblem: "Problem",
3048
- statusBeta: "Beta",
3118
+ statusBeta: "Experimental",
3049
3119
  reasonLabel: "Reason",
3050
3120
  metadataError: "Package metadata error: {error}",
3051
3121
  versionTag: "v{version}",
@@ -3115,6 +3185,11 @@ window.__ModuleLoader__.load({
3115
3185
  installingTitle: "Installing the plugin…",
3116
3186
  installedTitle: "Installed",
3117
3187
  installFailedTitle: "The plugin could not be installed",
3188
+ installGithubFailedTitle: "Cannot access GitHub",
3189
+ installGithubTimeoutTitle: "GitHub connection timed out",
3190
+ installGithubFailedDescription: "Try another installation source.",
3191
+ installUseGithubMirror: "Use mainland China mirror",
3192
+ installPackageLabel: "Plugin package name",
3118
3193
  installEdit: "Edit",
3119
3194
  installEditAria: "Back to editing",
3120
3195
  installCancelAndEdit: "Cancel installation and return to editing",
@@ -3206,6 +3281,8 @@ window.__ModuleLoader__.load({
3206
3281
  reasonStopProfile: "This profile runs without HMR; stop it and uninstall the package with dsh plugin.",
3207
3282
  reasonBundleInUse: "Other configuration still uses this bundle's components; switch them off first.",
3208
3283
  reasonStaleApproval: "The pending script approvals changed; install again to refresh them.",
3284
+ reasonIncompatibleVersion: "{plugin} is incompatible with DSH {runtime} (requires {peers}); running it may cause crashes or data loss. Install a plugin version compatible with this DSH.",
3285
+ reasonIncompatibleVersionUnnamed: "This plugin is incompatible with the running DSH version; running it may cause crashes or data loss.",
3209
3286
  reasonOperationError: "The Host reported an error."
3210
3287
  };
3211
3288
  //#endregion
@@ -86,6 +86,11 @@ export declare const zh: {
86
86
  installingTitle: string;
87
87
  installedTitle: string;
88
88
  installFailedTitle: string;
89
+ installGithubFailedTitle: string;
90
+ installGithubTimeoutTitle: string;
91
+ installGithubFailedDescription: string;
92
+ installUseGithubMirror: string;
93
+ installPackageLabel: string;
89
94
  installEdit: string;
90
95
  installEditAria: string;
91
96
  installCancelAndEdit: string;
@@ -177,6 +182,8 @@ export declare const zh: {
177
182
  reasonStopProfile: string;
178
183
  reasonBundleInUse: string;
179
184
  reasonStaleApproval: string;
185
+ reasonIncompatibleVersion: string;
186
+ reasonIncompatibleVersionUnnamed: string;
180
187
  reasonOperationError: string;
181
188
  };
182
189
  /** Plugin manager locale key union. */
@@ -268,6 +275,11 @@ export declare const en: {
268
275
  installingTitle: string;
269
276
  installedTitle: string;
270
277
  installFailedTitle: string;
278
+ installGithubFailedTitle: string;
279
+ installGithubTimeoutTitle: string;
280
+ installGithubFailedDescription: string;
281
+ installUseGithubMirror: string;
282
+ installPackageLabel: string;
271
283
  installEdit: string;
272
284
  installEditAria: string;
273
285
  installCancelAndEdit: string;
@@ -359,6 +371,8 @@ export declare const en: {
359
371
  reasonStopProfile: string;
360
372
  reasonBundleInUse: string;
361
373
  reasonStaleApproval: string;
374
+ reasonIncompatibleVersion: string;
375
+ reasonIncompatibleVersionUnnamed: string;
362
376
  reasonOperationError: string;
363
377
  };
364
378
  //# sourceMappingURL=locales.d.ts.map
@@ -6,7 +6,7 @@
6
6
  * change made on another surface shows here without a manual refresh.
7
7
  */
8
8
  import type { Context as ClientContext } from '@deepseek-ai/cordis';
9
- import type { BundleInfo, ManagementError, PluginEntryId, PluginInfo, PluginInspectProblem, PluginInstallFailureKind, PluginInstallLogChunk, PluginInstallProgress, PluginInstallRequestId, PluginRegistries, PluginSpecInspection, ReadOnlyReason, Registry } from '@deepseek-ai/dsh-api-remotes/client';
9
+ import type { BundleInfo, IncompatiblePlugin, ManagementError, PluginEntryId, PluginInfo, PluginInspectProblem, PluginInstallFailureKind, PluginInstallLogChunk, PluginInstallProgress, PluginInstallRequestId, PluginRegistries, PluginSpecInspection, ReadOnlyReason, Registry } from '@deepseek-ai/dsh-api-remotes/client';
10
10
  import { type SnapshotStore } from '@deepseek-ai/dsh-client-store';
11
11
  import type { HostObservable } from '@deepseek-ai/dsh-client-ui-slots';
12
12
  import type { LocalizedText, PluginLocalizedMeta } from '@deepseek-ai/dsh-package-manifest';
@@ -38,6 +38,8 @@ export type ManagerNotice = {
38
38
  readonly code?: ManagementError['code'];
39
39
  /** The Host's diagnostic or the transport's words, shown verbatim; empty when the code says it all. */
40
40
  readonly reason: string;
41
+ /** The packages an `incompatible-version` refusal names. */
42
+ readonly incompatible?: readonly IncompatiblePlugin[];
41
43
  readonly packageName?: string;
42
44
  readonly seq: number;
43
45
  };
@@ -127,6 +129,8 @@ export interface InstallState {
127
129
  readonly open: boolean;
128
130
  /** The package spec as typed. */
129
131
  readonly spec: string;
132
+ /** The spec form was reopened after switching away from a failed GitHub address. */
133
+ readonly mirrorRecovery?: boolean;
130
134
  /** The registries the Host configured, read when the dialog opens; null until the Host answered. */
131
135
  readonly registries: PluginRegistries | null;
132
136
  /** The registry this install asks first: the one last used, else the Host's first. */
@@ -166,6 +170,8 @@ export interface InstallState {
166
170
  readonly failure: {
167
171
  readonly reason: string;
168
172
  readonly code?: ManagementError['code'];
173
+ /** The packages an `incompatible-version` refusal names. */
174
+ readonly incompatible?: readonly IncompatiblePlugin[];
169
175
  readonly kind?: PluginInstallFailureKind;
170
176
  /** What the last failed run could not reach, as the Host attributed it: the registry, or the spec's own host. */
171
177
  readonly failedAt?: 'registry' | 'spec-host';
@@ -183,6 +189,12 @@ export interface InstallState {
183
189
  * @returns true while a Host result or a check for an active request is outstanding.
184
190
  */
185
191
  export declare function isInstallPending(phase: InstallState['phase']): boolean;
192
+ /**
193
+ * Offer the configured mainland mirror after a confirmed GitHub connection failure.
194
+ * @param install - the installation and the Host's failure attribution.
195
+ * @returns the offered mirror URL, or undefined when this recovery does not apply.
196
+ */
197
+ export declare function githubRecoveryRegistry(install: InstallState): string | undefined;
186
198
  /** A destructive action waiting for the user's confirmation: a package's uninstall. */
187
199
  export interface ConfirmState {
188
200
  readonly action: 'uninstall';
@@ -231,6 +243,8 @@ export interface PluginManagerFace {
231
243
  chooseRegistry: (choice: RegistryChoice) => void;
232
244
  /** From the failed screen: back to the spec with the registry options unfolded. */
233
245
  changeRegistry: () => void;
246
+ /** Return from a GitHub connection failure to an empty spec with the offered mainland mirror selected. */
247
+ useGithubMirror: () => void;
234
248
  /** Allow the install scripts the failed run left pending, saved for this profile, and run the same spec again. */
235
249
  approveBuildsAndRetry: () => void;
236
250
  /** Leave the check or the failed screen for the spec, or ask the Host to stop the run and wait for its cleanup. */
@@ -1,5 +1,5 @@
1
1
  /** Display labels and toast sentences for global plugin management. */
2
- import type { ManagementError, Registry } from '@deepseek-ai/dsh-api-remotes/client';
2
+ import type { IncompatiblePlugin, ManagementError, Registry } from '@deepseek-ai/dsh-api-remotes/client';
3
3
  import type { PropsLocale } from '@deepseek-ai/dsh-client-ui-slots';
4
4
  import type { ManagerNotice, PackageRow, PackageView, PluginManagerFace } from './manager-store.ts';
5
5
  /** The translate seat of the manager's dictionary. */
@@ -17,15 +17,16 @@ export declare function registryText(registry: Registry, t: Translate, resolved:
17
17
  host: string;
18
18
  };
19
19
  /**
20
- * What a management error reads as: the code's sentence, or, for an
21
- * operation error, the Host's diagnostic as it is.
22
- * @param error - the Host's code and its diagnostic, when it has one.
20
+ * What a management error reads as: the code's sentence, one sentence per
21
+ * package an incompatibility names, or, for an operation error, the Host's diagnostic as it is.
22
+ * @param error - the Host's code, its diagnostic, and the packages an incompatibility names.
23
23
  * @param t - the manager's translate seat.
24
24
  * @returns the sentence.
25
25
  */
26
26
  export declare function managementText(error: {
27
27
  readonly code: ManagementError['code'];
28
28
  readonly diagnostic?: string;
29
+ readonly incompatible?: readonly IncompatiblePlugin[];
29
30
  }, t: Translate): string;
30
31
  /**
31
32
  * Compact a package name to what a person calls it.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@deepseek-ai/dsh-client-ui-plugin-manager",
3
3
  "description": "Plugin management for the dsh web client: the sidebar Plugins panel installs, enables, disables, retries, and composes installed plugin packages",
4
- "version": "0.1.7-alpha.2",
4
+ "version": "0.1.7-rc.1",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -56,19 +56,19 @@
56
56
  "react": "^18.2.0",
57
57
  "react-dom": "^18.2.0",
58
58
  "@deepseek-ai/cordis": "~4.0.4",
59
- "@deepseek-ai/dsh-api-remotes": "0.1.7-alpha.2",
60
- "@deepseek-ai/dsh-client-locale": "0.1.7-alpha.2",
61
- "@deepseek-ai/dsh-client-store": "0.1.7-alpha.2",
62
- "@deepseek-ai/dsh-client-test-runtime": "0.1.7-alpha.2",
63
- "@deepseek-ai/dsh-client-ui-layout": "0.1.7-alpha.2",
64
- "@deepseek-ai/dsh-client-ui-primitives": "0.1.7-alpha.2",
65
- "@deepseek-ai/dsh-client-ui-renderer": "0.1.7-alpha.2",
66
- "@deepseek-ai/dsh-client-ui-sidebar": "0.1.7-alpha.2",
67
- "@deepseek-ai/dsh-package-manifest": "0.1.7-alpha.2",
68
- "@deepseek-ai/dsh-util-crypto": "0.1.7-alpha.2",
69
- "@deepseek-ai/dsh-plugin-manager": "0.1.7-alpha.2",
70
- "@deepseek-ai/dsh-client-ui-slots": "0.1.7-alpha.2",
71
- "@deepseek-ai/dsh-client-ui-settings": "0.1.7-alpha.2"
59
+ "@deepseek-ai/dsh-client-locale": "0.1.7-rc.1",
60
+ "@deepseek-ai/dsh-api-remotes": "0.1.7-rc.1",
61
+ "@deepseek-ai/dsh-client-store": "0.1.7-rc.1",
62
+ "@deepseek-ai/dsh-client-test-runtime": "0.1.7-rc.1",
63
+ "@deepseek-ai/dsh-client-ui-primitives": "0.1.7-rc.1",
64
+ "@deepseek-ai/dsh-client-ui-renderer": "0.1.7-rc.1",
65
+ "@deepseek-ai/dsh-client-ui-layout": "0.1.7-rc.1",
66
+ "@deepseek-ai/dsh-client-ui-sidebar": "0.1.7-rc.1",
67
+ "@deepseek-ai/dsh-client-ui-slots": "0.1.7-rc.1",
68
+ "@deepseek-ai/dsh-package-manifest": "0.1.7-rc.1",
69
+ "@deepseek-ai/dsh-util-crypto": "0.1.7-rc.1",
70
+ "@deepseek-ai/dsh-client-ui-settings": "0.1.7-rc.1",
71
+ "@deepseek-ai/dsh-plugin-manager": "0.1.7-rc.1"
72
72
  },
73
73
  "files": [
74
74
  "lib/index.js",
@@ -81,9 +81,9 @@
81
81
  ],
82
82
  "dependencies": {
83
83
  "zod": "^4.4.3",
84
- "@deepseek-ai/dsh-timeout": "0.1.7-alpha.2",
84
+ "@deepseek-ai/dsh-timeout": "0.1.7-rc.1",
85
85
  "@deepseek-ai/schemastery": "~3.18.4",
86
- "@deepseek-ai/dsh-typert-protocol": "0.1.7-alpha.2"
86
+ "@deepseek-ai/dsh-typert-protocol": "0.1.7-rc.1"
87
87
  },
88
88
  "scripts": {
89
89
  "bundle": "tsdown",