deepseek-harness/.agents/notes/implemented/architecture/2026-08-29-plugin-inventory-agent-preset-scopes.md

34 lines
6.9 KiB
Markdown
Raw Normal View History

feat(plugin-inventory): carry every agent preset's composition and group the settings plugin list by scope 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.
2026-08-29 17:13:45 +08:00
# Agent Note: The plugin inventory carries every agent preset's composition
Status: implemented
English | [中文](2026-08-29-plugin-inventory-agent-preset-scopes.zh.md)
## Problem
[Per-session agent presets](2026-08-03-per-session-agent-presets.md) moved every model-facing row onto the agent plane, and the settings plugin list kept projecting `ctx.loader.entries()` alone. The surface therefore hid the plugins sessions actually run — a directly-plugged preset subtree never appears in the Loader's entries — and actively misled about the rest: the web overlay's deliberate `disabled: true` tombstones (`tool-bash`, `tool-fs`, `plan-mode`, …) rendered as two dozen plainly "disabled" rows while the same modules ran in every standard-preset session. Beside it, General settings carried a default-preset dropdown that wrote the same `agent-presets.default` field as the roster section's own make-default action — two editors for one fact, one of them blind to the roster it was choosing from.
## Decision
**The inventory speaks for both planes.** `pluginInventory/list` gains an optional `agentPresets` block — one group per roster preset with id, trust, display name, default marking, health, and flattened composition rows — supplied by the new `AgentPresets.compositionInventory()`: a preset with a live standing mount — matched within this runtime's own root, so a second Cordis runtime in the same process never answers for it — answers from its newest generation's Loader entries even when its file has since broken (the mount is what sessions run; the broken verdict applies only to a preset nothing composed), and one never composed since boot answers from its composition file. `dsh-host-plugin-inventory` resolves the roster as an optional peer through `ctx.get('agentPresets')` (the `plugin-package-inventory-deepseek` pattern) and only maps root-fiber states onto its public phase vocabulary, so deployments without a roster keep serving Loader entries alone with the field absent.
feat(plugin-inventory): carry every agent preset's composition and group the settings plugin list by scope 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.
2026-08-29 17:13:45 +08:00
**File answers are evaluated, not guessed, and reading never mounts.** `!!js` disabled gates are platform/environment conditions the [Loader itself evaluates at every mount decision](2026-08-11-loader-entry-disabled-interpolation.md), so the file read evaluates them against the Loader context and reports the decision a mount on this host would make; a gate the evaluator refuses stays `'conditional'` with its expression text carried for display. The read parses and evaluates only — no import, no compose — so listing every preset's plugins activates none of them, and a regression test pins `livePresetMounts()` empty after a full inventory read. Building this surface also exposed the reverse leak: `EntryTree`'s constructor files every new tree under the nearest owning Loader entry's `subtree` slot, so the first standing mount hung the whole preset composition off the roster's own row and root `loader.entries()` walked it as host entries. `PresetTree` now reclaims the slot, restoring the standing mount's documented absence from the Loader, and a regression test holds the root entry list identical across a mount.
feat(plugin-inventory): carry every agent preset's composition and group the settings plugin list by scope 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.
2026-08-29 17:13:45 +08:00
**The list is grouped by scope, with the misleading rows given their own state.** The preset group renders first, collapsible and open by default, behind a display-only switcher — the General-settings selector pill over a menu — that opens on the default preset and writes no settings, because inspecting `minimal` must not change what new sessions run. Preset names resolve through the shared `presetDisplayText` fold in `dsh-agent-presets/display` — the groups carry `trust` for exactly this split, and an inline-safe pure module is the seam that satisfies both the client purity gate (no cross-plugin runtime imports) and the typert client analyzer (no new Context service face) — so shipped presets follow the active locale's dictionaries while user-authored metadata stays untranslated. The global group follows collapsed, failures float first, and a global entry that is disabled while at least one preset row for the same module specifier is actually enabled is marked preset-provided in place, its details naming the enabling presets — a third state instead of the generic "disabled" that started this, and deliberately not a sub-group: the preset group above already shows those plugins as compositions, so a second cluster restating them earned its removal. The status dot appears only for a live root fiber — a file-state row carries its enablement tag alone, so an unmounted preset does not read as a column of grey mystery dots. The provider rule is strict `enabled === true`: counting conditional declarations would claim per-session provision `tool-pwsh` never delivers on POSIX. Search spans both groups, forces them open, and points at matches sitting in unselected presets.
feat(plugin-inventory): carry every agent preset's composition and group the settings plugin list by scope 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.
2026-08-29 17:13:45 +08:00
**The General row is deleted, not relocated.** The default keeps two surfaces that can still act on it — the roster section's make-default beside the visible roster, and the new-session chip for the session about to start — so `ui-agent-preset` drops the row, its menu, and the write/writability half of its settings store, which slims to the display roster the header label reads.
## Alternatives considered
**Render every preset as its own always-open section.** Four shipped presets already put ~100 rows behind the fold; the switcher keeps one composition in view while the per-row provider details and the search pointers preserve the cross-scope answer the all-at-once layout was buying.
feat(plugin-inventory): carry every agent preset's composition and group the settings plugin list by scope 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.
2026-08-29 17:13:45 +08:00
**Keep file-state gates unevaluated (`conditional` until first mount).** Honest but it re-created the misleading reading this change removes: on a cold host the default preset's `tool-bash` read as "conditional" and its host row fell back to plain "disabled" until the first session mounted the preset.
**A structured composition viewer in the Agent presets section.** A second home for the same rows; the section keeps its raw-YAML viewer for authors and the plugin list owns the structured view.
**Enable/disable toggles in the same change.** Writing a row's `disabled` back into a custom preset's `agent.cordis.yml` needs comment-preserving partial YAML edits, applies-to-new-sessions messaging, and a copy-then-edit path for shipped presets — deliberately its own change; this one is read-side truth.
## Consequences
Searching "bash" now answers the question that motivated the change in one screen: enabled in the standard preset, provided per session where the global plane disabled it, plainly disabled only where nothing enables it. The wire snapshot's row enablement is the union `boolean | 'conditional'` with the gate expression beside it, and the settings-chrome goldens pin the grouped layout. `ui-agent-preset` loses `AgentPresetRow` and `PresetMenu`; the `settings.agentPreset` locale namespace declaration moved to the plugin entry, and the `settings-chrome` English scenario probes locale resolution through the nav label instead of the deleted row.