A collapsed plugin card carried both an enablement tag and a green root-fiber
status dot; for the common `active` phase the two stated the same settled
state. The dot now renders only for `pending`, `loading` and `unloading`,
the phases the enablement tag cannot state, and the expanded card keeps the
phase fact.
Related to #4424
Master now carries the final #4182 plugin manager and the live client
module graph of #4189, so the web management layer is re-ported onto
them:
- The manager keeps master's `ManagementError` codes, `ChangeResult`
stages, `configure()` under `hmr.runExclusive`, `reload()` warnings
and `writePluginEnabled` name matching, and adds back `inspect`,
request-id installs with `plugin-manager/install-log` and
`plugin-manager/install-state`, `cancelInstall`, `listBundles` titles,
rows and overrides, `packageResult.kind`, `pnpmCommand` and
`inspectTimeoutMs`, and `plugin-manager/changed` after each change and
each `profile/reconciled`. A failed or cancelled installation restores
the snapshotted manifest and lockfile instead of master's one-shot
`pnpm remove` cleanup, so `cleanup` and `remainingDependencies` leave
the result.
- App boot keeps master's reconciliation diagnostics and emits
`profile/reconciled`; `watchUserPatches` goes with the launcher's
watcher now that HMR owns profile reloads.
- The settings Plugin list tab stays read-only with #4189's page-local
sync failures and retry; master's bundle form and row checkboxes are
dropped again. The sidebar page words the Host's codes through its
dictionary, and a client bundle change follows the live module graph
without a page reload.
- The web e2e scaffold no longer chooses a reload policy: the base
bundle's `hmr` row turns on with the profile context and application
readiness, and `profile.hmr: false` disables it through an overlay for
the startup-only scenario.
The sidebar Plugins page now drives `ctx.pluginManager` from #4182 and the
settings Plugin list tab goes back to read-only: its bundle form and row
checkboxes are removed, and the page joins `listBundles` with
`listPlugins` into one view per bundle, decides availability from the
inventory's `managementAvailable`, switches bundles and rows through
`setBundleEnabled` and `setPluginEnabled`, and confirms uninstalls.
The manager gains what the guided install dialog needs: `inspect(spec)`
reads a spec through the registry or a directory before anything installs
and refuses specs without a bundle; `installBundle` takes a request id,
streams each pnpm run as `plugin-manager/install-log`, reports its phases
as `plugin-manager/install-state`, classifies a failed run into
`packageResult.kind`, names the bundle it added, and restores
`package.json` and `pnpm-lock.yaml` when the run fails, is cancelled, or
adds a package without a bundle patch; `cancelInstall(requestId)` answers
only after pnpm exited and the files are back; `listBundles` carries
titles, one-liners, declared rows with their live entries, and the
built-in rows a bundle overrides; every completed operation and every
patch generation applied outside the manager emits
`plugin-manager/changed`. The config gains `pnpmCommand` and
`inspectTimeoutMs`. App boot emits `profile/reconciled` after each applied
patch generation and finds a root Include mounted under its pinned id by
another copy of the module.
Row diagnostics, dependency checks, and the non-bundle dependency group
are not carried over; the web e2e scaffold mounts a launcher-style
managed profile instead of the removed profile runtime.
The web plugin management layer now sits on the current-profile plugin
manager from #4182 instead of the boot and manager layers of #3593 and
#3594. This merge takes #4182's host side whole — `packages/boot/hmr`,
its `packages/boot/plugin-manager`, `profileContext` in app-boot, the
vendored loader change — and drops the replaced host packages and their
app-boot, inventory, Remote, CLI, and catalog edits. The web files of
#3596 are carried over unchanged: the sidebar page package, the
ui-primitives terminal changes, the settings-section tweaks, the e2e
scenarios and fixtures, and the two web Agent Notes. They do not build
against the new host until the following commit adapts them.
The sidebar page no longer shows the profile template's own bundles
(dsh-base, dsh-web-app). The Settings Plugins section gets its read-only
Plugin list tab back for them, with both of its groups collapsed by
default. Restores @deepseek-ai/dsh-client-ui-settings-plugin-inventory
with its web bundle row, tsconfig entries, catalogs, and goldens, and
drops the built-in tag, locked switch, and source fact the page no
longer needs.
The Manage plugins tab now speaks two nouns (plugin pack, plugin) and four
states (running, off, restart needed, problem). Cards carry a display name,
a one-liner, one kind tag, and their uninstall beside the switch; built-in
packs fold behind one line; the selected preset's composition renders as a
grid of cards named through the tab's own dictionary for harness modules
and through the manifest for third-party ones; entry ids, module names,
cordis copies, and probe times leave the page. The install dialog keeps
pnpm's output behind a fold that opens on failure and words each outcome;
confirmations name what still uses the package and no longer require an
acknowledgement. The client passes a preset row's declared id, not the
live tree's prefixed entry id, to the row actions.
The read-only Plugin list tab and its package, ui-settings-plugin-inventory,
are removed; the pluginInventory Remote stays as the manager's source of
preset compositions and host-tree module names.
Add the Manage plugins tab (ui-settings-plugin-manager): package cards
with enable/disable switches, retry and uninstall, an install dialog
streaming pnpm output, and the selected preset's composition with per-row
switches and removal; the read-only list gains provenance facts.
Settings cards edit one scope at a time: ui-settings keeps one describe
mirror per settings scope (SettingsMirrorRegistry) and binds
{ namespace, scope }; ui-settings-plugins stages forms per scope, marks
inherited fields, and adds the skill-filesystem roots card.
Fixes surfaced by driving the browser end to end: the client gateway now
lets a call omit trailing parameters that accept undefined (PR3's
optional scope arguments failed the strict arity check), the plugins
install verb is `add` because the client namespace service reserves
`install`/`remove` (guarded by remote-method-names.host.spec), a plugin
module offers its main export as an addable `.`, and describing a settings
scope no owner registered resolves over the global instance's base and
never throws.
The web e2e scaffold can mount a profile runtime over fixture packages;
plugin-manager.e2e drives the tab and writes one field under a preset
scope; plugin-config and settings-chrome goldens are refreshed.
Add `dsh-host-plugin-manager`: the `pluginManager` service and the
`plugins` Remote that install, uninstall, enable, disable, and retry
bundles over the booted profile, add and remove rows in the profile's
or an agent preset's user layer, report what a package's services keep
alive, and fold the manifest, the probe record, and the live tree into
one view per package. pnpm runs the way the CLI runs it, streaming its
output as `plugins/install-log`; every change emits `plugins/changed`,
and api-remotes forwards both. Enabling is the Loader's own
transaction: a rejected recomposition restores the layer list, and a
runtime recomposition records contained rows through
`recordContainedStates` because the boot audit does not run again;
`ContainedGroup.create` now records a row that resolved pending rather
than clearing it.
Give every agent preset a user patch layer: `cordis.patch.yml` beside
a locally authored composition, or alone in `.agent-presets/<id>/`
for a shipped one. Discovery attaches and judges it, the mount applies
it as the include's runtime patches, standing generations follow the
layer's content and return to a retired generation when it comes back,
the composition inventory reports each row's `source` and
`disabledBy`, a copy carries the layer, and `removeOverlay` deletes it.
`dsh-global-tool-mask` is `tools.restrict()` as a scope-only row for
such a layer.
Add `dsh-patch-file` as the one home of the patch-list format: the
parser moved out of app-boot, plus a comment-preserving key-level
writer with lock, atomic replace, and readback. app-boot exports
`layerTrust`, `bundleLayerPatches`, and `recordContainedStates`, and
`ProfileRuntime` carries the install anchor.
Review round on #3316: the composition inventory answers from a standing
mount before the broken verdict (a file corrupted after mounting no longer
hides the running composition), livePresetMounts filters by the caller's
root fiber so a second Cordis runtime in one process never answers for it,
compositions carry trust and the plugin list resolves shipped preset names
through the shared dsh-agent-presets/display fold over ui-agent-preset's
dictionaries (INLINE_SAFE inline import; no cross-plugin runtime import),
the condition detail label reads Disabled when/禁用条件, and the stale
four-surfaces comment says three.
Both scope groups drop their boxed chrome for the General-settings row
idiom: a title row (session-plugins title with the right-aligned preset
selector pill; global-plugins title), a grey subtitle line carrying the
count, a hairline divider between groups, and the cards grid below.
Counts ride the subtitle as text while the data attributes keep the raw
numbers for tests.
Review-driven refinements to the scope-grouped plugin list:
- The preset switcher becomes the General-settings selector pill over the
shared Menu primitive instead of a native select, and the preset group
is collapsible like the global one (search still forces both open).
- The session-plugins drawer is removed: rows the presets took over sit
inline in the global list with the preset-provided tag and per-preset
details, since the preset group above already shows those compositions.
- The status dot renders only for a live root fiber, so file-state rows
of an unmounted preset carry their enablement tag alone instead of a
column of grey dots.
- PresetTree reclaims the owning entry's subtree slot: EntryTree's
constructor filed the standing mount under the roster's own Loader row,
so after the first session composed a preset the whole composition
leaked into root loader.entries() as host rows (each preset overwriting
the last). A regression test holds the root entry list identical across
a mount.
The settings plugin list projected ctx.loader.entries() alone, hiding the
plugins sessions actually run and rendering the web overlay's deliberate
disabled tombstones (tool-bash, tool-fs, ...) as two dozen plainly disabled
rows while the same modules ran in every standard-preset session.
- dsh-agent-presets: compositionInventory() answers flattened rows per
preset — newest live standing generation when mounted, composition file
otherwise with !!js disabled gates evaluated against the Loader context;
reading never mounts (regression-tested), refusal stays 'conditional',
raced files report broken with the reason.
- dsh-host-plugin-inventory: list() gains an optional agentPresets block,
resolving the roster as an optional peer and mapping fiber states to the
public phase vocabulary.
- ui-settings-plugin-inventory: preset group first behind a display-only
switcher opening on the default preset; global group collapsed with
failures floated; host-disabled modules enabled by >=1 preset fold into a
session-plugins drawer naming providers; search spans scopes and points
at matches in unselected presets.
- ui-agent-preset: the General-settings default-preset row is deleted — the
roster section's make-default and the new-session chip keep the field —
and the settings store slims to the display roster the header label reads.
Docs, catalogs, module graph, settings-chrome goldens, and the bilingual
Agent Note ride along.