@deepseek-ai/dsh-client-ui-plugin-manager 0.1.7-alpha.1 → 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 +2 -2
- package/README.md +16 -6
- package/README.zh.md +16 -6
- package/lib/client.js +161 -26
- package/lib/index.js +140 -4
- package/lib/typert.host.d.ts +3 -0
- package/lib/typert.host.js +53 -0
- package/lib/typert.remote-client.d.ts +21 -0
- package/lib/typert.remote-client.js +28 -0
- package/lib/types/client/locales.d.ts +14 -0
- package/lib/types/client/manager-store.d.ts +17 -1
- package/lib/types/client/presentation.d.ts +5 -4
- package/lib/types/index.d.ts +35 -3
- package/package.json +36 -18
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:
|
|
6
|
-
README.zh.md:
|
|
5
|
+
README.md: ae6bae12c8fead7ba4a4f3529a16b8a5225f9e3b
|
|
6
|
+
README.zh.md: 4ee4871f69dbcf4e14cdaa931c041631780a6e38
|
package/README.md
CHANGED
|
@@ -25,16 +25,22 @@ 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 **
|
|
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
|
|
|
32
32
|
### Installing a bundle
|
|
33
33
|
|
|
34
|
-
|
|
34
|
+
On first use, when pnpm uses the unconfigured official npm registry and npmmirror is offered, the Host probes both registries in parallel. The first successful HTTPS ping response selects the initial source. The dialog preserves remembered choices, manual selections, explicit manager configuration, and custom or unknown pnpm registries. An install clicked during initial probing waits for that bounded operation; a late result cannot overwrite a manual choice or reopen a closed dialog.
|
|
35
|
+
|
|
36
|
+
The Host sends GET requests to `https://registry.npmjs.org/-/ping` and `https://registry.npmmirror.com/-/ping` through its normal fetch proxy. A 2xx response wins; redirects and failures do not. The remaining request is cancelled, both response bodies are discarded, and cleanup completes before the result returns. The default deadline is 1500 ms and results, including unavailable results, are cached for five minutes. Configure `registryProbeTimeoutMs` and `registryProbeCacheTtlMs` on `ui-plugin-manager`; `registryProbeEnabled: false` disables probing. If both requests fail or time out, the existing default remains selected. No IP-location service or Session content is involved.
|
|
37
|
+
|
|
38
|
+
**Add plugin** takes a package name with an optional version, a Git address, a tarball, or an absolute local path; the dialog says a package name is what follows `dsh plugin add` in a README. **Install guide and examples** under the field opens a guide that shows the three common forms with an example each; **Use example** drops one into the field. **Registry** beside it names the registry the install asks first and unfolds the choice: the default registry, pnpm's own, titled with the host the machine's pnpm configuration names; each mirror the Host configured (`pluginManager.registries`), npmmirror as the mainland China mirror; and a typed http(s) address. The options float over the dialog from the control, so unfolding them never lengthens the card, and the card's content scrolls when the guide makes it taller than the viewport. The initial choice follows the response comparison above; the choice is then remembered in this browser (`localStorage`) and starts the next dialog, and a remembered address the Host no longer offers stays as a typed one and remains the requested address when installation starts before the registry list returns. **Install** first asks the Host to read what the spec names (`pluginManager.inspect`) at the chosen registry: a name the list already shows, a name no registry has, a path without a package, a package without a bundle patch, or a spec pnpm would refuse comes back under the field as one sentence, with the spec kept for editing; when no registry could be reached, the sentence names every one asked. The install then starts at the registry that answered. An accepted spec opens the installing screen, which shows the package's name, one-liner, and version as the Host read them and folds pnpm's command and output behind **Show install details**. A finished install offers **Enable now**, which switches the new bundle on, closes the dialog, and scrolls the list to it; closing instead leaves it installed and off. While the Host moves on to another registry, the installing screen says which registry could not serve the package and which is asked now, and each pnpm run behind the details carries a badge naming its registry. A failed install says what went wrong in one line, as the Host attributed it — no registry could be reached, naming every one asked; the host a GitHub address or a tarball link is fetched from could not be reached, which no registry helps; the package was not found, the disk is full, the profile is not writable, pnpm blocked a build script — with pnpm's output behind the details and **Retry** at hand, and **Change registry** beside it when the Host laid the failure at the registry it asked, which returns to the spec with the registry options open; the Host has already put the profile files back. When pnpm blocked a dependency's install scripts, the failed screen lists the packages whose scripts wait for permission and offers **Allow these scripts and retry** in place of **Retry**; the Host saves the permission in the profile's `pnpm-workspace.yaml`, which a failed run leaves as pnpm wrote it, then runs pnpm again, and the installed screen names what was allowed. A successful installation does not certify that a module can activate.
|
|
35
39
|
|
|
36
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.
|
|
37
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
|
+
|
|
38
44
|
### Switching a bundle
|
|
39
45
|
|
|
40
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.
|
|
@@ -59,7 +65,7 @@ The bundle's patch must declare the row under that id, and the registration exis
|
|
|
59
65
|
|
|
60
66
|
### Detail page extension points
|
|
61
67
|
|
|
62
|
-
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,
|
|
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`.
|
|
63
69
|
|
|
64
70
|
```tsx ignore-check
|
|
65
71
|
ctx.slots.inject('plugins.detail.section', () => ctx.slots.register({
|
|
@@ -83,6 +89,8 @@ Package management uses the profile's dependency records: installed bundles can
|
|
|
83
89
|
|
|
84
90
|
### Registration
|
|
85
91
|
|
|
92
|
+
The Host entry exposes `pluginRegistryProbe.fastest()` through the generated Remote interface. It shares pending comparisons, expires cached results, and aborts and awaits outstanding probes on unload. Calls after unload return a rejected Promise.
|
|
93
|
+
|
|
86
94
|
The browser plugin registers the `plugins` sidebar entry and its `main` panel through `ctx.slots.inject()`, so both follow late slot declaration, locale changes and teardown. The page is global and belongs to no Session. Display text comes from package metadata and the page's dictionary.
|
|
87
95
|
|
|
88
96
|
### The store
|
|
@@ -93,7 +101,7 @@ The browser plugin registers the `plugins` sidebar entry and its `main` panel th
|
|
|
93
101
|
|
|
94
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.
|
|
95
103
|
|
|
96
|
-
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
|
|
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.
|
|
97
105
|
|
|
98
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.
|
|
99
107
|
|
|
@@ -124,6 +132,8 @@ None; this package neither assembles nor sends a provider request.
|
|
|
124
132
|
|
|
125
133
|
## Known Limitations and Deferred Work
|
|
126
134
|
|
|
135
|
+
- Ping response time does not measure package-download throughput. Probes use the Host fetch route, which pnpm-specific proxy settings can differ from; users can always choose the registry manually, and installation retains its existing fallback rules.
|
|
136
|
+
|
|
127
137
|
<a id="known-limitations-and-deferred-work"></a>
|
|
128
138
|
|
|
129
139
|
|
|
@@ -134,7 +144,7 @@ These limits define the reach of the management view; they are current package c
|
|
|
134
144
|
- **Rows show a phase, not a reason** — a failed row reads as failed without the Host's error text; the Host log has it.
|
|
135
145
|
- **One install at a time** — the dialog runs one pnpm command; a second spec waits for the first to finish.
|
|
136
146
|
- **No version picker** — the spec is typed as pnpm accepts it; the page neither lists registry versions nor offers upgrades.
|
|
137
|
-
- **The registry choice is this browser's** — it lives in `localStorage`, so another browser
|
|
147
|
+
- **The registry choice is this browser's** — it lives in `localStorage`, so another browser applies its own initial recommendation; the `dsh plugin` command and the agent tool use the Host's configured registry.
|
|
138
148
|
- **Every registry read runs pnpm** — opening the dialog, the check, and the install each ask pnpm what its own configuration names; a machine without pnpm reads it as unknown and is offered no fallback.
|
|
139
149
|
|
|
140
150
|
<a id="dev-note"></a>
|
|
@@ -147,4 +157,4 @@ None.
|
|
|
147
157
|
|
|
148
158
|
</details>
|
|
149
159
|
|
|
150
|
-
**Runtime invariant:** No companion is published.
|
|
160
|
+
**Runtime invariant:** No companion is published. The panel reads Host-owned facts, and the registry-probe cache stores one comparison result without an independently maintained projection.
|
package/README.zh.md
CHANGED
|
@@ -25,16 +25,22 @@ kind: "package-reference"
|
|
|
25
25
|
<a id="use-this-package"></a>
|
|
26
26
|
## 使用本包
|
|
27
27
|
|
|
28
|
-
在侧栏选择**插件**。页面首次打开时通过 `api-remotes` 读取清单与组合包;没有受管 profile 的 Host
|
|
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
|
|
|
32
32
|
### 安装一个组合包
|
|
33
33
|
|
|
34
|
-
|
|
34
|
+
首次使用时,如果未显式配置安装源、pnpm 使用官方 npm 源,且列表提供 npmmirror,Host 会并发探测这两个源,选择最先成功响应 HTTPS ping 的源。已记住的选择、用户手选、管理器显式配置,以及自定义或未知的 pnpm 源均保留。在初次探测期间点击安装会等待这次有时限的操作;迟到结果不会覆盖手选或重新打开已关闭的对话框。
|
|
35
|
+
|
|
36
|
+
Host 通过普通 fetch 代理向 `https://registry.npmjs.org/-/ping` 和 `https://registry.npmmirror.com/-/ping` 发送 GET 请求,以最先返回 2xx 的源为结果;重定向和失败不参与选择。随后取消另一条请求、丢弃两个响应体,并在清理完成后返回。默认时限为 1500 毫秒,包括不可用结果在内的探测结果均缓存五分钟。在 `ui-plugin-manager` 上配置 `registryProbeTimeoutMs` 和 `registryProbeCacheTtlMs`;`registryProbeEnabled: false` 关闭探测。两条请求都失败或超时时保留现有默认源。不使用 IP 地区服务,也不发送 Session 内容。
|
|
37
|
+
|
|
38
|
+
**添加插件**接受包名(可带版本)、Git 地址、压缩包或本地绝对路径;对话框说明包名就是 README 里 `dsh plugin add` 后面的那一段。输入框下方的**插件安装引导和示例**展开一段引导,给出三种常见形式各一个示例;**填入示例**把示例填进输入框。旁边的**安装源**写着安装首先询问的注册表,展开后可选:默认安装源,即 pnpm 自身的注册表,标题带上本机 pnpm 配置指向的主机名;Host 配置的每个镜像(`pluginManager.registries`),npmmirror 显示为中国大陆镜像源;以及手动输入的 http(s) 地址。选项从控件上浮在对话框之上,展开不会拉长卡片;引导使卡片高过视口时,卡片内容可以滚动。初始选择遵循上文的响应比较规则;之后选择记在本浏览器(`localStorage`)里,下次打开对话框从它开始,Host 不再提供的已记住地址保留为手动输入的地址;在安装源列表返回前开始安装,也仍请求该地址。**安装**先让 Host 在所选注册表读出 spec 指向什么(`pluginManager.inspect`):列表中已有的名字、所有注册表都没有的名字、没有包的路径、没有组合包 patch 的包,或 pnpm 会拒绝的 spec,都以一句话回到输入框下方,spec 保留可继续编辑;所有注册表都连不上时,这句话列出问过的每一个。随后安装从作答的那个注册表开始。通过检查的 spec 打开安装中界面,展示 Host 读到的包名、一句话简介和版本,pnpm 的命令与输出折叠在**查看安装详情**之后。安装完成后提供**立即启用**:启用新组合包、关闭对话框并把列表滚动到它;直接关闭则让它保持已安装但关闭。Host 改问另一个注册表时,安装中界面会说明哪个源没能提供这个包、现在改问哪个,详情里每次 pnpm 运行都带一个写明所用源的标记。安装失败时按 Host 的归因用一行话说明原因——所有安装源都连不上,并列出问过的每一个;GitHub 地址或压缩包链接自身的主机连不上,换源无济于事;包不存在、磁盘已满、profile 不可写、pnpm 拦下了构建脚本——pnpm 输出在详情里,**重试**就在手边,Host 把失败归于所问注册表时还在旁边提供**更换安装源**,回到 spec 输入界面并展开安装源选项;Host 已经把 profile 文件放回原样。pnpm 拦下依赖的安装脚本时,失败界面列出等待允许的包,并以**允许这些脚本并重试**取代**重试**;Host 把授权写进 profile 的 `pnpm-workspace.yaml`(失败的运行保留 pnpm 写入的这个文件)再运行 pnpm,安装完成界面会说明允许了哪些脚本。安装成功不代表模块一定能够激活。
|
|
35
39
|
|
|
36
40
|
准备和下载期间,**取消安装**会请求 Host 停止运行并等待确认。加载组合包的阶段不可取消。点击 ×、按 Escape 或点击遮罩会立即隐藏对话框,并在可以取消时请求取消。**查看安装任务**会重新打开同一任务并保留输出;结果待定或尚未确认时不能发起另一项安装。确认取消后回到 spec 输入界面并显示 toast;manifest 与 lockfile 已恢复,已下载文件可能保留。安装响应丢失后会请求恢复结果;**核对安装状态**和重连会重试该请求。Host 已无活动请求时,**未能获取安装结果**允许检查插件列表后返回编辑。早于接收确认的取消请求会等待并自动重试;取消失败可手动重试。隐藏的任务通过 toast 通知结果,不会重新弹出对话框。
|
|
37
41
|
|
|
42
|
+
Host 将网络失败或超时归因于 GitHub 地址,且提供 npmmirror 时,对话框显示**无法访问 GitHub**,超时时显示**连接 GitHub 超时**,提供**改用国内镜像**和**取消**。选择镜像后回到空的包名输入框,记住所选安装源,不自动开始下一次安装。其他失败保留原有诊断和操作。镜像提供注册表中的包及依赖,不代替 GitHub 仓库下载。
|
|
43
|
+
|
|
38
44
|
### 切换一个组合包
|
|
39
45
|
|
|
40
46
|
组合包页面在标题下方显示完整包名,也就是在别处安装它所需的 spec。组合包开关改变其层选择。启用了 HMR 的 profile 在操作完成前重组;没有 HMR 的 profile,以及被更高层覆盖的组合包,会以 toast 说明。Host 读不了的组合包带异常标签,其页面给出原因,且不能打开;提供管理组件的组合包保持锁定。Host 以错误码作答,由页面字典措辞;pnpm 与 Loader 自己的诊断原样显示。页面从卡片与数量中排除内置 profile 组合包,即使 profile 将它们列为依赖或 Host 报告了异常。Host 清单仍保留完整数据;设置中「插件」分区的「插件列表」标签页负责查看它们的插件。
|
|
@@ -59,7 +65,7 @@ ctx.slots.inject('plugins.row.config', () => ctx.slots.register({
|
|
|
59
65
|
|
|
60
66
|
### 详情页扩展点
|
|
61
67
|
|
|
62
|
-
对某个不属于自己的组合包、行或官方插件有话要说的插件,通过本页声明的三个 list slot 向该对象的页面贡献内容:`plugins.detail.actions` 在页头放一个控件,位于页面自己的开关和卸载之前;`plugins.detail.badge`
|
|
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` 排列条目。
|
|
63
69
|
|
|
64
70
|
```tsx ignore-check
|
|
65
71
|
ctx.slots.inject('plugins.detail.section', () => ctx.slots.register({
|
|
@@ -83,6 +89,8 @@ ctx.slots.inject('plugins.detail.section', () => ctx.slots.register({
|
|
|
83
89
|
|
|
84
90
|
### 注册
|
|
85
91
|
|
|
92
|
+
Host 入口通过生成的 Remote 接口暴露 `pluginRegistryProbe.fastest()`,共享进行中的比较、让缓存按时过期,并在卸载时中止和等待未完成探测。卸载后调用会返回拒绝的 Promise。
|
|
93
|
+
|
|
86
94
|
浏览器插件通过 `ctx.slots.inject()` 注册 `plugins` 侧栏入口与它的 `main` 面板,使两者跟随 slot 延迟声明、本地化变化与销毁。页面为全局页面,不属于任何 Session。显示文本来自包元信息与页面字典。
|
|
87
95
|
|
|
88
96
|
### store
|
|
@@ -93,7 +101,7 @@ ctx.slots.inject('plugins.detail.section', () => ctx.slots.register({
|
|
|
93
101
|
|
|
94
102
|
自定义条目页以 Host 条目 id 作为注册 id;行页面使用 bundle 包名和行 id。当条目提供可编辑 Config 字段时,页面宿主传入 `form.state` 和 `form.mutate(operations, expectedRevision)`。自定义页面负责草稿和校验提示,并可复用 ui-primitives 的 `ConfigField`。整个 bundle 的页面可以包含多个条目,因此没有单一表单。
|
|
95
103
|
|
|
96
|
-
页面的 `main` 注册把 `plugins.item`、`plugins.bundle.config` 与 `plugins.row.config` 声明为子 slot,因此它们与页面同生,注册方的 `ctx.slots.inject` 会等到它们出现。`configLedgerSource` 把三份账本投影成一个可观察对象——按账本顺序排列、标签按当前语言解析的官方条目,以及组合包与行的键——在账本或语言变化前保持缓存;页面把它作为 `useConfigLedger` 绑在 store 旁边,自身从不点名任何可配置插件。打开的是哪一页是页面本地状态:卡片、某个组合包、某个官方插件,或组合包的某一行。注册与做出它的浏览器半侧同生共死。`dsh-client-modules` 只把一个包的浏览器半侧挂在说明符恰为包名的那一行 Loader 行上,所以组合包为自己或任一行注册的页面,都会在那一行被关闭时一起消失;需要在其他行关闭时仍保留页面的子插件,应作为独立的包发布。 名称以 `@deepseek-ai/dsh-experimental-`
|
|
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-` 开头的官方包显示实验性标记。
|
|
97
105
|
|
|
98
106
|
`plugins.bundle.config` 以 npm 包名为 key,提供 Bundle 详情配置。`plugins.bundle.activation` 在用户从列表显式启用后提供 Bundle 自有引导,并传入关闭引导和打开详情的回调。仅列出已启用的 Bundle 不会触发引导。
|
|
99
107
|
|
|
@@ -124,6 +132,8 @@ ctx.slots.inject('plugins.detail.section', () => ctx.slots.register({
|
|
|
124
132
|
|
|
125
133
|
## 已知限制与延期工作
|
|
126
134
|
|
|
135
|
+
- Ping 响应时间不代表包下载吞吐量。探测使用 Host fetch 的网络路径,pnpm 专用代理设置可能使用不同路径;用户始终可以手动选择安装源,安装仍遵循现有回退规则。
|
|
136
|
+
|
|
127
137
|
<a id="known-limitations-and-deferred-work"></a>
|
|
128
138
|
|
|
129
139
|
|
|
@@ -134,7 +144,7 @@ ctx.slots.inject('plugins.detail.section', () => ctx.slots.register({
|
|
|
134
144
|
- **行只显示阶段,不显示原因**——失败的行只显示为失败,没有 Host 的错误文本;Host 日志里有。
|
|
135
145
|
- **一次只能安装一个**——对话框一次运行一个 pnpm 命令;第二个 spec 要等前一个完成。
|
|
136
146
|
- **没有版本选择器**——spec 按 pnpm 接受的写法输入;页面不列出注册表版本,也不提供升级。
|
|
137
|
-
- **安装源选择只属于本浏览器**——它存在 `localStorage`
|
|
147
|
+
- **安装源选择只属于本浏览器**——它存在 `localStorage` 里,所以另一个浏览器会独立计算初始推荐;`dsh plugin` 命令和 agent 工具使用 Host 配置的注册表。
|
|
138
148
|
- **每次读注册表都要运行 pnpm**——打开对话框、检查、安装各问一次 pnpm 自身配置指向哪里;没有 pnpm 的机器读作未知,不提供备选。
|
|
139
149
|
|
|
140
150
|
<a id="dev-note"></a>
|
|
@@ -147,4 +157,4 @@ ctx.slots.inject('plugins.detail.section', () => ctx.slots.register({
|
|
|
147
157
|
|
|
148
158
|
</details>
|
|
149
159
|
|
|
150
|
-
**运行时不变式:**
|
|
160
|
+
**运行时不变式:** 不发布伴生检查。面板读取 Host 事实,安装源探测缓存只保存一次比较结果,没有独立维护的投影。
|
package/lib/client.js
CHANGED
|
@@ -74,7 +74,7 @@ window.__ModuleLoader__.load({
|
|
|
74
74
|
};
|
|
75
75
|
}
|
|
76
76
|
//#endregion
|
|
77
|
-
//#region ../../util/crypto/
|
|
77
|
+
//#region ../../util/crypto/lib/index.js
|
|
78
78
|
/**
|
|
79
79
|
* Random v4 UUID, minted from `crypto.getRandomValues`.
|
|
80
80
|
* @returns the UUID string.
|
|
@@ -86,10 +86,25 @@ window.__ModuleLoader__.load({
|
|
|
86
86
|
}).join("");
|
|
87
87
|
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
88
88
|
}
|
|
89
|
-
|
|
90
|
-
|
|
89
|
+
/** Public npmmirror URL shared by the fallback configuration and public-registry comparison. */
|
|
90
|
+
const NPMMIRROR_REGISTRY = "https://registry.npmmirror.com/";
|
|
91
91
|
/** An http(s) URL, as pnpm's `--registry` takes it. */
|
|
92
92
|
const REGISTRY_URL = /^https?:\/\/\S+$/;
|
|
93
|
+
/**
|
|
94
|
+
* Parse a registry URL into the form pnpm compares registries in: lower-case host, trailing slash.
|
|
95
|
+
* @param url - the registry as configured or requested.
|
|
96
|
+
* @returns the normalized URL.
|
|
97
|
+
* @throws {Error} for anything but an http(s) URL.
|
|
98
|
+
*/
|
|
99
|
+
function normalizeRegistry(url) {
|
|
100
|
+
let parsed;
|
|
101
|
+
try {
|
|
102
|
+
parsed = new URL(url);
|
|
103
|
+
} catch {}
|
|
104
|
+
if (parsed === void 0 || parsed.protocol !== "http:" && parsed.protocol !== "https:") throw new Error(`a registry must be an http(s) URL: ${url}`);
|
|
105
|
+
if (!parsed.pathname.endsWith("/")) parsed.pathname += "/";
|
|
106
|
+
return parsed.href;
|
|
107
|
+
}
|
|
93
108
|
//#endregion
|
|
94
109
|
//#region lib/types/client/presentation.js
|
|
95
110
|
/** Display labels and toast sentences for global plugin management. */
|
|
@@ -137,6 +152,7 @@ window.__ModuleLoader__.load({
|
|
|
137
152
|
"stop-profile": "reasonStopProfile",
|
|
138
153
|
"bundle-in-use": "reasonBundleInUse",
|
|
139
154
|
"stale-approval": "reasonStaleApproval",
|
|
155
|
+
"incompatible-version": "reasonIncompatibleVersionUnnamed",
|
|
140
156
|
"operation-error": "reasonOperationError"
|
|
141
157
|
};
|
|
142
158
|
/** The sentence a failed action opens with, by what was being done. */
|
|
@@ -148,13 +164,18 @@ window.__ModuleLoader__.load({
|
|
|
148
164
|
rowDisable: "failedRowDisable"
|
|
149
165
|
};
|
|
150
166
|
/**
|
|
151
|
-
* What a management error reads as: the code's sentence,
|
|
152
|
-
* operation error, the Host's diagnostic as it is.
|
|
153
|
-
* @param error - the Host's code
|
|
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.
|
|
154
170
|
* @param t - the manager's translate seat.
|
|
155
171
|
* @returns the sentence.
|
|
156
172
|
*/
|
|
157
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(" ");
|
|
158
179
|
if (error.code !== "operation-error") return t(CODE_KEYS[error.code]);
|
|
159
180
|
return error.diagnostic === void 0 || error.diagnostic === "" ? t("reasonOperationError") : error.diagnostic;
|
|
160
181
|
}
|
|
@@ -212,7 +233,8 @@ window.__ModuleLoader__.load({
|
|
|
212
233
|
case "failed": {
|
|
213
234
|
const reason = notice.code === void 0 ? notice.reason : managementText({
|
|
214
235
|
code: notice.code,
|
|
215
|
-
diagnostic: notice.reason
|
|
236
|
+
diagnostic: notice.reason,
|
|
237
|
+
...notice.incompatible === void 0 ? {} : { incompatible: notice.incompatible }
|
|
216
238
|
}, t);
|
|
217
239
|
return t(FAILED_KEYS[notice.action], { reason: reason === "" ? t("reasonOperationError") : reason });
|
|
218
240
|
}
|
|
@@ -250,14 +272,27 @@ window.__ModuleLoader__.load({
|
|
|
250
272
|
function isInstallPending(phase) {
|
|
251
273
|
return phase === "starting" || phase === "running" || phase === "cancelling" || phase === "applying" || phase === "unconfirmed";
|
|
252
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
|
+
}
|
|
253
286
|
/** A refused answer or a change the Host could not apply, carrying what it said and, for a refusal, its code. */
|
|
254
287
|
var RemoteAnswerError = class extends Error {
|
|
255
288
|
reason;
|
|
256
289
|
code;
|
|
257
|
-
|
|
290
|
+
incompatible;
|
|
291
|
+
constructor(reason, code, incompatible) {
|
|
258
292
|
super(reason);
|
|
259
293
|
this.reason = reason;
|
|
260
294
|
this.code = code;
|
|
295
|
+
this.incompatible = incompatible;
|
|
261
296
|
this.name = "RemoteAnswerError";
|
|
262
297
|
}
|
|
263
298
|
};
|
|
@@ -266,6 +301,7 @@ window.__ModuleLoader__.load({
|
|
|
266
301
|
return {
|
|
267
302
|
reason: error?.diagnostic ?? "",
|
|
268
303
|
...error === void 0 ? {} : { code: error.code },
|
|
304
|
+
...error?.incompatible === void 0 ? {} : { incompatible: error.incompatible },
|
|
269
305
|
...kind === void 0 ? {} : { kind },
|
|
270
306
|
...failedAt === void 0 ? {} : { failedAt },
|
|
271
307
|
...pendingBuilds === void 0 || pendingBuilds.length === 0 ? {} : { pendingBuilds }
|
|
@@ -274,10 +310,12 @@ window.__ModuleLoader__.load({
|
|
|
274
310
|
/** The notice a thrown failure becomes: a refusal keeps its code, anything else its words. */
|
|
275
311
|
function failedNotice(error, subject, seq) {
|
|
276
312
|
const code = error instanceof RemoteAnswerError ? error.code : void 0;
|
|
313
|
+
const incompatible = error instanceof RemoteAnswerError ? error.incompatible : void 0;
|
|
277
314
|
return {
|
|
278
315
|
kind: "failed",
|
|
279
316
|
reason: reasonOf(error),
|
|
280
317
|
...code === void 0 ? {} : { code },
|
|
318
|
+
...incompatible === void 0 ? {} : { incompatible },
|
|
281
319
|
...subject,
|
|
282
320
|
seq
|
|
283
321
|
};
|
|
@@ -359,13 +397,14 @@ window.__ModuleLoader__.load({
|
|
|
359
397
|
};
|
|
360
398
|
/** The dialog back at its spec: the run and its outcome forgotten, the spec and the registries kept. */
|
|
361
399
|
function specAgain(install) {
|
|
362
|
-
const { open, spec, registries, registry } = install;
|
|
400
|
+
const { open, spec, registries, registry, mirrorRecovery } = install;
|
|
363
401
|
return {
|
|
364
402
|
...IDLE_INSTALL,
|
|
365
403
|
open,
|
|
366
404
|
spec,
|
|
367
405
|
registries,
|
|
368
|
-
registry
|
|
406
|
+
registry,
|
|
407
|
+
...mirrorRecovery === void 0 ? {} : { mirrorRecovery }
|
|
369
408
|
};
|
|
370
409
|
}
|
|
371
410
|
/**
|
|
@@ -382,6 +421,18 @@ window.__ModuleLoader__.load({
|
|
|
382
421
|
url: remembered.registry
|
|
383
422
|
} : remembered;
|
|
384
423
|
}
|
|
424
|
+
/** Only the shipped public mirror can replace an unconfigured official npm default. */
|
|
425
|
+
function eligibleMirror(registries) {
|
|
426
|
+
if (registries.registry !== null || registries.resolved === null) return void 0;
|
|
427
|
+
let resolved;
|
|
428
|
+
try {
|
|
429
|
+
resolved = normalizeRegistry(registries.resolved);
|
|
430
|
+
} catch (_error) {
|
|
431
|
+
return;
|
|
432
|
+
}
|
|
433
|
+
if (resolved !== "https://registry.npmjs.org/") return void 0;
|
|
434
|
+
return registries.fallbackRegistries.find((registry) => registry === NPMMIRROR_REGISTRY);
|
|
435
|
+
}
|
|
385
436
|
/** Reads and mutates the profile's plugins through the `pluginManager` Remote. */
|
|
386
437
|
var PluginManagerController = class {
|
|
387
438
|
ctx;
|
|
@@ -395,6 +446,7 @@ window.__ModuleLoader__.load({
|
|
|
395
446
|
inspectAbort;
|
|
396
447
|
request;
|
|
397
448
|
noticeSeq = 0;
|
|
449
|
+
registryRead;
|
|
398
450
|
/** The registry last used from this browser, kept across dialogs and page loads; null until one was used. */
|
|
399
451
|
registryMemory = (0, _deepseek_ai_dsh_client_store.createSnapshotStore)(null, { persist: { name: "dsh.plugin-manager.install-registry" } });
|
|
400
452
|
/**
|
|
@@ -422,6 +474,7 @@ window.__ModuleLoader__.load({
|
|
|
422
474
|
/** Stop publishing and drop every late settlement. */
|
|
423
475
|
dispose() {
|
|
424
476
|
this.disposed = true;
|
|
477
|
+
this.registryRead = void 0;
|
|
425
478
|
this.request = void 0;
|
|
426
479
|
this.generation += 1;
|
|
427
480
|
}
|
|
@@ -453,7 +506,11 @@ window.__ModuleLoader__.load({
|
|
|
453
506
|
open: true,
|
|
454
507
|
registry: this.registryMemory.getSnapshot() ?? OFFICIAL_REGISTRY
|
|
455
508
|
} });
|
|
456
|
-
this.
|
|
509
|
+
const read = { choice: this.getSnapshot().install.registry };
|
|
510
|
+
this.registryRead = read;
|
|
511
|
+
read.done = this.readRegistries(read).finally(() => {
|
|
512
|
+
delete read.done;
|
|
513
|
+
});
|
|
457
514
|
} else this.patchInstall({ open: true });
|
|
458
515
|
this.reconcileInstall();
|
|
459
516
|
},
|
|
@@ -464,6 +521,7 @@ window.__ModuleLoader__.load({
|
|
|
464
521
|
return;
|
|
465
522
|
}
|
|
466
523
|
this.abortInspect();
|
|
524
|
+
this.registryRead = void 0;
|
|
467
525
|
this.patch({ install: IDLE_INSTALL });
|
|
468
526
|
},
|
|
469
527
|
editInstallSpec: (text) => {
|
|
@@ -496,6 +554,22 @@ window.__ModuleLoader__.load({
|
|
|
496
554
|
registryOpen: true
|
|
497
555
|
} });
|
|
498
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
|
+
},
|
|
499
573
|
approveBuildsAndRetry: () => {
|
|
500
574
|
this.approveBuildsAndRetry();
|
|
501
575
|
},
|
|
@@ -578,15 +652,31 @@ window.__ModuleLoader__.load({
|
|
|
578
652
|
} }
|
|
579
653
|
});
|
|
580
654
|
}
|
|
655
|
+
currentRegistryRead(read) {
|
|
656
|
+
return !this.disposed && this.registryRead === read;
|
|
657
|
+
}
|
|
581
658
|
/** Read the registries the Host offers, for the dialog just opened; a refused read leaves pnpm's own and a typed one. */
|
|
582
|
-
async readRegistries() {
|
|
659
|
+
async readRegistries(read) {
|
|
583
660
|
const answer = await this.ctx.remote.pluginManager.registries();
|
|
584
661
|
const install = this.getSnapshot().install;
|
|
585
|
-
if (this.
|
|
662
|
+
if (!this.currentRegistryRead(read) || !install.open || !answer.ok) return;
|
|
663
|
+
const remembered = this.registryMemory.getSnapshot();
|
|
664
|
+
const untouched = install.registry === read.choice && (install.phase === "idle" || install.phase === "checking");
|
|
665
|
+
if (untouched) read.choice = reconciled(remembered, answer.value);
|
|
586
666
|
this.patchInstall({
|
|
587
667
|
registries: answer.value,
|
|
588
|
-
registry:
|
|
668
|
+
...untouched ? { registry: read.choice } : {}
|
|
589
669
|
});
|
|
670
|
+
const mirror = eligibleMirror(answer.value);
|
|
671
|
+
if (!untouched || remembered !== null || mirror === void 0) return;
|
|
672
|
+
const fastest = await this.ctx.remote.pluginRegistryProbe.fastest();
|
|
673
|
+
const current = this.getSnapshot().install;
|
|
674
|
+
if (!this.currentRegistryRead(read) || !current.open || current.phase !== "idle" && current.phase !== "checking" || current.registry !== read.choice || !fastest.ok || fastest.value !== mirror) return;
|
|
675
|
+
read.choice = {
|
|
676
|
+
kind: "offered",
|
|
677
|
+
registry: mirror
|
|
678
|
+
};
|
|
679
|
+
this.patchInstall({ registry: read.choice });
|
|
590
680
|
}
|
|
591
681
|
/**
|
|
592
682
|
* Fold a chunk belonging to this installation into its pnpm command.
|
|
@@ -713,11 +803,6 @@ window.__ModuleLoader__.load({
|
|
|
713
803
|
});
|
|
714
804
|
return;
|
|
715
805
|
}
|
|
716
|
-
const registry = typed ?? choice.registry;
|
|
717
|
-
this.registryMemory.set(typed === void 0 ? choice : {
|
|
718
|
-
kind: "custom",
|
|
719
|
-
url: typed
|
|
720
|
-
});
|
|
721
806
|
this.abortInspect();
|
|
722
807
|
const controller = new AbortController();
|
|
723
808
|
this.inspectAbort = controller;
|
|
@@ -734,6 +819,18 @@ window.__ModuleLoader__.load({
|
|
|
734
819
|
failure: null,
|
|
735
820
|
approvedBuilds: []
|
|
736
821
|
});
|
|
822
|
+
const read = this.registryRead;
|
|
823
|
+
if (read !== void 0 && choice === read.choice && read.done !== void 0) {
|
|
824
|
+
await read.done;
|
|
825
|
+
if (this.gone(controller.signal) || this.registryRead !== read) return;
|
|
826
|
+
}
|
|
827
|
+
const currentChoice = this.getSnapshot().install.registry;
|
|
828
|
+
const selected = currentChoice.kind === "custom" ? {
|
|
829
|
+
...currentChoice,
|
|
830
|
+
url: currentChoice.url.trim()
|
|
831
|
+
} : currentChoice;
|
|
832
|
+
const registry = selected.kind === "custom" ? selected.url : selected.registry;
|
|
833
|
+
this.registryMemory.set(selected);
|
|
737
834
|
const inspected = await this.ctx.remote.pluginManager.inspect(spec, { registry }, controller.signal);
|
|
738
835
|
if (this.gone(controller.signal)) return;
|
|
739
836
|
this.inspectAbort = void 0;
|
|
@@ -1017,7 +1114,7 @@ window.__ModuleLoader__.load({
|
|
|
1017
1114
|
if (!answer.ok) throw new RemoteAnswerError(answer.error.message);
|
|
1018
1115
|
const result = answer.value;
|
|
1019
1116
|
switch (result.application) {
|
|
1020
|
-
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);
|
|
1021
1118
|
case "cancelled":
|
|
1022
1119
|
this.patch({ notice: {
|
|
1023
1120
|
kind: "cancelled",
|
|
@@ -1939,6 +2036,10 @@ window.__ModuleLoader__.load({
|
|
|
1939
2036
|
*/
|
|
1940
2037
|
function failureText(failure, t, install) {
|
|
1941
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);
|
|
1942
2043
|
if (failure.kind === "build-blocked" && !failure.pendingBuilds?.length) return t("installFailureBuildBlockedManual");
|
|
1943
2044
|
const host = install?.subject?.host;
|
|
1944
2045
|
if (failure.failedAt === "spec-host" && host !== void 0) return t("installFailureNetworkHost", { host });
|
|
@@ -1980,7 +2081,7 @@ window.__ModuleLoader__.load({
|
|
|
1980
2081
|
* and failed screens over the same subject card. A failed run that left
|
|
1981
2082
|
* install scripts undecided shows them for approval in place of plain retry.
|
|
1982
2083
|
*/
|
|
1983
|
-
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 }) {
|
|
1984
2085
|
const errorId = (0, react.useId)();
|
|
1985
2086
|
const guideId = (0, react.useId)();
|
|
1986
2087
|
const approvalId = (0, react.useId)();
|
|
@@ -2012,6 +2113,23 @@ window.__ModuleLoader__.load({
|
|
|
2012
2113
|
document.removeEventListener("keydown", onKeyDown, true);
|
|
2013
2114
|
};
|
|
2014
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
|
+
});
|
|
2015
2133
|
if (phase === "idle" || phase === "checking") {
|
|
2016
2134
|
const checking = phase === "checking";
|
|
2017
2135
|
const empty = install.spec.trim() === "";
|
|
@@ -2026,7 +2144,7 @@ window.__ModuleLoader__.load({
|
|
|
2026
2144
|
onClose,
|
|
2027
2145
|
title: t("installTitle"),
|
|
2028
2146
|
closeLabel: t("close"),
|
|
2029
|
-
description: t("installDescription"),
|
|
2147
|
+
...install.mirrorRecovery ? {} : { description: t("installDescription") },
|
|
2030
2148
|
className: PluginManagerPage_module_css_default.installDialog,
|
|
2031
2149
|
contentClassName: PluginManagerPage_module_css_default.installContent,
|
|
2032
2150
|
footer: (0, react_jsx_runtime.jsxs)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
@@ -2044,10 +2162,11 @@ window.__ModuleLoader__.load({
|
|
|
2044
2162
|
className: PluginManagerPage_module_css_default.installField,
|
|
2045
2163
|
children: (0, react_jsx_runtime.jsx)("input", {
|
|
2046
2164
|
type: "text",
|
|
2165
|
+
autoFocus: install.mirrorRecovery === true,
|
|
2047
2166
|
value: install.spec,
|
|
2048
2167
|
placeholder: t("installSpecPlaceholder"),
|
|
2049
2168
|
disabled: checking,
|
|
2050
|
-
"aria-label": t("installSpecLabel"),
|
|
2169
|
+
"aria-label": t(install.mirrorRecovery ? "installPackageLabel" : "installSpecLabel"),
|
|
2051
2170
|
"aria-invalid": install.inputError !== null,
|
|
2052
2171
|
"aria-describedby": install.inputError === null ? void 0 : errorId,
|
|
2053
2172
|
onChange: (event) => {
|
|
@@ -2767,7 +2886,8 @@ window.__ModuleLoader__.load({
|
|
|
2767
2886
|
onApproveBuilds: props.approveBuildsAndRetry,
|
|
2768
2887
|
onToggleRegistry: props.toggleRegistryOptions,
|
|
2769
2888
|
onChooseRegistry: props.chooseRegistry,
|
|
2770
|
-
onChangeRegistry: props.changeRegistry
|
|
2889
|
+
onChangeRegistry: props.changeRegistry,
|
|
2890
|
+
onUseGithubMirror: props.useGithubMirror
|
|
2771
2891
|
}),
|
|
2772
2892
|
state.confirm === null ? null : (0, react_jsx_runtime.jsx)(ConfirmDialog, {
|
|
2773
2893
|
name: packageText(state.packages.find((pkg) => pkg.name === state.confirm?.packageName) ?? { name: state.confirm.packageName }, resolveText).title,
|
|
@@ -2808,7 +2928,7 @@ window.__ModuleLoader__.load({
|
|
|
2808
2928
|
bundlesTitle: "已安装",
|
|
2809
2929
|
officialTitle: "官方",
|
|
2810
2930
|
statusProblem: "异常",
|
|
2811
|
-
statusBeta: "
|
|
2931
|
+
statusBeta: "实验性",
|
|
2812
2932
|
reasonLabel: "原因",
|
|
2813
2933
|
metadataError: "包元信息错误:{error}",
|
|
2814
2934
|
versionTag: "v{version}",
|
|
@@ -2878,6 +2998,11 @@ window.__ModuleLoader__.load({
|
|
|
2878
2998
|
installingTitle: "插件安装中…",
|
|
2879
2999
|
installedTitle: "已安装",
|
|
2880
3000
|
installFailedTitle: "插件安装失败",
|
|
3001
|
+
installGithubFailedTitle: "无法访问 GitHub",
|
|
3002
|
+
installGithubTimeoutTitle: "连接 GitHub 超时",
|
|
3003
|
+
installGithubFailedDescription: "请尝试其他安装来源。",
|
|
3004
|
+
installUseGithubMirror: "改用国内镜像",
|
|
3005
|
+
installPackageLabel: "插件包名",
|
|
2881
3006
|
installEdit: "编辑",
|
|
2882
3007
|
installEditAria: "返回编辑",
|
|
2883
3008
|
installCancelAndEdit: "取消安装并返回编辑",
|
|
@@ -2969,6 +3094,8 @@ window.__ModuleLoader__.load({
|
|
|
2969
3094
|
reasonStopProfile: "这个 profile 没有启用 HMR,正在使用的包要停止后用 dsh plugin 卸载",
|
|
2970
3095
|
reasonBundleInUse: "其他配置仍在使用这个组合包的组件,请先停用它们",
|
|
2971
3096
|
reasonStaleApproval: "待允许的安装脚本列表已变化,请重新安装以刷新",
|
|
3097
|
+
reasonIncompatibleVersion: "{plugin} 与 DSH {runtime} 不兼容(要求 {peers}),运行它可能导致崩溃或数据丢失。请安装与当前 DSH 兼容的插件版本。",
|
|
3098
|
+
reasonIncompatibleVersionUnnamed: "这个插件与当前 DSH 版本不兼容,运行它可能导致崩溃或数据丢失",
|
|
2972
3099
|
reasonOperationError: "Host 报告了一个错误"
|
|
2973
3100
|
};
|
|
2974
3101
|
/** English dictionary checked against the Chinese key set. */
|
|
@@ -2988,7 +3115,7 @@ window.__ModuleLoader__.load({
|
|
|
2988
3115
|
bundlesTitle: "Installed",
|
|
2989
3116
|
officialTitle: "Official",
|
|
2990
3117
|
statusProblem: "Problem",
|
|
2991
|
-
statusBeta: "
|
|
3118
|
+
statusBeta: "Experimental",
|
|
2992
3119
|
reasonLabel: "Reason",
|
|
2993
3120
|
metadataError: "Package metadata error: {error}",
|
|
2994
3121
|
versionTag: "v{version}",
|
|
@@ -3058,6 +3185,11 @@ window.__ModuleLoader__.load({
|
|
|
3058
3185
|
installingTitle: "Installing the plugin…",
|
|
3059
3186
|
installedTitle: "Installed",
|
|
3060
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",
|
|
3061
3193
|
installEdit: "Edit",
|
|
3062
3194
|
installEditAria: "Back to editing",
|
|
3063
3195
|
installCancelAndEdit: "Cancel installation and return to editing",
|
|
@@ -3149,6 +3281,8 @@ window.__ModuleLoader__.load({
|
|
|
3149
3281
|
reasonStopProfile: "This profile runs without HMR; stop it and uninstall the package with dsh plugin.",
|
|
3150
3282
|
reasonBundleInUse: "Other configuration still uses this bundle's components; switch them off first.",
|
|
3151
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.",
|
|
3152
3286
|
reasonOperationError: "The Host reported an error."
|
|
3153
3287
|
};
|
|
3154
3288
|
//#endregion
|
|
@@ -3172,6 +3306,7 @@ window.__ModuleLoader__.load({
|
|
|
3172
3306
|
"remote",
|
|
3173
3307
|
"remote.pluginManager",
|
|
3174
3308
|
"remote.pluginInventory",
|
|
3309
|
+
"remote.pluginRegistryProbe",
|
|
3175
3310
|
"configForms"
|
|
3176
3311
|
];
|
|
3177
3312
|
/**
|
package/lib/index.js
CHANGED
|
@@ -1,6 +1,142 @@
|
|
|
1
|
+
import z from "@deepseek-ai/schemastery";
|
|
2
|
+
import { MAX_TIMER_DELAY_MS } from "@deepseek-ai/dsh-timeout";
|
|
3
|
+
import { Remote, TypertRemoteService } from "@deepseek-ai/dsh-typert-protocol";
|
|
1
4
|
//#region lib/types/index.js
|
|
2
|
-
/** Host
|
|
3
|
-
|
|
4
|
-
|
|
5
|
+
/** Host registry-response probing for the plugin installation dialog. */
|
|
6
|
+
var __runInitializers = function(thisArg, initializers, value) {
|
|
7
|
+
var useValue = arguments.length > 2;
|
|
8
|
+
for (var i = 0; i < initializers.length; i++) value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
|
|
9
|
+
return useValue ? value : void 0;
|
|
10
|
+
};
|
|
11
|
+
var __esDecorate = function(ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
|
|
12
|
+
function accept(f) {
|
|
13
|
+
if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected");
|
|
14
|
+
return f;
|
|
15
|
+
}
|
|
16
|
+
var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
|
|
17
|
+
var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
|
|
18
|
+
var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
|
|
19
|
+
var _, done = false;
|
|
20
|
+
for (var i = decorators.length - 1; i >= 0; i--) {
|
|
21
|
+
var context = {};
|
|
22
|
+
for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
|
|
23
|
+
for (var p in contextIn.access) context.access[p] = contextIn.access[p];
|
|
24
|
+
context.addInitializer = function(f) {
|
|
25
|
+
if (done) throw new TypeError("Cannot add initializers after decoration has completed");
|
|
26
|
+
extraInitializers.push(accept(f || null));
|
|
27
|
+
};
|
|
28
|
+
var result = (0, decorators[i])(kind === "accessor" ? {
|
|
29
|
+
get: descriptor.get,
|
|
30
|
+
set: descriptor.set
|
|
31
|
+
} : descriptor[key], context);
|
|
32
|
+
if (kind === "accessor") {
|
|
33
|
+
if (result === void 0) continue;
|
|
34
|
+
if (result === null || typeof result !== "object") throw new TypeError("Object expected");
|
|
35
|
+
if (_ = accept(result.get)) descriptor.get = _;
|
|
36
|
+
if (_ = accept(result.set)) descriptor.set = _;
|
|
37
|
+
if (_ = accept(result.init)) initializers.unshift(_);
|
|
38
|
+
} else if (_ = accept(result)) if (kind === "field") initializers.unshift(_);
|
|
39
|
+
else descriptor[key] = _;
|
|
40
|
+
}
|
|
41
|
+
if (target) Object.defineProperty(target, contextIn.name, descriptor);
|
|
42
|
+
done = true;
|
|
43
|
+
};
|
|
44
|
+
let PluginRegistryProbe = (() => {
|
|
45
|
+
let _classSuper = TypertRemoteService;
|
|
46
|
+
let _instanceExtraInitializers = [];
|
|
47
|
+
let _fastest_decorators;
|
|
48
|
+
return class PluginRegistryProbe extends _classSuper {
|
|
49
|
+
static {
|
|
50
|
+
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
|
|
51
|
+
_fastest_decorators = [Remote];
|
|
52
|
+
__esDecorate(this, null, _fastest_decorators, {
|
|
53
|
+
kind: "method",
|
|
54
|
+
name: "fastest",
|
|
55
|
+
static: false,
|
|
56
|
+
private: false,
|
|
57
|
+
access: {
|
|
58
|
+
has: (obj) => "fastest" in obj,
|
|
59
|
+
get: (obj) => obj.fastest
|
|
60
|
+
},
|
|
61
|
+
metadata: _metadata
|
|
62
|
+
}, null, _instanceExtraInitializers);
|
|
63
|
+
if (_metadata) Object.defineProperty(this, Symbol.metadata, {
|
|
64
|
+
enumerable: true,
|
|
65
|
+
configurable: true,
|
|
66
|
+
writable: true,
|
|
67
|
+
value: _metadata
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
config = __runInitializers(this, _instanceExtraInitializers);
|
|
71
|
+
static Config = z.object({
|
|
72
|
+
registryProbeEnabled: z.boolean().default(true),
|
|
73
|
+
registryProbeTimeoutMs: z.natural().min(1).max(MAX_TIMER_DELAY_MS).default(1500),
|
|
74
|
+
registryProbeCacheTtlMs: z.natural().default(3e5)
|
|
75
|
+
});
|
|
76
|
+
lifetime = new AbortController();
|
|
77
|
+
pending;
|
|
78
|
+
cached;
|
|
79
|
+
constructor(ctx, config) {
|
|
80
|
+
super(ctx, "pluginRegistryProbe");
|
|
81
|
+
this.config = config;
|
|
82
|
+
ctx.effect(() => async () => {
|
|
83
|
+
this.lifetime.abort();
|
|
84
|
+
await this.pending;
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Race npm and npmmirror HTTPS ping responses through the Host's fetch proxy.
|
|
89
|
+
* Concurrent readers share a probe; a winner cancels and awaits the other request.
|
|
90
|
+
* @returns the first registry with a successful response, or null when disabled or neither responds successfully; results are cached.
|
|
91
|
+
* @throws rejects when the service has been unloaded.
|
|
92
|
+
*/
|
|
93
|
+
async fastest() {
|
|
94
|
+
this.lifetime.signal.throwIfAborted();
|
|
95
|
+
if (!this.config.registryProbeEnabled) return null;
|
|
96
|
+
if (this.cached !== void 0 && this.cached.expiresAt > Date.now()) return this.cached.registry;
|
|
97
|
+
this.pending ??= this.probe().finally(() => {
|
|
98
|
+
this.pending = void 0;
|
|
99
|
+
});
|
|
100
|
+
return this.pending;
|
|
101
|
+
}
|
|
102
|
+
async probe() {
|
|
103
|
+
const finished = new AbortController();
|
|
104
|
+
const signal = AbortSignal.any([
|
|
105
|
+
this.lifetime.signal,
|
|
106
|
+
finished.signal,
|
|
107
|
+
AbortSignal.timeout(this.config.registryProbeTimeoutMs)
|
|
108
|
+
]);
|
|
109
|
+
const requests = ["https://registry.npmjs.org/-/ping", "https://registry.npmmirror.com/-/ping"].map(async (endpoint) => ({
|
|
110
|
+
registry: new URL("/", endpoint).href,
|
|
111
|
+
response: await fetch(endpoint, {
|
|
112
|
+
signal,
|
|
113
|
+
redirect: "error"
|
|
114
|
+
})
|
|
115
|
+
}));
|
|
116
|
+
const successful = requests.map(async (request) => {
|
|
117
|
+
const { registry, response } = await request;
|
|
118
|
+
if (!response.ok) throw new Error(`Registry ping returned HTTP ${response.status}`);
|
|
119
|
+
return registry;
|
|
120
|
+
});
|
|
121
|
+
let registry;
|
|
122
|
+
try {
|
|
123
|
+
registry = await Promise.any(successful);
|
|
124
|
+
} catch (_unavailableRegistries) {
|
|
125
|
+
registry = null;
|
|
126
|
+
} finally {
|
|
127
|
+
finished.abort();
|
|
128
|
+
const responses = await Promise.allSettled(requests);
|
|
129
|
+
await Promise.allSettled(responses.map(async (result) => {
|
|
130
|
+
if (result.status === "fulfilled") await result.value.response.body?.cancel();
|
|
131
|
+
}));
|
|
132
|
+
}
|
|
133
|
+
if (!this.lifetime.signal.aborted) this.cached = {
|
|
134
|
+
registry,
|
|
135
|
+
expiresAt: Date.now() + this.config.registryProbeCacheTtlMs
|
|
136
|
+
};
|
|
137
|
+
return registry;
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
})();
|
|
5
141
|
//#endregion
|
|
6
|
-
export {
|
|
142
|
+
export { PluginRegistryProbe as default };
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/* Generated by @deepseek-ai/dsh-typert-generator from FaceModel — do not edit. */
|
|
2
|
+
import { z } from 'zod'
|
|
3
|
+
|
|
4
|
+
let _deepseek_ai_dsh_client_ui_plugin_manager_pluginRegistryProbe_fastest_result$schema$value
|
|
5
|
+
const _deepseek_ai_dsh_client_ui_plugin_manager_pluginRegistryProbe_fastest_result$schema = () => (_deepseek_ai_dsh_client_ui_plugin_manager_pluginRegistryProbe_fastest_result$schema$value ??= z.union([z.literal(null), z.string()]))
|
|
6
|
+
|
|
7
|
+
export const TYPERT = {
|
|
8
|
+
package: '@deepseek-ai/dsh-client-ui-plugin-manager',
|
|
9
|
+
face: 'host',
|
|
10
|
+
schemas: [
|
|
11
|
+
],
|
|
12
|
+
invocations: [
|
|
13
|
+
{
|
|
14
|
+
id: '@deepseek-ai/dsh-client-ui-plugin-manager#pluginRegistryProbe/fastest',
|
|
15
|
+
service: 'pluginRegistryProbe',
|
|
16
|
+
namespace: 'pluginRegistryProbe',
|
|
17
|
+
method: 'fastest',
|
|
18
|
+
invocation: { kind: 'direct' },
|
|
19
|
+
parameters: [
|
|
20
|
+
],
|
|
21
|
+
result: {
|
|
22
|
+
mode: 'strict',
|
|
23
|
+
typeSymbol: '@deepseek-ai/dsh-client-ui-plugin-manager#pluginRegistryProbe/fastest:result',
|
|
24
|
+
create: _deepseek_ai_dsh_client_ui_plugin_manager_pluginRegistryProbe_fastest_result$schema,
|
|
25
|
+
},
|
|
26
|
+
sourceLocation: {"file":"packages/client/ui-plugin-manager/src/index.ts","line":51,"column":9},
|
|
27
|
+
},
|
|
28
|
+
],
|
|
29
|
+
model: {
|
|
30
|
+
"services": [
|
|
31
|
+
{
|
|
32
|
+
"description": "Compares public registry responses on the Host; the Client owns the initial selection.",
|
|
33
|
+
"summary": "Compares public registry responses on the Host; the Client owns the initial selection.",
|
|
34
|
+
"tags": [],
|
|
35
|
+
"jsDoc": "/** Compares public registry responses on the Host; the Client owns the initial selection. */",
|
|
36
|
+
"key": "pluginRegistryProbe",
|
|
37
|
+
"exportName": "default",
|
|
38
|
+
"members": [
|
|
39
|
+
{
|
|
40
|
+
"kind": "method",
|
|
41
|
+
"name": "fastest",
|
|
42
|
+
"signature": "@Remote async fastest(): Promise<string | null>",
|
|
43
|
+
"summary": "Race npm and npmmirror HTTPS ping responses through the Host's fetch proxy.",
|
|
44
|
+
"jsDoc": "/**\n * Race npm and npmmirror HTTPS ping responses through the Host's fetch proxy.\n * Concurrent readers share a probe; a winner cancels and awaits the other request.\n * @returns the first registry with a successful response, or null when disabled or neither responds successfully; results are cached.\n * @throws rejects when the service has been unloaded.\n */"
|
|
45
|
+
}
|
|
46
|
+
],
|
|
47
|
+
"types": []
|
|
48
|
+
}
|
|
49
|
+
],
|
|
50
|
+
"events": [],
|
|
51
|
+
"objects": []
|
|
52
|
+
},
|
|
53
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/* Generated by @deepseek-ai/dsh-typert-generator from the Host FaceModel — do not edit. */
|
|
2
|
+
import type {
|
|
3
|
+
RemoteResult,
|
|
4
|
+
TypertRemoteContribution,
|
|
5
|
+
} from '@deepseek-ai/dsh-typert-protocol'
|
|
6
|
+
|
|
7
|
+
declare module '@deepseek-ai/dsh-typert-protocol' {
|
|
8
|
+
interface TypertRemoteNamespace$706c7567696e526567697374727950726f6265 {
|
|
9
|
+
fastest: () => Promise<RemoteResult<string | null>>
|
|
10
|
+
}
|
|
11
|
+
interface TypertRemoteMap {
|
|
12
|
+
'pluginRegistryProbe/fastest': () => Promise<RemoteResult<string | null>>
|
|
13
|
+
}
|
|
14
|
+
interface TypertRemoteNamespaceMap {
|
|
15
|
+
'pluginRegistryProbe': TypertRemoteNamespace$706c7567696e526567697374727950726f6265
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export declare const TYPERT_REMOTE: TypertRemoteContribution
|
|
20
|
+
export default TYPERT_REMOTE
|
|
21
|
+
//# sourceMappingURL=typert.remote-client.d.ts.map
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/* Generated by @deepseek-ai/dsh-typert-generator from the Host FaceModel — do not edit. */
|
|
2
|
+
import { z } from 'zod'
|
|
3
|
+
|
|
4
|
+
let _deepseek_ai_dsh_client_ui_plugin_manager_pluginRegistryProbe_fastest_result$schema$value
|
|
5
|
+
const _deepseek_ai_dsh_client_ui_plugin_manager_pluginRegistryProbe_fastest_result$schema = () => (_deepseek_ai_dsh_client_ui_plugin_manager_pluginRegistryProbe_fastest_result$schema$value ??= z.union([z.literal(null), z.string()]))
|
|
6
|
+
|
|
7
|
+
export const TYPERT_REMOTE = {
|
|
8
|
+
package: '@deepseek-ai/dsh-client-ui-plugin-manager',
|
|
9
|
+
descriptors: [
|
|
10
|
+
{
|
|
11
|
+
id: '@deepseek-ai/dsh-client-ui-plugin-manager#pluginRegistryProbe/fastest',
|
|
12
|
+
service: 'pluginRegistryProbe',
|
|
13
|
+
namespace: 'pluginRegistryProbe',
|
|
14
|
+
method: 'fastest',
|
|
15
|
+
invocation: { kind: 'direct' },
|
|
16
|
+
parameters: [
|
|
17
|
+
],
|
|
18
|
+
result: {
|
|
19
|
+
mode: 'strict',
|
|
20
|
+
typeSymbol: '@deepseek-ai/dsh-client-ui-plugin-manager#pluginRegistryProbe/fastest:result',
|
|
21
|
+
create: _deepseek_ai_dsh_client_ui_plugin_manager_pluginRegistryProbe_fastest_result$schema,
|
|
22
|
+
},
|
|
23
|
+
sourceLocation: {"file":"packages/client/ui-plugin-manager/src/index.ts","line":51,"column":9},
|
|
24
|
+
},
|
|
25
|
+
],
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export default TYPERT_REMOTE
|
|
@@ -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. */
|
|
@@ -285,6 +299,7 @@ export declare class PluginManagerController {
|
|
|
285
299
|
private inspectAbort;
|
|
286
300
|
private request;
|
|
287
301
|
private noticeSeq;
|
|
302
|
+
private registryRead;
|
|
288
303
|
/** The registry last used from this browser, kept across dialogs and page loads; null until one was used. */
|
|
289
304
|
private readonly registryMemory;
|
|
290
305
|
/**
|
|
@@ -310,6 +325,7 @@ export declare class PluginManagerController {
|
|
|
310
325
|
* @param progress - a request id and phase received from the Host.
|
|
311
326
|
*/
|
|
312
327
|
installProgress(progress: PluginInstallProgress): void;
|
|
328
|
+
private currentRegistryRead;
|
|
313
329
|
/** Read the registries the Host offers, for the dialog just opened; a refused read leaves pnpm's own and a typed one. */
|
|
314
330
|
private readRegistries;
|
|
315
331
|
/**
|
|
@@ -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,
|
|
21
|
-
* operation error, the Host's diagnostic as it is.
|
|
22
|
-
* @param error - the Host's code
|
|
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/lib/types/index.d.ts
CHANGED
|
@@ -1,4 +1,36 @@
|
|
|
1
|
-
/** Host
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
/** Host registry-response probing for the plugin installation dialog. */
|
|
2
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
3
|
+
import z from '@deepseek-ai/schemastery';
|
|
4
|
+
import { TypertRemoteService } from '@deepseek-ai/dsh-typert-protocol';
|
|
5
|
+
declare module '@deepseek-ai/cordis' {
|
|
6
|
+
interface Context {
|
|
7
|
+
pluginRegistryProbe: PluginRegistryProbe;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
/** Registry-probe deadline and process-local cache policy. */
|
|
11
|
+
export interface Config {
|
|
12
|
+
/** Whether the dialog can compare the public npm registries. */
|
|
13
|
+
registryProbeEnabled: boolean;
|
|
14
|
+
/** Deadline for the parallel HTTPS probes, including response cleanup. */
|
|
15
|
+
registryProbeTimeoutMs: number;
|
|
16
|
+
/** Lifetime of a winning registry or unavailable result. */
|
|
17
|
+
registryProbeCacheTtlMs: number;
|
|
18
|
+
}
|
|
19
|
+
/** Compares public registry responses on the Host; the Client owns the initial selection. */
|
|
20
|
+
export default class PluginRegistryProbe extends TypertRemoteService {
|
|
21
|
+
private readonly config;
|
|
22
|
+
static Config: z<Partial<Config>, Config>;
|
|
23
|
+
private readonly lifetime;
|
|
24
|
+
private pending;
|
|
25
|
+
private cached;
|
|
26
|
+
constructor(ctx: Context, config: Config);
|
|
27
|
+
/**
|
|
28
|
+
* Race npm and npmmirror HTTPS ping responses through the Host's fetch proxy.
|
|
29
|
+
* Concurrent readers share a probe; a winner cancels and awaits the other request.
|
|
30
|
+
* @returns the first registry with a successful response, or null when disabled or neither responds successfully; results are cached.
|
|
31
|
+
* @throws rejects when the service has been unloaded.
|
|
32
|
+
*/
|
|
33
|
+
fastest(): Promise<string | null>;
|
|
34
|
+
private probe;
|
|
35
|
+
}
|
|
4
36
|
//# sourceMappingURL=index.d.ts.map
|
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-
|
|
4
|
+
"version": "0.1.7-rc.1",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
@@ -23,7 +23,15 @@
|
|
|
23
23
|
"default": "./lib/client.js"
|
|
24
24
|
},
|
|
25
25
|
"./src/*": "./src/*",
|
|
26
|
-
"./package.json": "./package.json"
|
|
26
|
+
"./package.json": "./package.json",
|
|
27
|
+
"./typert": {
|
|
28
|
+
"types": "./lib/typert.host.d.ts",
|
|
29
|
+
"default": "./lib/typert.host.js"
|
|
30
|
+
},
|
|
31
|
+
"./remote": {
|
|
32
|
+
"types": "./lib/typert.remote-client.d.ts",
|
|
33
|
+
"default": "./lib/typert.remote-client.js"
|
|
34
|
+
}
|
|
27
35
|
},
|
|
28
36
|
"dsh": {
|
|
29
37
|
"client": {
|
|
@@ -38,7 +46,7 @@
|
|
|
38
46
|
},
|
|
39
47
|
"license": "MIT",
|
|
40
48
|
"peerDependencies": {
|
|
41
|
-
"@deepseek-ai/cordis": "
|
|
49
|
+
"@deepseek-ai/cordis": "~4.0.4"
|
|
42
50
|
},
|
|
43
51
|
"devDependencies": {
|
|
44
52
|
"@testing-library/react": "^16.1.0",
|
|
@@ -47,26 +55,36 @@
|
|
|
47
55
|
"clsx": "^2.1.1",
|
|
48
56
|
"react": "^18.2.0",
|
|
49
57
|
"react-dom": "^18.2.0",
|
|
50
|
-
"@deepseek-ai/cordis": "
|
|
51
|
-
"@deepseek-ai/dsh-
|
|
52
|
-
"@deepseek-ai/dsh-
|
|
53
|
-
"@deepseek-ai/dsh-client-store": "
|
|
54
|
-
"@deepseek-ai/dsh-client-test-runtime": "
|
|
55
|
-
"@deepseek-ai/dsh-client-ui-
|
|
56
|
-
"@deepseek-ai/dsh-client-ui-
|
|
57
|
-
"@deepseek-ai/dsh-client-ui-
|
|
58
|
-
"@deepseek-ai/dsh-client-ui-sidebar": "
|
|
59
|
-
"@deepseek-ai/dsh-client-ui-slots": "
|
|
60
|
-
"@deepseek-ai/dsh-
|
|
61
|
-
"@deepseek-ai/dsh-
|
|
62
|
-
"@deepseek-ai/dsh-
|
|
63
|
-
"@deepseek-ai/dsh-
|
|
58
|
+
"@deepseek-ai/cordis": "~4.0.4",
|
|
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"
|
|
64
72
|
},
|
|
65
73
|
"files": [
|
|
66
74
|
"lib/index.js",
|
|
67
75
|
"lib/client.js",
|
|
68
|
-
"lib/types/**/*.d.ts"
|
|
76
|
+
"lib/types/**/*.d.ts",
|
|
77
|
+
"lib/typert.host.js",
|
|
78
|
+
"lib/typert.host.d.ts",
|
|
79
|
+
"lib/typert.remote-client.js",
|
|
80
|
+
"lib/typert.remote-client.d.ts"
|
|
69
81
|
],
|
|
82
|
+
"dependencies": {
|
|
83
|
+
"zod": "^4.4.3",
|
|
84
|
+
"@deepseek-ai/dsh-timeout": "0.1.7-rc.1",
|
|
85
|
+
"@deepseek-ai/schemastery": "~3.18.4",
|
|
86
|
+
"@deepseek-ai/dsh-typert-protocol": "0.1.7-rc.1"
|
|
87
|
+
},
|
|
70
88
|
"scripts": {
|
|
71
89
|
"bundle": "tsdown",
|
|
72
90
|
"watch": "tsdown --watch"
|