Simplify plugin activation toggle UI
Replace the plugin list activation dropdown and advanced shortcut with a one-click ON/OFF switch. Keep project/profile-specific activation inside the plugin config flow, remove the old advanced-only modal, update plugin docs, and add regression coverage for the binary list toggle contract.
Alessandro committed
May 21, 2026 at 04:31 UTC
d4a9cd82d5afef76a5522e0bc61bbf581bf2c033
9 files changed
+194
-370
docs/agents/AGENTS.plugins.md
+2
-2
@@ -228,9 +228,9 @@ embedding:
228
229
- Global and scoped activation are independent, with no inheritance between scopes.
230
- Activation flags are files: `.toggle-1` (ON) and `.toggle-0` (OFF).
231
-- UI states are `ON`, `OFF`, and `Advanced` (shown when any project/profile-specific override exists).
231
+- The plugin list shows a binary `ON`/`OFF` global activation switch.
232
- `always_enabled: true` in `plugin.yaml` forces ON and disables toggle controls in the UI.
233
-- The "Switch" modal is the canonical per-scope activation surface, and "Configure Plugin" keeps scope synchronized with the settings modal.
233
+- For plugins with project/profile scoping, the plugin config modal is the canonical per-scope activation surface.
234
235
---
236
helpers/plugins.py
+4
-19
@@ -44,7 +44,7 @@ _META_TARGET_RE = re.compile(
44
)
45
46
47
-type ToggleState = Literal["enabled", "disabled", "advanced"]
47
+type ToggleState = Literal["enabled", "disabled"]
48
49
50
class PluginAssetFile(TypedDict):
@@ -517,30 +517,15 @@ def get_toggle_state(plugin_name: str) -> ToggleState:
517
if meta.always_enabled:
518
return "enabled"
519
520
- # root plugin paths
520
+ # List-level activation is the global/root state. Scoped project/profile
521
+ # overrides are managed inside the plugin config modal.
522
plugin_paths = get_plugin_roots(plugin_name)
522
- state = (
523
+ return (
524
"enabled"
525
if determined_toggle_from_paths(True, reversed(plugin_paths))
526
else "disabled"
527
)
528
528
- # additional toggles in project/agent directories, return advanced
529
- if meta.per_agent_config or meta.per_project_config:
530
- configs = find_plugin_assets(
531
- TOGGLE_FILE_PATTERN,
532
- plugin_name=plugin_name,
533
- project_name="*" if meta.per_project_config else "",
534
- agent_profile="*" if meta.per_agent_config else "",
535
- only_first=False,
536
- )
537
-
538
- # Advanced if there are specific overrides (project or agent specific)
539
- if any(c.get("project_name") or c.get("agent_profile") for c in configs):
540
- state = "advanced"
541
-
542
- return state
543
-
529
530
@extension.extensible
531
def toggle_plugin(
tests/test_plugin_activation_ui.py
new
+108
@@ -0,0 +1,108 @@
1
+import sys
2
+from pathlib import Path
3
+from types import SimpleNamespace
4
+
5
+
6
+PROJECT_ROOT = Path(__file__).resolve().parents[1]
7
+if str(PROJECT_ROOT) not in sys.path:
8
+ sys.path.insert(0, str(PROJECT_ROOT))
9
+
10
+from helpers import files, plugins
11
+
12
+
13
+def test_plugins_list_uses_binary_toggle_instead_of_advanced_dropdown():
14
+ html = (PROJECT_ROOT / "webui/components/plugins/list/plugin-list.html").read_text(
15
+ encoding="utf-8"
16
+ )
17
+ store = (
18
+ PROJECT_ROOT / "webui/components/plugins/list/pluginListStore.js"
19
+ ).read_text(encoding="utf-8")
20
+
21
+ assert "plugin-status-toggle" in html
22
+ assert "plugin-status-select" not in html
23
+ assert "Open Advanced" not in html
24
+ assert 'value="advanced"' not in html
25
+ assert "openPluginAdvancedToggle" not in store
26
+ assert "plugin-toggle-advanced.html" not in store
27
+
28
+
29
+def test_advanced_plugin_toggle_modal_was_removed():
30
+ assert not (
31
+ PROJECT_ROOT / "webui/components/plugins/toggle/plugin-toggle-advanced.html"
32
+ ).exists()
33
+
34
+
35
+def test_list_toggle_state_is_global_even_when_scoped_rules_exist(monkeypatch):
36
+ monkeypatch.setattr(
37
+ plugins,
38
+ "get_plugin_meta",
39
+ lambda _plugin_name: SimpleNamespace(always_enabled=False),
40
+ )
41
+ monkeypatch.setattr(
42
+ plugins,
43
+ "get_plugin_roots",
44
+ lambda _plugin_name: ["plugins/example", "usr/plugins/example"],
45
+ )
46
+ monkeypatch.setattr(
47
+ plugins,
48
+ "determined_toggle_from_paths",
49
+ lambda _default, _paths: False,
50
+ )
51
+
52
+ def fail_on_scoped_lookup(*_args, **_kwargs):
53
+ raise AssertionError("Plugin list toggle state should not inspect scoped rules")
54
+
55
+ monkeypatch.setattr(plugins, "find_plugin_assets", fail_on_scoped_lookup)
56
+
57
+ assert plugins.get_toggle_state("example") == "disabled"
58
+
59
+
60
+def test_config_scope_activation_toggle_saves_immediately_for_selected_scope():
61
+ html = (PROJECT_ROOT / "webui/components/plugins/plugin-settings.html").read_text(
62
+ encoding="utf-8"
63
+ )
64
+ settings_store = (
65
+ PROJECT_ROOT / "webui/components/plugins/plugin-settings-store.js"
66
+ ).read_text(encoding="utf-8")
67
+ toggle_store = (
68
+ PROJECT_ROOT / "webui/components/plugins/toggle/plugin-toggle-store.js"
69
+ ).read_text(encoding="utf-8")
70
+
71
+ assert "@change=\"context.setPluginEnabled($event.target.checked)\"" in html
72
+ assert "projectName: this.projectName || \"\"" in settings_store
73
+ assert "agentProfileKey: this.agentProfileKey || \"\"" in settings_store
74
+ assert (
75
+ "async setEnabled(enabled, { projectName = this.projectName, "
76
+ "agentProfileKey = this.agentProfileKey } = {})"
77
+ ) in toggle_store
78
+ assert "action: \"toggle_plugin\"" in toggle_store
79
+
80
+
81
+def test_toggle_plugin_writes_project_scope_file_immediately(tmp_path, monkeypatch):
82
+ monkeypatch.setattr(files, "_base_dir", str(tmp_path))
83
+ monkeypatch.setattr(plugins, "after_plugin_change", lambda *_args, **_kwargs: None)
84
+ monkeypatch.setitem(
85
+ sys.modules,
86
+ "helpers.projects",
87
+ SimpleNamespace(
88
+ get_project_meta=lambda name, *sub_dirs: files.get_abs_path(
89
+ "usr/projects",
90
+ name,
91
+ ".a0proj",
92
+ *sub_dirs,
93
+ )
94
+ ),
95
+ )
96
+
97
+ plugins.toggle_plugin.__wrapped__("example", False, project_name="alpha")
98
+
99
+ scoped_plugin_dir = (
100
+ tmp_path / "usr/projects/alpha/.a0proj/plugins/example"
101
+ )
102
+ assert (scoped_plugin_dir / ".toggle-0").exists()
103
+ assert not (scoped_plugin_dir / ".toggle-1").exists()
104
+
105
+ plugins.toggle_plugin.__wrapped__("example", True, project_name="alpha")
106
+
107
+ assert (scoped_plugin_dir / ".toggle-1").exists()
108
+ assert not (scoped_plugin_dir / ".toggle-0").exists()
webui/components/plugins/list/plugin-list.html
+39
-41
@@ -180,24 +180,17 @@
180
<div class="plugin-footer-row">
181
<div class="plugin-description" x-text="plugin.description || 'No description provided.'"></div>
182
<div class="plugin-status-group">
183
- <template x-if="plugin.toggle_state === 'advanced'">
184
- <button type="button"
185
- class="button icon-button"
186
- title="Open Advanced"
187
- @click="$store.pluginListStore.openPluginAdvancedToggle(plugin)">
188
- <span class="icon material-symbols-outlined">rule_settings</span>
189
- </button>
190
- </template>
191
- <select class="plugin-status-select"
192
- @change="$store.pluginListStore.updateToggle(plugin, $event.target.value)"
193
- @click.stop
194
- :disabled="plugin.always_enabled">
195
- <option value="enabled" :selected="plugin.toggle_state === 'enabled'">ON</option>
196
- <option value="disabled" :selected="plugin.toggle_state === 'disabled'">OFF</option>
197
- <template x-if="plugin.per_project_config || plugin.per_agent_config">
198
- <option value="advanced" :selected="plugin.toggle_state === 'advanced'">Advanced</option>
199
- </template>
200
- </select>
183
+ <label class="toggle plugin-status-toggle"
184
+ :class="{ 'disabled-appearance': plugin.always_enabled || $store.pluginListStore.loading }"
185
+ :title="plugin.always_enabled ? 'Always enabled' : ($store.pluginListStore.isPluginEnabled(plugin) ? 'Disable plugin' : 'Enable plugin')">
186
+ <input type="checkbox"
187
+ :checked="$store.pluginListStore.isPluginEnabled(plugin)"
188
+ :disabled="plugin.always_enabled || $store.pluginListStore.loading"
189
+ @change="$store.pluginListStore.updateToggle(plugin, $event.target.checked)"
190
+ @click.stop>
191
+ <span class="toggler"></span>
192
+ </label>
193
+ <span class="plugin-status-text" x-text="$store.pluginListStore.toggleStatusLabel(plugin)"></span>
194
</div>
195
</div>
196
</div>
@@ -389,35 +382,40 @@
382
flex: 0 0 auto;
383
}
384
392
- .plugin-status-group .button {
393
- padding: 0.3rem 0.5rem;
394
- height: 2.2rem;
395
- display: flex;
396
- align-items: center;
397
- justify-content: center;
385
+ .plugin-status-toggle {
386
+ width: 48px;
387
+ height: 28px;
388
+ flex: 0 0 48px;
389
}
390
400
- .plugin-status-group .button .icon {
401
- font-size: 1.1rem;
391
+ .plugin-status-toggle .toggler {
392
+ border-radius: 999px;
393
}
394
404
- .plugin-status-select {
405
- padding: 0.3rem 2rem 0.3rem 0.6rem;
406
- font-size: 0.9rem;
407
- border: 1px solid var(--color-border);
408
- border-radius: 4px;
409
- background: var(--color-background);
410
- color: var(--color-text-primary);
411
- cursor: pointer;
412
- height: 2.2rem;
413
- width: 8rem;
414
- flex: 0 0 8rem;
395
+ .plugin-status-toggle .toggler:before {
396
+ width: 20px;
397
+ height: 20px;
398
+ left: 4px;
399
+ bottom: 4px;
400
+ }
401
+
402
+ .plugin-status-toggle input:checked + .toggler:before {
403
+ transform: translateX(20px);
404
}
416
-
417
- .plugin-status-select:disabled {
418
- opacity: 0.7;
405
+
406
+ .plugin-status-toggle input:disabled + .toggler {
407
cursor: default;
420
- background: var(--color-bg-secondary);
408
+ }
409
+
410
+ .plugin-status-toggle.disabled-appearance {
411
+ opacity: 0.6;
412
+ }
413
+
414
+ .plugin-status-text {
415
+ color: var(--color-text-primary);
416
+ font-size: 0.9rem;
417
+ font-weight: 600;
418
+ min-width: 2rem;
419
}
420
421
.plugin-description {
webui/components/plugins/list/pluginListStore.js
+15
-26
@@ -2,7 +2,6 @@ import { createStore } from "/js/AlpineStore.js";
2
import * as api from "/js/api.js";
3
import { renderSafeMarkdown } from "/js/safe-markdown.js";
4
import { store as pluginSettingsStore } from "/components/plugins/plugin-settings-store.js";
5
-import { store as pluginToggleStore } from "/components/plugins/toggle/plugin-toggle-store.js";
5
import { store as pluginExecuteStore } from "/components/plugins/list/plugin-execute-store.js";
6
import { store as fileBrowserStore } from "/components/modals/file-browser/file-browser-store.js";
7
import { store as markdownModalStore } from "/components/modals/markdown/markdown-store.js";
@@ -114,47 +113,37 @@ const model = {
113
}
114
},
115
117
- async openPluginAdvancedToggle(plugin) {
118
- if (!plugin?.name) return;
119
- this.selectedPlugin = plugin;
120
- try {
121
- if (!pluginToggleStore?.open) {
122
- throw new Error("Plugin toggle store is unavailable.");
123
- }
124
- await pluginToggleStore.open(plugin);
125
- window.openModal?.("components/plugins/toggle/plugin-toggle-advanced.html");
126
- } catch (e) {
127
- showErrorNotification(e, "Failed to open plugin switch");
128
- }
116
+ isPluginEnabled(plugin) {
117
+ if (plugin?.always_enabled) return true;
118
+ return plugin?.toggle_state === "enabled";
119
+ },
120
+
121
+ toggleStatusLabel(plugin) {
122
+ return this.isPluginEnabled(plugin) ? "ON" : "OFF";
123
},
124
131
- async updateToggle(plugin, value) {
125
+ async updateToggle(plugin, enabled) {
126
if (!plugin?.name) return;
133
-
134
- if (value === 'advanced') {
135
- await this.openPluginAdvancedToggle(plugin);
136
- return;
137
- }
127
+ if (plugin.always_enabled) return;
128
139
- const enabled = value === 'enabled';
140
- const clearOverrides = plugin.toggle_state === 'advanced';
141
- if (clearOverrides && !window.confirm(
142
- `"${plugin.display_name || plugin.name}" has per-scope activation rules that will be removed. Set globally to ${enabled ? 'ON' : 'OFF'}?`
143
- )) return;
129
+ const nextEnabled = !!enabled;
130
+ const previousState = plugin.toggle_state;
131
+ plugin.toggle_state = nextEnabled ? "enabled" : "disabled";
132
133
this.loading = true;
134
try {
135
const response = await api.callJsonApi("plugins", {
136
action: "toggle_plugin",
137
plugin_name: plugin.name,
150
- enabled: enabled,
138
+ enabled: nextEnabled,
139
project_name: "",
140
agent_profile: "",
153
- clear_overrides: clearOverrides,
141
+ clear_overrides: false,
142
});
143
if (response?.error) throw new Error(response.error);
144
await this.refresh();
145
} catch (e) {
146
+ plugin.toggle_state = previousState;
147
showErrorNotification(e, "Failed to toggle plugin");
148
this.loading = false;
149
}
webui/components/plugins/plugin-settings-store.js
+13
@@ -114,6 +114,19 @@ const model = {
114
await pluginToggleStore.loadToggleStatus();
115
},
116
117
+ async setPluginEnabled(enabled) {
118
+ if (!pluginToggleStore?.setEnabled) return;
119
+ this.error = null;
120
+ try {
121
+ await pluginToggleStore.setEnabled(enabled, {
122
+ projectName: this.projectName || "",
123
+ agentProfileKey: this.agentProfileKey || "",
124
+ });
125
+ } catch (e) {
126
+ this.error = e?.message || "Failed to save activation state";
127
+ }
128
+ },
129
+
130
async onScopeChanged() {
131
const nextProject = this.projectName || "";
132
const nextProfile = this.agentProfileKey || "";
webui/components/plugins/plugin-settings.html
+2
-2
@@ -49,8 +49,8 @@
49
<label class="toggle">
50
<input type="checkbox"
51
:checked="$store.pluginToggle?.status === 'enabled'"
52
- :disabled="$store.pluginToggle?.isSaving"
53
- @change="$store.pluginToggle.setEnabled($event.target.checked)">
52
+ :disabled="$store.pluginToggle?.isSaving || context.isLoading"
53
+ @change="context.setPluginEnabled($event.target.checked)">
54
<span class="toggler"></span>
55
</label>
56
<span class="plugin-toggle-status-text" x-text="$store.pluginToggle?.statusLabel"></span>
webui/components/plugins/toggle/plugin-toggle-advanced.html
deleted
-261
@@ -1,261 +0,0 @@
1
-<html>
2
-<head>
3
- <title>Plugin Switch</title>
4
- <script type="module">
5
- import { store } from "/components/plugins/toggle/plugin-toggle-store.js";
6
- </script>
7
-</head>
8
-<body>
9
- <div x-data>
10
- <template x-if="$store.pluginToggle">
11
- <div x-create="$store.pluginToggle.open($store.pluginListStore?.selectedPlugin || '')">
12
-
13
- <!-- Error -->
14
- <div x-show="$store.pluginToggle.error" class="plugin-settings-error">
15
- <span class="material-symbols-outlined">error</span>
16
- <span x-text="$store.pluginToggle.error"></span>
17
- </div>
18
-
19
- <!-- Loading -->
20
- <div x-show="$store.pluginToggle.isLoading" class="plugin-settings-loading">
21
- <span class="material-symbols-outlined spinning">progress_activity</span>
22
- <span>Loading status...</span>
23
- </div>
24
-
25
- <!-- Main UI -->
26
- <div x-show="!$store.pluginToggle.isLoading">
27
-
28
- <!-- Scope selector + per-scope ON/OFF -->
29
- <div class="plugin-settings-scope-section">
30
- <div class="plugin-settings-scope-header">
31
- <div class="plugin-settings-scope-header-copy">
32
- <div class="plugin-settings-scope-title">Scope</div>
33
- <div class="plugin-settings-scope-desc">Select which project or agent profile to configure.</div>
34
- </div>
35
- <div class="plugin-settings-scope-header-toggle">
36
- <span class="plugin-settings-toolbar-label">Enabled</span>
37
- <label class="toggle" :class="{ 'disabled-appearance': $store.pluginToggle.alwaysEnabled || $store.pluginToggle.isSaving }">
38
- <input type="checkbox"
39
- :checked="$store.pluginToggle.status === 'enabled'"
40
- :disabled="$store.pluginToggle.alwaysEnabled || $store.pluginToggle.isSaving"
41
- @change="$store.pluginToggle.setEnabled($event.target.checked)">
42
- <span class="toggler"></span>
43
- </label>
44
- <span class="plugin-toggle-status-text" x-text="$store.pluginToggle.statusLabel"></span>
45
- </div>
46
- </div>
47
- <div class="plugin-settings-toolbar">
48
- <div class="plugin-settings-toolbar-row">
49
-
50
- <label class="plugin-settings-toolbar-item">
51
- <span class="plugin-settings-toolbar-label">Project</span>
52
- <select x-model="$store.pluginToggle.projectName"
53
- @change="$store.pluginToggle.onScopeChanged()"
54
- :disabled="!$store.pluginToggle.perProjectConfig">
55
- <option value="">Global</option>
56
- <template x-for="p in $store.pluginToggle.projects" :key="p.key">
57
- <option :value="p.key" x-text="p.label"></option>
58
- </template>
59
- </select>
60
- </label>
61
-
62
- <label class="plugin-settings-toolbar-item">
63
- <span class="plugin-settings-toolbar-label">Agent profile</span>
64
- <select x-model="$store.pluginToggle.agentProfileKey"
65
- @change="$store.pluginToggle.onScopeChanged()"
66
- :disabled="!$store.pluginToggle.perAgentConfig">
67
- <option value="">All profiles</option>
68
- <template x-for="a in $store.pluginToggle.agentProfiles" :key="a.key">
69
- <option :value="a.key" x-text="a.label"></option>
70
- </template>
71
- </select>
72
- </label>
73
-
74
- <button type="button" class="button plugin-settings-toolbar-button" title="View all scope configs" @click="$store.pluginToggle.openConfigListModal()">
75
- <span class="icon material-symbols-outlined">list</span>
76
- </button>
77
-
78
- </div>
79
- </div>
80
-
81
- <div x-show="$store.pluginToggle.noScopeRuleMessage" class="plugin-settings-scope-info">
82
- <span class="material-symbols-outlined">info</span>
83
- <span x-text="$store.pluginToggle.noScopeRuleMessage"></span>
84
- </div>
85
- </div>
86
-
87
- </div>
88
-
89
- </div>
90
- </template>
91
- </div>
92
-
93
- <!-- Footer -->
94
- <div class="modal-footer" data-modal-footer>
95
- <template x-if="$store.pluginToggle.hasConfigScreen">
96
- <button type="button" class="btn btn-ok footer-btn-left" @click="$store.pluginToggle.openConfigWithScope()">
97
- <span class="icon material-symbols-outlined">settings</span>
98
- Configure Plugin
99
- </button>
100
- </template>
101
- <button class="btn btn-cancel" @click="window.closeModal?.()">
102
- Close
103
- </button>
104
- </div>
105
-
106
- <style>
107
- .footer-btn-left {
108
- margin-left: var(--spacing-lg);
109
- margin-right: auto;
110
- }
111
-
112
- /* Shared scope layout — mirrors plugin-settings.html */
113
- .plugin-settings-scope-section {
114
- border: 1px solid var(--color-border);
115
- border-radius: 4px;
116
- margin: 1rem;
117
- padding: 0;
118
- overflow: hidden;
119
- }
120
-
121
- .plugin-settings-scope-header {
122
- display: flex;
123
- align-items: flex-start;
124
- justify-content: space-between;
125
- gap: 1rem;
126
- padding: 0.75rem 1rem;
127
- border-bottom: 1px solid var(--color-border);
128
- background: var(--color-bg-secondary);
129
- }
130
-
131
- .plugin-settings-scope-header-copy {
132
- min-width: 0;
133
- }
134
-
135
- .plugin-settings-scope-header-toggle {
136
- display: flex;
137
- align-items: center;
138
- gap: 0.75rem;
139
- margin-left: auto;
140
- flex: 0 0 auto;
141
- white-space: nowrap;
142
- }
143
-
144
- .plugin-settings-scope-title {
145
- font-weight: 600;
146
- font-size: var(--font-size-normal);
147
- color: var(--color-text-primary);
148
- }
149
-
150
- .plugin-settings-scope-desc {
151
- font-size: var(--font-size-small);
152
- color: var(--color-text-secondary);
153
- margin-top: 0.25rem;
154
- }
155
-
156
- .plugin-settings-toolbar {
157
- padding: 1rem;
158
- }
159
-
160
- .plugin-settings-toolbar-row {
161
- display: flex;
162
- align-items: center;
163
- gap: clamp(0.75rem, 2vw, 2rem);
164
- flex-wrap: wrap;
165
- }
166
-
167
- .plugin-settings-toolbar-item {
168
- display: flex;
169
- align-items: center;
170
- gap: 0.5rem;
171
- flex: 1 1 0;
172
- min-width: 12rem;
173
- margin: 0;
174
- }
175
-
176
- .plugin-settings-toolbar-item select {
177
- flex: 1 1 auto;
178
- min-width: 0;
179
- }
180
-
181
- .plugin-settings-toolbar-label {
182
- font-weight: 600;
183
- color: var(--color-text-secondary);
184
- white-space: nowrap;
185
- }
186
-
187
- .plugin-settings-toolbar-button {
188
- flex: 0 0 auto;
189
- padding: 0.5rem 0.75rem;
190
- height: 2.5rem;
191
- }
192
-
193
- .plugin-settings-scope-info {
194
- display: flex;
195
- align-items: flex-start;
196
- gap: 0.5rem;
197
- padding: 0 1rem 1rem 1rem;
198
- color: var(--color-text-secondary);
199
- font-size: var(--font-size-small);
200
- }
201
-
202
- @media (max-width: 640px) {
203
- .plugin-settings-scope-header {
204
- flex-direction: column;
205
- align-items: stretch;
206
- }
207
-
208
- .plugin-settings-scope-header-toggle {
209
- margin-left: 0;
210
- justify-content: flex-start;
211
- white-space: normal;
212
- }
213
-
214
- .plugin-settings-toolbar-item {
215
- flex-basis: 100%;
216
- min-width: 0;
217
- }
218
- }
219
-
220
- .plugin-settings-loading {
221
- display: flex;
222
- align-items: center;
223
- justify-content: center;
224
- gap: 0.5rem;
225
- padding: 2rem;
226
- color: var(--color-text-secondary);
227
- }
228
-
229
- .plugin-settings-error {
230
- display: flex;
231
- align-items: center;
232
- gap: 0.5rem;
233
- color: var(--color-error, #e74c3c);
234
- background: var(--color-error-bg, #fdecea);
235
- border-radius: 4px;
236
- padding: 0.5rem 0.75rem;
237
- margin-bottom: 0.75rem;
238
- font-size: var(--font-size-small);
239
- }
240
-
241
- .spinning {
242
- animation: spin 1s linear infinite;
243
- }
244
-
245
- @keyframes spin {
246
- from { transform: rotate(0deg); }
247
- to { transform: rotate(360deg); }
248
- }
249
-
250
- .disabled-appearance {
251
- opacity: 0.6;
252
- }
253
-
254
- .plugin-toggle-status-text {
255
- font-weight: 600;
256
- color: var(--color-text-primary);
257
- min-width: 2rem;
258
- }
259
- </style>
260
-</body>
261
-</html>
webui/components/plugins/toggle/plugin-toggle-store.js
+11
-19
@@ -169,24 +169,6 @@ const model = {
169
}
170
},
171
172
- async openConfigWithScope() {
173
- if (!this.pluginName) return;
174
- this.error = null;
175
- try {
176
- await settingsStore.openConfig(
177
- this.pluginName,
178
- this.projectName || "",
179
- this.agentProfileKey || ""
180
- );
181
- } catch (e) {
182
- this.error = e?.message || "Failed to open plugin config";
183
- }
184
- },
185
-
186
- async openConfigListModal() {
187
- await window.openModal?.("/components/plugins/toggle/plugin-toggles.html");
188
- },
189
-
172
async switchToConfig(projectName, agentProfile) {
173
this.projectName = projectName || "";
174
this.agentProfileKey = agentProfile || "";
@@ -217,8 +199,14 @@ const model = {
199
}
200
},
201
220
- async setEnabled(enabled) {
202
+ async setEnabled(enabled, { projectName = this.projectName, agentProfileKey = this.agentProfileKey } = {}) {
203
if (!this.pluginName || this.alwaysEnabled) return;
204
+ const previousStatus = this.status;
205
+ const previousProjectName = this.projectName;
206
+ const previousAgentProfileKey = this.agentProfileKey;
207
+ this.projectName = projectName || "";
208
+ this.agentProfileKey = agentProfileKey || "";
209
+ this.status = enabled ? 'enabled' : 'disabled';
210
this.isSaving = true;
211
try {
212
const response = await fetchApi("/plugins", {
@@ -237,7 +225,11 @@ const model = {
225
await new Promise(r => setTimeout(r, 100));
226
await this.loadConfigs();
227
} catch (e) {
228
+ this.status = previousStatus;
229
+ this.projectName = previousProjectName;
230
+ this.agentProfileKey = previousAgentProfileKey;
231
this.error = e.message || "Failed to save";
232
+ throw e;
233
} finally {
234
this.isSaving = false;
235
}