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