feat: add agent profile switcher to chat composer

Surface the active Agent Profile beside the model preset switcher and let users switch profiles through the existing settings flow. - add agent profile metadata to state snapshots - list available profiles in the chat composer profile dropdown - persist profile changes via settings_get/settings_set - add a Create new Agent Profile action that opens a guided a0-create-agent chat - rename the agent-profile creation skill/docs from a0-new-agent to a0-create-agent - clean up fetchApi imports for related WebUI modules

Alessandro committed Apr 21, 2026 at 18:05 UTC 539d80978996bfec6371b32da22653642445126c
10 files changed +385 -94
helpers/state_snapshot.py
+30
@@ -112,6 +112,34 @@ def _coerce_non_negative_int(value: Any, default: int = 0) -> int:
112 return as_int if as_int >= 0 else default
113
114
115 +def _get_agent_profile_labels() -> dict[str, str]:
116 + try:
117 + from helpers import subagents
118 +
119 + return {
120 + str(item.get("key") or ""): str(item.get("label") or item.get("key") or "")
121 + for item in subagents.get_all_agents_list()
122 + if item.get("key")
123 + }
124 + except Exception:
125 + return {}
126 +
127 +
128 +def _apply_agent_profile_metadata(
129 + context_data: dict[str, Any],
130 + ctx: AgentContext,
131 + labels: dict[str, str],
132 +) -> None:
133 + agent_config = getattr(getattr(ctx, "agent0", None), "config", None)
134 + profile = str(
135 + getattr(agent_config, "profile", None)
136 + or getattr(getattr(ctx, "config", None), "profile", "")
137 + or ""
138 + )
139 + context_data["agent_profile"] = profile
140 + context_data["agent_profile_label"] = labels.get(profile, profile) if profile else ""
141 +
142 +
143 def parse_state_request_payload(payload: Mapping[str, Any]) -> StateRequestV1:
144 context = payload.get("context")
145 log_from = payload.get("log_from")
@@ -240,6 +268,7 @@ async def build_snapshot_from_request(*, request: StateRequestV1) -> SnapshotV1:
268 ctxs: list[dict[str, Any]] = []
269 tasks: list[dict[str, Any]] = []
270 processed_contexts: set[str] = set()
271 + agent_profile_labels = _get_agent_profile_labels()
272
273 all_ctxs = AgentContext.all()
274 for ctx in all_ctxs:
@@ -251,6 +280,7 @@ async def build_snapshot_from_request(*, request: StateRequestV1) -> SnapshotV1:
280 continue
281
282 context_data = ctx.output()
283 + _apply_agent_profile_metadata(context_data, ctx, agent_profile_labels)
284
285 context_task = scheduler.get_task_by_uuid(ctx.id)
286 is_task_context = context_task is not None and context_task.context_id == ctx.id
plugins/_model_config/extensions/webui/chat-input-progress-start/model-switcher.html
+219 -85
@@ -5,105 +5,168 @@
5
6 <div x-data>
7 <template x-if="$store.modelConfig">
8 -<div x-data="{ showDropdown: false }"
8 +<div x-data="{ showDropdown: false, showProfileDropdown: false }"
9 x-init="
10 - await $store.modelConfig.refreshSwitcher($store.chats?.selected || '');
10 + await Promise.all([
11 + $store.modelConfig.refreshSwitcher($store.chats?.selected || ''),
12 + $store.modelConfig.loadAgentProfiles(),
13 + ]);
14 $watch('$store.chats.selected', v => $store.modelConfig.refreshSwitcher(v || ''));
15 ">
13 - <template x-if="$store.modelConfig.switcherAllowed && !$store.modelConfig.switcherLoading">
16 + <template x-if="($store.modelConfig.switcherAllowed && !$store.modelConfig.switcherLoading) || $store.chats?.selectedContext?.agent_profile">
17 <div class="model-switcher-container">
15 - <div class="model-switcher-anchor">
16 - <button class="btn-icon-action model-switcher-btn"
17 - :class="{ 'has-override': !!$store.modelConfig.switcherOverride }"
18 - @click="showDropdown = !showDropdown"
19 - @click.outside="showDropdown = false">
20 - <span class="material-symbols-outlined" style="font-size: 16px;">neurology</span>
21 - <span class="model-switcher-label" x-text="$store.modelConfig.getSwitcherLabel()"></span>
22 - <span class="material-symbols-outlined" style="font-size: 0.7rem;"
23 - x-text="showDropdown ? 'expand_less' : 'expand_more'"></span>
24 - </button>
18 + <div class="model-context-strip">
19 + <template x-if="$store.modelConfig.switcherAllowed && !$store.modelConfig.switcherLoading">
20 + <div class="model-switcher-anchor" @click.outside="showDropdown = false">
21 + <button class="btn-icon-action model-switcher-btn"
22 + :class="{ 'has-override': !!$store.modelConfig.switcherOverride }"
23 + @click="showDropdown = !showDropdown; showProfileDropdown = false">
24 + <span class="material-symbols-outlined" style="font-size: 16px;">neurology</span>
25 + <span class="model-switcher-label" x-text="$store.modelConfig.getSwitcherLabel()"></span>
26 + <span class="material-symbols-outlined" style="font-size: 0.7rem;"
27 + x-text="showDropdown ? 'expand_less' : 'expand_more'"></span>
28 + </button>
29
26 - <!-- Inline active model pills (shown when a preset is active) -->
27 - <template x-if="$store.modelConfig.switcherOverride">
28 - <div class="model-switcher-active-pills">
29 - <template x-if="$store.modelConfig.getActiveModels().main">
30 - <div class="model-pill">
31 - <span class="model-pill-role">Main</span>
32 - <span class="model-pill-name" x-text="$store.modelConfig.getActiveModels().main.name"></span>
33 - </div>
34 - </template>
35 - <template x-if="$store.modelConfig.getActiveModels().utility">
36 - <div class="model-pill">
37 - <span class="model-pill-role">Util</span>
38 - <span class="model-pill-name" x-text="$store.modelConfig.getActiveModels().utility.name"></span>
30 + <!-- Inline active model pills (shown when a preset is active) -->
31 + <template x-if="$store.modelConfig.switcherOverride">
32 + <div class="model-switcher-active-pills">
33 + <template x-if="$store.modelConfig.getActiveModels().main">
34 + <div class="model-pill">
35 + <span class="model-pill-role">Main</span>
36 + <span class="model-pill-name" x-text="$store.modelConfig.getActiveModels().main.name"></span>
37 + </div>
38 + </template>
39 + <template x-if="$store.modelConfig.getActiveModels().utility">
40 + <div class="model-pill">
41 + <span class="model-pill-role">Util</span>
42 + <span class="model-pill-name" x-text="$store.modelConfig.getActiveModels().utility.name"></span>
43 + </div>
44 + </template>
45 </div>
46 </template>
41 - </div>
42 - </template>
47
44 - <!-- Dropdown (opens upward) -->
45 - <div class="model-switcher-dropdown" x-show="showDropdown" x-transition.opacity>
46 - <!-- Use Default -->
47 - <template x-if="$store.modelConfig.switcherOverride">
48 - <div class="model-switcher-item revert" @click="
49 - $store.modelConfig.clearOverrideSwitch($store.chats?.selected || '');
50 - showDropdown = false;
51 - ">
52 - <span class="material-symbols-outlined" style="font-size: 15px;">undo</span>
53 - <span>Use Default</span>
54 - </div>
55 - </template>
56 - <div class="model-switcher-divider" x-show="$store.modelConfig.switcherOverride"></div>
48 + <!-- Dropdown (opens upward) -->
49 + <div class="model-switcher-dropdown" x-show="showDropdown" x-transition.opacity style="display: none;">
50 + <!-- Use Default -->
51 + <template x-if="$store.modelConfig.switcherOverride">
52 + <div class="model-switcher-item revert" @click="
53 + $store.modelConfig.clearOverrideSwitch($store.chats?.selected || '');
54 + showDropdown = false;
55 + ">
56 + <span class="material-symbols-outlined" style="font-size: 15px;">undo</span>
57 + <span>Use Default</span>
58 + </div>
59 + </template>
60 + <div class="model-switcher-divider" x-show="$store.modelConfig.switcherOverride"></div>
61
58 - <!-- No presets message -->
59 - <template x-if="$store.modelConfig.switcherPresets.length === 0">
60 - <div class="model-switcher-item disabled">No presets configured</div>
61 - </template>
62 + <!-- No presets message -->
63 + <template x-if="$store.modelConfig.switcherPresets.length === 0">
64 + <div class="model-switcher-item disabled">No presets configured</div>
65 + </template>
66
63 - <!-- Preset list -->
64 - <template x-for="preset in $store.modelConfig.switcherPresets" :key="preset.name">
65 - <div class="model-switcher-item"
66 - :class="{ 'active': $store.modelConfig.switcherOverride?.preset_name === preset.name }"
67 - @click="
68 - $store.modelConfig.selectPresetSwitch($store.chats?.selected || '', preset.name);
69 - showDropdown = false;
70 - ">
71 - <div class="model-switcher-preset-name" x-text="preset.name"></div>
72 - <div class="model-switcher-preset-models">
73 - <template x-if="preset.chat?.name">
74 - <div class="model-switcher-model-row">
75 - <span class="model-switcher-model-label">Main</span>
76 - <span class="model-switcher-model-value">
77 - <span x-text="preset.chat.provider" style="opacity:0.5;"></span>
78 - <span style="opacity:0.3; margin:0 3px;">/</span>
79 - <span x-text="preset.chat.name"></span>
80 - </span>
67 + <!-- Preset list -->
68 + <template x-for="preset in $store.modelConfig.switcherPresets" :key="preset.name">
69 + <div class="model-switcher-item"
70 + :class="{ 'active': $store.modelConfig.switcherOverride?.preset_name === preset.name }"
71 + @click="
72 + $store.modelConfig.selectPresetSwitch($store.chats?.selected || '', preset.name);
73 + showDropdown = false;
74 + ">
75 + <div class="model-switcher-preset-name" x-text="preset.name"></div>
76 + <div class="model-switcher-preset-models">
77 + <template x-if="preset.chat?.name">
78 + <div class="model-switcher-model-row">
79 + <span class="model-switcher-model-label">Main</span>
80 + <span class="model-switcher-model-value">
81 + <span x-text="preset.chat.provider" style="opacity:0.5;"></span>
82 + <span style="opacity:0.3; margin:0 3px;">/</span>
83 + <span x-text="preset.chat.name"></span>
84 + </span>
85 + </div>
86 + </template>
87 + <template x-if="preset.utility?.name">
88 + <div class="model-switcher-model-row">
89 + <span class="model-switcher-model-label">Utility</span>
90 + <span class="model-switcher-model-value">
91 + <span x-text="preset.utility.provider" style="opacity:0.5;"></span>
92 + <span style="opacity:0.3; margin:0 3px;">/</span>
93 + <span x-text="preset.utility.name"></span>
94 + </span>
95 + </div>
96 + </template>
97 </div>
82 - </template>
83 - <template x-if="preset.utility?.name">
84 - <div class="model-switcher-model-row">
85 - <span class="model-switcher-model-label">Utility</span>
86 - <span class="model-switcher-model-value">
87 - <span x-text="preset.utility.provider" style="opacity:0.5;"></span>
88 - <span style="opacity:0.3; margin:0 3px;">/</span>
89 - <span x-text="preset.utility.name"></span>
90 - </span>
91 - </div>
92 - </template>
98 + </div>
99 + </template>
100 +
101 + <!-- Edit Presets shortcut -->
102 + <div class="model-switcher-divider" style="opacity:0.2;"></div>
103 + <div class="model-switcher-item model-switcher-edit" @click="
104 + openModal('/plugins/_model_config/webui/main.html');
105 + showDropdown = false;
106 + ">
107 + <span class="material-symbols-outlined" style="font-size: 14px;">tune</span>
108 + <span>Edit Presets</span>
109 </div>
110 </div>
95 - </template>
111 + </div>
112 + </template>
113 +
114 + <template x-if="$store.chats?.selectedContext?.agent_profile">
115 + <div class="agent-profile-anchor" @click.outside="showProfileDropdown = false">
116 + <button class="btn-icon-action agent-profile-btn"
117 + title="Active agent profile"
118 + @click="showProfileDropdown = !showProfileDropdown; showDropdown = false">
119 + <span class="material-symbols-outlined" style="font-size: 15px;">assignment_ind</span>
120 + <span class="agent-profile-label"
121 + x-text="$store.chats.selectedContext.agent_profile_label || $store.chats.selectedContext.agent_profile"></span>
122 + <span class="material-symbols-outlined" style="font-size: 0.7rem;"
123 + x-text="showProfileDropdown ? 'expand_less' : 'expand_more'"></span>
124 + </button>
125 +
126 + <div class="agent-profile-dropdown" x-show="showProfileDropdown" x-transition.opacity style="display: none;">
127 + <button class="model-switcher-item agent-profile-create" @click="
128 + $store.modelConfig.createAgentProfileChat($store.chats?.selected || '')
129 + .then(ok => { if (ok) showProfileDropdown = false; });
130 + ">
131 + <span class="material-symbols-outlined" style="font-size: 15px;">add</span>
132 + <span>Create new Agent Profile</span>
133 + </button>
134 +
135 + <div class="model-switcher-divider" style="opacity:0.2;"></div>
136
97 - <!-- Edit Presets shortcut -->
98 - <div class="model-switcher-divider" style="opacity:0.2;"></div>
99 - <div class="model-switcher-item model-switcher-edit" @click="
100 - openModal('/plugins/_model_config/webui/main.html');
101 - showDropdown = false;
102 - ">
103 - <span class="material-symbols-outlined" style="font-size: 14px;">tune</span>
104 - <span>Edit Presets</span>
137 + <template x-if="$store.modelConfig.agentProfilesLoading">
138 + <div class="model-switcher-item disabled">Loading profiles...</div>
139 + </template>
140 +
141 + <template x-for="profile in $store.modelConfig.getAgentProfileList($store.chats.selectedContext.agent_profile, $store.chats.selectedContext.agent_profile_label)" :key="profile.key">
142 + <div class="model-switcher-item agent-profile-item"
143 + :class="{
144 + 'active': profile.key === $store.chats.selectedContext.agent_profile,
145 + 'disabled': $store.chats.selectedContext.running || $store.modelConfig.agentProfileSaving
146 + }"
147 + @click="
148 + if (profile.key === $store.chats.selectedContext.agent_profile) {
149 + showProfileDropdown = false;
150 + } else {
151 + $store.modelConfig.selectAgentProfile($store.chats?.selected || '', profile.key)
152 + .then(ok => { if (ok) showProfileDropdown = false; });
153 + }
154 + ">
155 + <div class="model-switcher-preset-name" x-text="profile.label || profile.key"></div>
156 + </div>
157 + </template>
158 +
159 + <div class="model-switcher-divider" style="opacity:0.2;"></div>
160 + <button class="model-switcher-item agent-profile-settings" @click="
161 + openModal('settings/settings.html');
162 + showProfileDropdown = false;
163 + ">
164 + <span class="material-symbols-outlined" style="font-size: 14px;">settings</span>
165 + <span>Agent Config</span>
166 + </button>
167 + </div>
168 </div>
106 - </div>
169 + </template>
170 </div>
171 </div>
172 </template>
@@ -116,6 +179,13 @@
179 position: relative;
180 z-index: 10;
181 }
182 + .model-context-strip {
183 + display: flex;
184 + align-items: center;
185 + gap: 8px;
186 + min-width: 0;
187 + flex-wrap: wrap;
188 + }
189 .model-switcher-anchor {
190 position: relative;
191 display: flex;
@@ -143,6 +213,29 @@
213 overflow: hidden;
214 text-overflow: ellipsis;
215 }
216 + .agent-profile-anchor {
217 + position: relative;
218 + display: flex;
219 + align-items: center;
220 + }
221 + .agent-profile-btn {
222 + width: auto;
223 + height: auto;
224 + gap: 4px;
225 + padding: var(--spacing-xs) var(--spacing-sm);
226 + border: none;
227 + font-size: 0.75rem;
228 + white-space: nowrap;
229 + flex-shrink: 0;
230 + }
231 + .agent-profile-btn:hover {
232 + border: none;
233 + }
234 + .agent-profile-label {
235 + max-width: 140px;
236 + overflow: hidden;
237 + text-overflow: ellipsis;
238 + }
239
240 /* Inline active model pills */
241 .model-switcher-active-pills {
@@ -170,7 +263,7 @@
263 opacity: 0.55;
264 font-size: 0.62rem;
265 text-transform: uppercase;
173 - letter-spacing: 0.03em;
266 + letter-spacing: 0;
267 flex-shrink: 0;
268 }
269 .model-pill-name {
@@ -195,6 +288,21 @@
288 box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
289 z-index: 100;
290 }
291 + .agent-profile-dropdown {
292 + position: absolute;
293 + bottom: calc(100% + 6px);
294 + left: 0;
295 + min-width: 260px;
296 + max-width: min(320px, 90vw);
297 + max-height: 550px;
298 + overflow-y: auto;
299 + background-color: var(--color-panel);
300 + border: 1px solid var(--color-border);
301 + border-radius: 8px;
302 + padding: 6px;
303 + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
304 + z-index: 100;
305 + }
306 .model-switcher-item {
307 padding: 6px 10px;
308 border-radius: 6px;
@@ -263,11 +371,37 @@
371 opacity: 0.6;
372 font-size: 0.75rem;
373 }
374 + .agent-profile-settings {
375 + display: flex;
376 + align-items: center;
377 + gap: 6px;
378 + width: 100%;
379 + border: none;
380 + background: transparent;
381 + color: var(--color-text);
382 + opacity: 0.7;
383 + text-align: left;
384 + font-family: inherit;
385 + }
386 + .agent-profile-create {
387 + display: flex;
388 + align-items: center;
389 + gap: 6px;
390 + width: 100%;
391 + border: none;
392 + background: transparent;
393 + color: var(--color-text);
394 + font-family: inherit;
395 + font-weight: 500;
396 + }
397
398 /* Responsive: hide pills on narrow screens */
399 @media (max-width: 600px) {
400 .model-switcher-active-pills {
401 display: none;
402 }
403 + .agent-profile-label {
404 + max-width: 92px;
405 + }
406 }
407 </style>
plugins/_model_config/webui/api-keys-mixin.js
+2
@@ -1,3 +1,5 @@
1 +import { fetchApi } from "/js/api.js";
2 +
3 const API_BASE = "/plugins/_model_config";
4 const API_KEY_PLACEHOLDER = "************";
5
plugins/_model_config/webui/config.html
+1 -1
@@ -75,7 +75,7 @@
75 margin-top: 4px;
76 }
77 .override-presets-link .text-button {
78 - font-size: 0.78rem;
78 + font-size: 0.78rem !important;
79 gap: 4px;
80 }
81
plugins/_model_config/webui/model-config-store.js
+1
@@ -1,4 +1,5 @@
1 import { createStore } from "/js/AlpineStore.js";
2 +import { fetchApi } from "/js/api.js";
3 import { store as pluginSettingsStore } from "/components/plugins/plugin-settings-store.js";
4 import { apiKeysState, apiKeysMethods } from "/plugins/_model_config/webui/api-keys-mixin.js";
5 import { switcherState, switcherMethods } from "/plugins/_model_config/webui/switcher-mixin.js";
plugins/_model_config/webui/switcher-mixin.js
+127
@@ -1,13 +1,48 @@
1 +import { fetchApi } from "/js/api.js";
2 +
3 const API_BASE = "/plugins/_model_config";
4 +const CREATE_AGENT_PROFILE_PROMPT = `I want to create a new Agent Zero agent profile.
5 +
6 +Use the a0-create-agent skill and guide me through the questionnaire. Start by asking what kind of agent profile I want to create, where it should live, and what behavior/personality/tools it should have. Prefer a normal user profile in /a0/usr/agents unless I choose another scope.`;
7
8 export const switcherState = {
9 switcherAllowed: false,
10 switcherOverride: null,
11 switcherPresets: [],
12 switcherLoading: true,
13 + agentProfiles: [],
14 + agentProfilesLoading: true,
15 + agentProfileSettings: null,
16 + agentProfileSaving: false,
17 };
18
19 export const switcherMethods = {
20 + async loadAgentProfiles(force = false) {
21 + if (!force && this.agentProfiles.length > 0 && this.agentProfileSettings) return this.agentProfiles;
22 + this.agentProfilesLoading = true;
23 + try {
24 + const res = await fetchApi("/settings_get", {
25 + method: "POST",
26 + headers: { "Content-Type": "application/json" },
27 + body: JSON.stringify({}),
28 + });
29 + const data = await res.json();
30 + this.agentProfileSettings = data.settings || {};
31 + this.agentProfiles = (data.additional?.agent_subdirs || [])
32 + .map(profile => ({
33 + key: profile.value || profile.key || "",
34 + label: profile.label || profile.value || profile.key || "",
35 + }))
36 + .filter(profile => profile.key && profile.key !== "_example");
37 + } catch (e) {
38 + console.error("Agent profile list load failed:", e);
39 + this.agentProfiles = [];
40 + } finally {
41 + this.agentProfilesLoading = false;
42 + }
43 + return this.agentProfiles;
44 + },
45 +
46 async loadSwitcherState(contextId) {
47 const result = { allowed: false, presets: [], override: null };
48 try {
@@ -57,6 +92,98 @@ export const switcherMethods = {
92 }
93 },
94
95 + getAgentProfileList(activeKey = "", activeLabel = "") {
96 + const profiles = [...(this.agentProfiles || [])];
97 + if (activeKey && !profiles.some(profile => profile.key === activeKey)) {
98 + profiles.unshift({ key: activeKey, label: activeLabel || activeKey });
99 + }
100 + return profiles;
101 + },
102 +
103 + async createAgentProfileChat(currentContextId = "") {
104 + try {
105 + const res = await fetchApi("/chat_create", {
106 + method: "POST",
107 + headers: { "Content-Type": "application/json" },
108 + body: JSON.stringify({ current_context: currentContextId || "" }),
109 + });
110 + const data = await res.json();
111 + if (!data.ok || !data.ctxid) return false;
112 +
113 + const chatsStore = window.Alpine?.store("chats");
114 + if (chatsStore?.selectChat) {
115 + await chatsStore.selectChat(data.ctxid);
116 + } else {
117 + window.setContext?.(data.ctxid);
118 + }
119 +
120 + const chatInputStore = window.Alpine?.store("chatInput");
121 + if (chatInputStore) {
122 + chatInputStore.message = CREATE_AGENT_PROFILE_PROMPT;
123 + setTimeout(() => {
124 + chatInputStore.adjustTextareaHeight?.();
125 + chatInputStore.focus?.();
126 + }, 0);
127 + }
128 +
129 + return true;
130 + } catch (e) {
131 + console.error("Failed to create agent profile chat:", e);
132 + window.toastFetchError?.("Failed to start profile creator", e);
133 + return false;
134 + }
135 + },
136 +
137 + async selectAgentProfile(contextId, agentProfile) {
138 + if (!contextId || !agentProfile) return false;
139 + if (this.agentProfileSaving) return false;
140 + const chatsStore = window.Alpine?.store("chats");
141 + const selectedContext = chatsStore?.selectedContext;
142 + if (selectedContext?.running) {
143 + window.justToast?.("Agent profile can be changed after the current run finishes.", "warning", 2500, "agent-profile-switch");
144 + return false;
145 + }
146 +
147 + const activeProfile = selectedContext?.agent_profile || this.agentProfileSettings?.agent_profile || "";
148 + if (activeProfile === agentProfile) return true;
149 +
150 + this.agentProfileSaving = true;
151 + try {
152 + await this.loadAgentProfiles();
153 + const settings = { ...(this.agentProfileSettings || {}), agent_profile: agentProfile };
154 + const res = await fetchApi("/settings_set", {
155 + method: "POST",
156 + headers: { "Content-Type": "application/json" },
157 + body: JSON.stringify({ settings }),
158 + });
159 + const data = await res.json();
160 + if (!data.settings) return false;
161 +
162 + this.agentProfileSettings = data.settings;
163 + this.agentProfiles = (data.additional?.agent_subdirs || this.agentProfiles)
164 + .map(profile => ({
165 + key: profile.value || profile.key || "",
166 + label: profile.label || profile.value || profile.key || "",
167 + }))
168 + .filter(profile => profile.key && profile.key !== "_example");
169 +
170 + const label = this.agentProfiles.find(profile => profile.key === agentProfile)?.label || agentProfile;
171 + if (selectedContext) {
172 + selectedContext.agent_profile = agentProfile;
173 + selectedContext.agent_profile_label = label;
174 + }
175 + document.dispatchEvent(new CustomEvent("settings-updated", { detail: this.agentProfileSettings }));
176 + window.justToast?.(`Agent profile: ${label}`, "success", 1600, "agent-profile-switch");
177 + return true;
178 + } catch (e) {
179 + console.error("Failed to set active agent profile:", e);
180 + window.toastFetchError?.("Failed to set agent profile", e);
181 + return false;
182 + } finally {
183 + this.agentProfileSaving = false;
184 + }
185 + },
186 +
187 getPresetLabel(preset) {
188 return preset?.name || "Unnamed";
189 },
skills/a0-create-agent/SKILL.md renamed
+2 -4
@@ -1,15 +1,13 @@
1 ---
2 -name: a0-new-agent
2 +name: a0-create-agent
3 description: Create a new Agent Zero agent profile (subordinate). Covers where profiles live (user / plugin-distributed / project-scoped), the agent.yaml schema, the prompt inheritance & override model, and optional profile-specific tools and extensions. Use for any "create/add/new agent profile" request.
4 version: 1.0.0
5 tags: ["agents", "profile", "create", "new", "subordinate"]
6 trigger_patterns:
7 - - "create agent profile"
7 + - "create agent"
8 - "new agent profile"
9 - "add agent profile"
10 - "make agent profile"
11 - - "new subordinate agent"
12 - - "create subordinate"
11 - "agent profile template"
12 - "build agent profile"
13 ---
skills/a0-development/SKILL.md
+1 -1
@@ -455,7 +455,7 @@ class MyEndpoint(ApiHandler):
455
456 Agent profiles define specialized subordinates with custom prompts and behaviors.
457
458 -> For a guided, step-by-step wizard (scope selection, `agent.yaml` schema, prompt overrides, tool/extension stubs, test checklist) use the dedicated `/a0/skills/a0-new-agent/SKILL.md` skill.
458 +> For a guided, step-by-step wizard (scope selection, `agent.yaml` schema, prompt overrides, tool/extension stubs, test checklist) use the dedicated `/a0/skills/a0-create-agent/SKILL.md` skill.
459
460 ### Profile Directory Structure
461
webui/components/plugins/plugin-settings-store.js
+1 -1
@@ -1,9 +1,9 @@
1 import { createStore } from "/js/AlpineStore.js";
2 import * as api from "/js/api.js";
3 +import { fetchApi } from "/js/api.js";
4 import { showConfirmDialog } from "/js/confirmDialog.js";
5 import { store as pluginToggleStore } from "/components/plugins/toggle/plugin-toggle-store.js";
6
6 -const fetchApi = globalThis.fetchApi;
7 const justToast = globalThis.justToast;
8
9 const model = {
webui/components/plugins/toggle/plugin-toggle-store.js
+1 -2
@@ -1,8 +1,7 @@
1 import { createStore } from "/js/AlpineStore.js";
2 +import { fetchApi } from "/js/api.js";
3 import { store as settingsStore } from "/components/plugins/plugin-settings-store.js";
4
4 -const fetchApi = globalThis.fetchApi;
5 -
5 const model = {
6 pluginName: null,
7