@deepseek-ai/dsh-client-ui-plugin-manager 0.1.6-alpha.2 → 0.1.7-alpha.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.i18n.yaml +2 -2
- package/README.md +38 -9
- package/README.zh.md +38 -9
- package/lib/client.js +1154 -382
- 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/PluginManagerPage.d.ts +1 -1
- package/lib/types/client/index.d.ts +1 -1
- package/lib/types/client/locales.d.ts +63 -19
- package/lib/types/client/manager-store.d.ts +87 -25
- package/lib/types/client/presentation.d.ts +29 -7
- package/lib/types/client/slot-contract.d.ts +111 -11
- package/lib/types/index.d.ts +35 -3
- package/package.json +37 -16
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: a7e212adfc0b0ee03e4e8aa77fdf12cfa52205bf
|
|
6
|
+
README.zh.md: be30b1af3624e6c5fa40fa6caf98f6d3e5bee323
|
package/README.md
CHANGED
|
@@ -27,13 +27,17 @@ Use the **Plugins** entry in the Web sidebar to manage the profile's installed b
|
|
|
27
27
|
|
|
28
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.
|
|
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
35
|
|
|
36
|
-
|
|
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.
|
|
39
|
+
|
|
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
|
|
|
38
42
|
### Switching a bundle
|
|
39
43
|
|
|
@@ -41,11 +45,11 @@ A bundle's page shows its full package name under the title, the spec that insta
|
|
|
41
45
|
|
|
42
46
|
### Switching one row of a bundle
|
|
43
47
|
|
|
44
|
-
A row's switch on the bundle's page calls `pluginManager.setPluginEnabled`, which writes the row's `disabled` override into the profile's `cordis.patch.yml`. The tree recomposes at once on a profile with HMR, so the row's host half unmounts or mounts while the rest of the bundle keeps running, and the page follows the client module graph without reloading. Rows
|
|
48
|
+
A row's switch on the bundle's page calls `pluginManager.setPluginEnabled`, which writes the row's `disabled` override into the profile's `cordis.patch.yml`. The tree recomposes at once on a profile with HMR, so the row's host half unmounts or mounts while the rest of the bundle keeps running, and the page follows the client module graph without reloading. Rows use the shared status marker for their Host fiber phase: pending and disabled are idle, loading and unloading are ongoing, active is done, and failed is error. The switch appears only on a bundle that is on; a row without a live entry, or one the Host will not address through the profile patch, is locked with the Host's reason. A list longer than ten rows gets a filter over localized titles, descriptions, row ids, and module names.
|
|
45
49
|
|
|
46
50
|
### Configuration pages
|
|
47
51
|
|
|
48
|
-
A plugin that carries its own configuration renders it on this page rather than in Settings, through three slots the page declares: `plugins.item` (list) for an official plugin, listed in the Official group by its `label`; `plugins.bundle.config` (keyed by the bundle's package name) for a bundle's own configuration, shown on the bundle's page between its description and its rows; and `plugins.row.config` (keyed by `<package name>#<row id>`) for one row's configuration, which gives that row a **Configure** control opening the row's page. The page
|
|
52
|
+
A plugin that carries its own configuration renders it on this page rather than in Settings, through three slots the page declares: `plugins.item` (list) for an official plugin, listed in the Official group by its `label`; `plugins.bundle.config` (keyed by the bundle's package name) for a bundle's own configuration, shown on the bundle's page between its description and its rows; and `plugins.row.config` (keyed by `<package name>#<row id>`) for one row's configuration, which gives that row a **Configure** control opening the row's page. The page renders `view: 'page'` for forms with their own save controls. Official plugin cards also render `view: 'summary'` under the title; a row's detail page uses that view only when its package description is absent. Only a save writes: the page draws the title, icon, and crumb, and the entry's form drops its staged edits when the page is left. The four host-plane pages the installation ships — the shell executor, the agent loop, Subagent, and the DeepSeek search provider — come from one companion package each, [ui-settings-shell](../ui-settings-shell/README.md), [ui-settings-agent-loop](../ui-settings-agent-loop/README.md), [ui-settings-subagent](../ui-settings-subagent/README.md), and [ui-settings-web-search](../ui-settings-web-search/README.md), registered while the Host serves their namespaces. A bundle's browser half registers the same way:
|
|
49
53
|
|
|
50
54
|
```tsx ignore-check
|
|
51
55
|
ctx.slots.inject('plugins.row.config', () => ctx.slots.register({
|
|
@@ -57,6 +61,20 @@ ctx.slots.inject('plugins.row.config', () => ctx.slots.register({
|
|
|
57
61
|
|
|
58
62
|
The bundle's patch must declare the row under that id, and the registration exists while the bundle is on, so a bundle that is off shows no configure control.
|
|
59
63
|
|
|
64
|
+
### Detail page extension points
|
|
65
|
+
|
|
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`.
|
|
67
|
+
|
|
68
|
+
```tsx ignore-check
|
|
69
|
+
ctx.slots.inject('plugins.detail.section', () => ctx.slots.register({
|
|
70
|
+
name: 'plugins.detail.section',
|
|
71
|
+
id: 'acme-health',
|
|
72
|
+
locale: 'acmeHealth',
|
|
73
|
+
}, ({ t, subject }) => subject.kind === 'bundle' ? <HealthSection pkg={subject.pkg} t={t} /> : null))
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
A row's page exists only while a `plugins.row.config` entry names the row, so a contribution meant for a row renders on the page that configuration opens.
|
|
77
|
+
|
|
60
78
|
-----
|
|
61
79
|
|
|
62
80
|
<a id="understand-the-implementation"></a>
|
|
@@ -69,15 +87,21 @@ Package management uses the profile's dependency records: installed bundles can
|
|
|
69
87
|
|
|
70
88
|
### Registration
|
|
71
89
|
|
|
90
|
+
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.
|
|
91
|
+
|
|
72
92
|
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.
|
|
73
93
|
|
|
74
94
|
### The store
|
|
75
95
|
|
|
76
|
-
`PluginManagerController` owns the bundle views, busy keys, notices, install progress and the uninstall confirmation. Each read asks the inventory whether the Host manages a profile, then joins `listBundles` with `listPlugins` into one view per bundle, whose rows carry the live entry's enablement and fiber phase. It coalesces overlapping reads, refreshes after operations, on `plugin-manager/changed`, and on reconnect, and ignores late results after disposal. Install output is grouped by job id. The install dialog moves `idle → checking → starting → running → done | failed`, with `cancelling` and `applying` as the Host reports them. The check runs under an `AbortController` that going back or closing aborts, and its settlement is dropped; a run is stopped only through `pluginManager.cancelInstall
|
|
96
|
+
`PluginManagerController` owns the bundle views, busy keys, notices, install progress and the uninstall confirmation. Each read asks the inventory whether the Host manages a profile, then joins `listBundles` with `listPlugins` into one view per bundle, whose rows carry the live entry's enablement and fiber phase. It coalesces overlapping reads, refreshes after operations, on `plugin-manager/changed`, and on reconnect, and ignores late results after disposal. Install output is grouped by job id. The install dialog moves `idle → checking → starting → running → done | failed`, with `cancelling` and `applying` as the Host reports them and `unconfirmed` when an installation or cancellation response is lost; confirmed `applying` progress never regresses. Recovery uses `waitForInstall`, settling to `unknown` if no active request remains; checking and every active phase use ongoing, while the final screens use done or error. The check runs under an `AbortController` that going back or closing aborts, and its settlement is dropped; a run is stopped only through `pluginManager.cancelInstall`. Closing hides a run without discarding its state. A cancellation that overtakes installation is retried when progress or output acknowledges the request. A change the Host could not apply, a restart it waits for, and an override by a higher layer become toasts that retire on their own.
|
|
77
97
|
|
|
78
98
|
### Configuration slots
|
|
79
99
|
|
|
80
|
-
|
|
100
|
+
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
|
+
|
|
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.
|
|
103
|
+
|
|
104
|
+
`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.
|
|
81
105
|
|
|
82
106
|
</details>
|
|
83
107
|
|
|
@@ -91,7 +115,7 @@ These pages cover the sidebar, the Remote calls, and the Host-side manager.
|
|
|
91
115
|
- [ui-sidebar](../ui-sidebar/README.md) — the panel list the Plugins entry registers into; [ui-layout](../ui-layout/README.md) — the main slot the page occupies.
|
|
92
116
|
- [api-remotes](../../api/remotes/README.md) — the Remote BFF surface behind `pluginManager.*` and `pluginInventory.*`.
|
|
93
117
|
- [plugin-manager](../../boot/plugin-manager/README.md) — the Host-side manager this page drives.
|
|
94
|
-
- [ui-settings-
|
|
118
|
+
- [ui-settings-shell](../ui-settings-shell/README.md), [ui-settings-agent-loop](../ui-settings-agent-loop/README.md), [ui-settings-subagent](../ui-settings-subagent/README.md), [ui-settings-web-search](../ui-settings-web-search/README.md) — the official configuration pages that register into this page's `plugins.item` slot.
|
|
95
119
|
|
|
96
120
|
-----
|
|
97
121
|
|
|
@@ -106,15 +130,20 @@ None; this package neither assembles nor sends a provider request.
|
|
|
106
130
|
|
|
107
131
|
## Known Limitations and Deferred Work
|
|
108
132
|
|
|
133
|
+
- 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.
|
|
134
|
+
|
|
109
135
|
<a id="known-limitations-and-deferred-work"></a>
|
|
110
136
|
|
|
111
137
|
|
|
112
138
|
These limits define the reach of the management view; they are current package constraints.
|
|
113
139
|
|
|
140
|
+
- **Page lifetime** — refreshing the browser loses the tracked request and output. Reconnecting within the same page can recover an active request; completed results are not retained by the Host. Profile file locking serializes installation writes.
|
|
114
141
|
- **Only bundles are managed** — a dependency without a bundle patch is refused before it installs; one the profile already holds is left off the page unless the profile selects it, and loading plain plugin modules stays a file operation.
|
|
115
142
|
- **Rows show a phase, not a reason** — a failed row reads as failed without the Host's error text; the Host log has it.
|
|
116
143
|
- **One install at a time** — the dialog runs one pnpm command; a second spec waits for the first to finish.
|
|
117
144
|
- **No version picker** — the spec is typed as pnpm accepts it; the page neither lists registry versions nor offers upgrades.
|
|
145
|
+
- **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.
|
|
146
|
+
- **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.
|
|
118
147
|
|
|
119
148
|
<a id="dev-note"></a>
|
|
120
149
|
### Dev Note
|
|
@@ -126,4 +155,4 @@ None.
|
|
|
126
155
|
|
|
127
156
|
</details>
|
|
128
157
|
|
|
129
|
-
**Runtime invariant:** No companion is published.
|
|
158
|
+
**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
|
@@ -27,13 +27,17 @@ kind: "package-reference"
|
|
|
27
27
|
|
|
28
28
|
在侧栏选择**插件**。页面首次打开时通过 `api-remotes` 读取清单与组合包;没有受管 profile 的 Host 上页面显示为不可用。**官方**排在前面,列出安装随附、供开启的组合包——开启前保持关闭、没有卸载、属于 beta 功能的带 **Beta** 标签——其后是注册了配置页的官方插件;**已安装**列出 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
35
|
|
|
36
|
-
|
|
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,安装完成界面会说明允许了哪些脚本。安装成功不代表模块一定能够激活。
|
|
39
|
+
|
|
40
|
+
准备和下载期间,**取消安装**会请求 Host 停止运行并等待确认。加载组合包的阶段不可取消。点击 ×、按 Escape 或点击遮罩会立即隐藏对话框,并在可以取消时请求取消。**查看安装任务**会重新打开同一任务并保留输出;结果待定或尚未确认时不能发起另一项安装。确认取消后回到 spec 输入界面并显示 toast;manifest 与 lockfile 已恢复,已下载文件可能保留。安装响应丢失后会请求恢复结果;**核对安装状态**和重连会重试该请求。Host 已无活动请求时,**未能获取安装结果**允许检查插件列表后返回编辑。早于接收确认的取消请求会等待并自动重试;取消失败可手动重试。隐藏的任务通过 toast 通知结果,不会重新弹出对话框。
|
|
37
41
|
|
|
38
42
|
### 切换一个组合包
|
|
39
43
|
|
|
@@ -41,11 +45,11 @@ Agent Teams、Agent Teams Web UI 和 Auto Authorization Review 三个包使用
|
|
|
41
45
|
|
|
42
46
|
### 切换组合包里的一行
|
|
43
47
|
|
|
44
|
-
组合包页面上行的开关调用 `pluginManager.setPluginEnabled`,往 profile 的 `cordis.patch.yml` 写入该行的 `disabled` 覆盖。启用了 HMR 的 profile
|
|
48
|
+
组合包页面上行的开关调用 `pluginManager.setPluginEnabled`,往 profile 的 `cordis.patch.yml` 写入该行的 `disabled` 覆盖。启用了 HMR 的 profile 的树随即重组,该行的宿主半区卸下或挂上,组合包其余部分照常运行,页面无需重载即跟随客户端模块图。行使用共享状态标记表示 Host fiber 阶段:pending 与 disabled 为 idle,loading 与 unloading 为 ongoing,active 为 done,failed 为 error。开关只出现在已打开的组合包上;没有存活条目的行,以及 Host 不通过 profile patch 寻址的行,带着 Host 的原因锁定。超过十行的列表带一个按本地化标题、描述、行 id 和模块名筛选的输入框。
|
|
45
49
|
|
|
46
50
|
### 配置页
|
|
47
51
|
|
|
48
|
-
自带配置的插件把配置渲染在本页而不是设置里,通过本页声明的三个 slot:`plugins.item`(list)用于官方插件,按其 `label` 列在官方分组里;`plugins.bundle.config`(以组合包的包名为键)用于组合包自己的配置,显示在组合包页面的描述与行之间;`plugins.row.config`(以 `<包名>#<行 id>`
|
|
52
|
+
自带配置的插件把配置渲染在本页而不是设置里,通过本页声明的三个 slot:`plugins.item`(list)用于官方插件,按其 `label` 列在官方分组里;`plugins.bundle.config`(以组合包的包名为键)用于组合包自己的配置,显示在组合包页面的描述与行之间;`plugins.row.config`(以 `<包名>#<行 id>` 为键)用于某一行的配置,这一行由此多出一个**配置**控件,打开该行自己的页面。页面用 `view: 'page'` 渲染带自己保存控件的表单。官方插件卡片还在标题下渲染 `view: 'summary'`;行详情页只在缺少包描述时使用该视图。只有保存才写入:页面负责画标题、图标与面包屑,条目的表单在离开页面时丢弃暂存的修改。安装随附的四个宿主平面配置页——shell 执行器、agent loop、Subagent、DeepSeek 搜索提供方——各来自一个伴生包:[ui-settings-shell](../ui-settings-shell/README.zh.md)、[ui-settings-agent-loop](../ui-settings-agent-loop/README.zh.md)、[ui-settings-subagent](../ui-settings-subagent/README.zh.md) 与 [ui-settings-web-search](../ui-settings-web-search/README.zh.md),在 Host 服务其命名空间期间注册。组合包的浏览器半侧用同样的方式注册:
|
|
49
53
|
|
|
50
54
|
```tsx ignore-check
|
|
51
55
|
ctx.slots.inject('plugins.row.config', () => ctx.slots.register({
|
|
@@ -57,6 +61,20 @@ ctx.slots.inject('plugins.row.config', () => ctx.slots.register({
|
|
|
57
61
|
|
|
58
62
|
组合包的 patch 必须以该 id 声明这一行;注册只在组合包开启期间存在,因此关闭的组合包不显示配置控件。
|
|
59
63
|
|
|
64
|
+
### 详情页扩展点
|
|
65
|
+
|
|
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` 排列条目。
|
|
67
|
+
|
|
68
|
+
```tsx ignore-check
|
|
69
|
+
ctx.slots.inject('plugins.detail.section', () => ctx.slots.register({
|
|
70
|
+
name: 'plugins.detail.section',
|
|
71
|
+
id: 'acme-health',
|
|
72
|
+
locale: 'acmeHealth',
|
|
73
|
+
}, ({ t, subject }) => subject.kind === 'bundle' ? <HealthSection pkg={subject.pkg} t={t} /> : null))
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
行页只在某个 `plugins.row.config` 条目点名这一行时存在,因此给行的贡献渲染在该配置打开的页面上。
|
|
77
|
+
|
|
60
78
|
-----
|
|
61
79
|
|
|
62
80
|
<a id="understand-the-implementation"></a>
|
|
@@ -69,15 +87,21 @@ ctx.slots.inject('plugins.row.config', () => ctx.slots.register({
|
|
|
69
87
|
|
|
70
88
|
### 注册
|
|
71
89
|
|
|
90
|
+
Host 入口通过生成的 Remote 接口暴露 `pluginRegistryProbe.fastest()`,共享进行中的比较、让缓存按时过期,并在卸载时中止和等待未完成探测。卸载后调用会返回拒绝的 Promise。
|
|
91
|
+
|
|
72
92
|
浏览器插件通过 `ctx.slots.inject()` 注册 `plugins` 侧栏入口与它的 `main` 面板,使两者跟随 slot 延迟声明、本地化变化与销毁。页面为全局页面,不属于任何 Session。显示文本来自包元信息与页面字典。
|
|
73
93
|
|
|
74
94
|
### store
|
|
75
95
|
|
|
76
|
-
`PluginManagerController` 拥有组合包视图、忙碌键、提示、安装进度和卸载确认。每次读取先问清单 Host 是否管理着 profile,再把 `listBundles` 与 `listPlugins` 合成每个组合包一份视图,其行携带存活条目的启停状态与 fiber 阶段。它合并重叠读取,在操作后、收到 `plugin-manager/changed` 时以及重连后刷新,并在销毁后忽略晚到结果。安装输出按 job id 分组。安装对话框沿 `idle → checking → starting → running → done | failed` 推进,`cancelling` 与 `applying` 按 Host
|
|
96
|
+
`PluginManagerController` 拥有组合包视图、忙碌键、提示、安装进度和卸载确认。每次读取先问清单 Host 是否管理着 profile,再把 `listBundles` 与 `listPlugins` 合成每个组合包一份视图,其行携带存活条目的启停状态与 fiber 阶段。它合并重叠读取,在操作后、收到 `plugin-manager/changed` 时以及重连后刷新,并在销毁后忽略晚到结果。安装输出按 job id 分组。安装对话框沿 `idle → checking → starting → running → done | failed` 推进,`cancelling` 与 `applying` 按 Host 的报告呈现,安装或取消响应丢失时进入 `unconfirmed`;已确认的 `applying` 阶段不会倒退。通过 `waitForInstall` 恢复结果,无活动请求时结束为 `unknown`;checking 与所有活动阶段使用 ongoing,最终页面使用 done 或 error。检查在一个 `AbortController` 下运行,返回编辑或关闭会中止它并丢弃其结果;运行只能通过 `pluginManager.cancelInstall` 停止。关闭会隐藏任务而保留其状态。如果取消请求先于安装到达,在进度或输出确认该请求后会再次请求取消。Host 无法应用的变更、要等重启的变更、被更高层覆盖的变更,都是会自行消失的 toast。
|
|
77
97
|
|
|
78
98
|
### 配置 slot
|
|
79
99
|
|
|
80
|
-
|
|
100
|
+
自定义条目页以 Host 条目 id 作为注册 id;行页面使用 bundle 包名和行 id。当条目提供可编辑 Config 字段时,页面宿主传入 `form.state` 和 `form.mutate(operations, expectedRevision)`。自定义页面负责草稿和校验提示,并可复用 ui-primitives 的 `ConfigField`。整个 bundle 的页面可以包含多个条目,因此没有单一表单。
|
|
101
|
+
|
|
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 标记。
|
|
103
|
+
|
|
104
|
+
`plugins.bundle.config` 以 npm 包名为 key,提供 Bundle 详情配置。`plugins.bundle.activation` 在用户从列表显式启用后提供 Bundle 自有引导,并传入关闭引导和打开详情的回调。仅列出已启用的 Bundle 不会触发引导。
|
|
81
105
|
|
|
82
106
|
</details>
|
|
83
107
|
|
|
@@ -91,7 +115,7 @@ ctx.slots.inject('plugins.row.config', () => ctx.slots.register({
|
|
|
91
115
|
- [ui-sidebar](../ui-sidebar/README.zh.md)——插件入口注册进的面板列表;[ui-layout](../ui-layout/README.zh.md)——页面占用的主 slot。
|
|
92
116
|
- [api-remotes](../../api/remotes/README.zh.md)——`pluginManager.*` 与 `pluginInventory.*` 背后的 Remote BFF 面。
|
|
93
117
|
- [plugin-manager](../../boot/plugin-manager/README.zh.md)——本页驱动的 Host 侧管理器。
|
|
94
|
-
- [ui-settings-
|
|
118
|
+
- [ui-settings-shell](../ui-settings-shell/README.zh.md)、[ui-settings-agent-loop](../ui-settings-agent-loop/README.zh.md)、[ui-settings-subagent](../ui-settings-subagent/README.zh.md)、[ui-settings-web-search](../ui-settings-web-search/README.zh.md)——注册进本页 `plugins.item` slot 的官方配置页。
|
|
95
119
|
|
|
96
120
|
-----
|
|
97
121
|
|
|
@@ -106,15 +130,20 @@ ctx.slots.inject('plugins.row.config', () => ctx.slots.register({
|
|
|
106
130
|
|
|
107
131
|
## 已知限制与延期工作
|
|
108
132
|
|
|
133
|
+
- Ping 响应时间不代表包下载吞吐量。探测使用 Host fetch 的网络路径,pnpm 专用代理设置可能使用不同路径;用户始终可以手动选择安装源,安装仍遵循现有回退规则。
|
|
134
|
+
|
|
109
135
|
<a id="known-limitations-and-deferred-work"></a>
|
|
110
136
|
|
|
111
137
|
|
|
112
138
|
这些限制界定了管理视图的范围;它们是当前包的约束。
|
|
113
139
|
|
|
140
|
+
- **页面生命周期**——刷新浏览器会丢失跟踪的请求与输出。同一页面内重连可以恢复活动请求;Host 不保留已完成的结果。profile 文件锁串行化安装写入。
|
|
114
141
|
- **只管理组合包**——没有组合包 patch 的依赖在安装前就被拒绝;profile 里已有的这类依赖不上页面,除非 profile 选中了它;加载普通插件模块仍是文件操作。
|
|
115
142
|
- **行只显示阶段,不显示原因**——失败的行只显示为失败,没有 Host 的错误文本;Host 日志里有。
|
|
116
143
|
- **一次只能安装一个**——对话框一次运行一个 pnpm 命令;第二个 spec 要等前一个完成。
|
|
117
144
|
- **没有版本选择器**——spec 按 pnpm 接受的写法输入;页面不列出注册表版本,也不提供升级。
|
|
145
|
+
- **安装源选择只属于本浏览器**——它存在 `localStorage` 里,所以另一个浏览器会独立计算初始推荐;`dsh plugin` 命令和 agent 工具使用 Host 配置的注册表。
|
|
146
|
+
- **每次读注册表都要运行 pnpm**——打开对话框、检查、安装各问一次 pnpm 自身配置指向哪里;没有 pnpm 的机器读作未知,不提供备选。
|
|
118
147
|
|
|
119
148
|
<a id="dev-note"></a>
|
|
120
149
|
### 开发备注
|
|
@@ -126,4 +155,4 @@ ctx.slots.inject('plugins.row.config', () => ctx.slots.register({
|
|
|
126
155
|
|
|
127
156
|
</details>
|
|
128
157
|
|
|
129
|
-
**运行时不变式:**
|
|
158
|
+
**运行时不变式:** 不发布伴生检查。面板读取 Host 事实,安装源探测缓存只保存一次比较结果,没有独立维护的投影。
|