Agent Zero AI framework
Hide the utility agent from profile selectors
Centralize omission of the internal default profile in the shared presentation catalog and keep status reporting intact for existing chats. Align Settings, model-switcher, Connector, Telegram, and selector contracts with focused regressions.
Alessandro committed
Aug 11, 2026 at 14:22 UTC
9dab4f2460cf4e1ead2188430c3ed8fc4aed478c
12 files changed
+71
-14
api/agents.py.dox.md
+2
@@ -20,6 +20,8 @@
20
- Update this file whenever request payloads, authentication or CSRF requirements, response shapes, route side effects, or WebSocket event contracts change.
21
- `Agents` is an `ApiHandler`.
22
- `Agents` defines `process(...)`.
23
+- The response uses the shared `subagents.get_all_agents_list()` presentation
24
+ catalog; profile visibility rules belong to that helper.
25
- Imported dependency areas include: `helpers`, `helpers.api`.
26
27
## Key Concepts
helpers/integration_commands.py.dox.md
+3
-1
@@ -20,7 +20,9 @@
20
- Update this file whenever public functions, classes, persistence behavior, path/security assumptions, side effects, or cross-module contracts change.
21
- Observed side-effect areas: filesystem writes, model calls, plugin state, settings/state persistence.
22
- Imported dependency areas include: `__future__`, `helpers`, `helpers.persist_chat`, `helpers.state_monitor_integration`, `plugins._model_config.helpers`, `re`, `typing`.
23
-- `/agent` switches the top-level chat profile and preserves existing subordinate agent profiles.
23
+- `/agent` switches the top-level chat profile and preserves existing
24
+ subordinate agent profiles. Choices come from the shared presentation
25
+ catalog, though status may report an existing chat using the utility profile.
26
- `/model <preset>` stores a per-chat global preset reference, `/model inherit` clears it, and status always reports the effective scoped-or-chat preset. `Default` is a real selectable preset, not an alias for clearing the chat selection.
27
28
## Key Concepts
helpers/settings.py
+2
-1
@@ -263,6 +263,7 @@ def convert_out(settings: Settings) -> SettingsOutput:
263
for key, item in sorted(
264
subagents.get_available_agents_dict(None).items()
265
)
266
+ if key != "default"
267
],
268
knowledge_subdirs=[{"value": subdir, "label": subdir}
269
for subdir in files.get_subdirectories("knowledge", exclude="default")],
@@ -288,7 +289,7 @@ def convert_out(settings: Settings) -> SettingsOutput:
289
}
290
291
current_profile = current.get("agent_profile")
291
- if current_profile and not any(
292
+ if current_profile and current_profile != "default" and not any(
293
option["value"] == current_profile
294
for option in additional["agent_subdirs"]
295
):
helpers/settings.py.dox.md
+2
-1
@@ -69,7 +69,8 @@
69
- `ui_control_visibility` stores validated mobile and desktop visibility flags for the project selector, clock, connection status, and right canvas rail; missing or malformed values fall back per device.
70
- The Global default-profile selector lists only globally available profiles.
71
A currently configured unavailable profile remains visible with an explicit
72
- unavailable label so settings can round-trip it truthfully.
72
+ unavailable label so settings can round-trip it truthfully, except that the
73
+ exact `default` utility profile is never offered as a selectable option.
74
- Keep request/response, tool, or helper semantics documented here at the same time as source changes.
75
76
## Work Guidance
helpers/subagents.py
+5
-5
@@ -300,11 +300,11 @@ def get_all_agents_list() -> list[dict[str, str]]:
300
else:
301
merged[name] = item
302
303
- result: list[dict[str, str]] = []
304
- for key in sorted(merged.keys()):
305
- item = merged[key]
306
- result.append({"key": key, "label": item.title or key})
307
- return result
303
+ return [
304
+ {"key": key, "label": item.title or key}
305
+ for key, item in sorted(merged.items())
306
+ if key != "default"
307
+ ]
308
309
310
def get_default_promp_file_names() -> list[str]:
helpers/subagents.py.dox.md
+2
@@ -51,6 +51,8 @@
51
title fallbacks do not become authored overrides.
52
- Available-profile resolution includes definitions from the selected project,
53
then applies that project's sparse `agents.json` availability overrides.
54
+- `get_all_agents_list()` is the shared presentation catalog and omits the exact
55
+ `default` utility profile. Runtime discovery and loading remain unchanged.
56
- Bundled directories require an authored profile definition; the `_example`
57
reference directory is never selectable. Every real profile, including
58
`Default`, follows the same Global and project availability rules.
plugins/_a0_connector/AGENTS.md
+2
@@ -56,6 +56,8 @@
56
- Model preset definitions exposed through v1 are global; project arguments select scope but never create project-owned definitions. Model switcher state reports the effective main, utility, and embedding models and preserves embedding-change notifications.
57
- The protected v1 `agent_editor` route delegates to the bundled Agent Editor
58
API and must not define another profile schema or write profile files itself.
59
+- The protected v1 `agents_list` response uses the shared agent presentation
60
+ catalog rather than applying connector-specific visibility rules.
61
- Computer Use receipts describe transport success unless the connector returns explicit effect evidence. Linux target-bound typing requires a verified active/focused `window_id`; window activation uses focus, never a press action on an application or window node. Do not retry an identical failed Computer Use call.
62
63
## Work Guidance
plugins/_model_config/AGENTS.md
+5
-1
@@ -28,7 +28,11 @@
28
- `modelConfig.createPresetEditor()` owns local preset drafts, row actions, and stable UI-only row keys so deletion or renaming cannot rebind nested model fields.
29
- The preset editor maps each model provider's API-key field to the shared API-key store; saving the editor persists dirty keys separately and never writes secrets into preset YAML.
30
- The compact chat selector label combines the effective preset with only the leaf name of its main model; utility and provider text stay out of the closed selector.
31
-- The adjacent agent-profile selector reads the always-enabled Agent Editor list endpoint directly so the active profile shows its effective title and avatar, and omits profiles disabled in the chat's current scope.
31
+- The adjacent agent-profile selector reads the always-enabled Agent Editor list
32
+ endpoint directly so the active profile shows its effective title and avatar,
33
+ and omits profiles disabled in the chat's current scope plus the exact
34
+ `default` utility profile. A chat already using `default` may still show that
35
+ current status without adding a selectable or editable row.
36
- Reload the agent-profile selector catalog when a chat changes project or
37
active profile so project-only profiles never linger in the visible choices.
38
- When forced agent-profile catalog loads overlap, only the newest request may
plugins/_model_config/webui/switcher-mixin.js
+2
-2
@@ -51,7 +51,7 @@ export const switcherMethods = {
51
});
52
if (requestSeq !== this.agentProfilesLoadSeq) return this.agentProfiles;
53
this.agentProfiles = (data.profiles || [])
54
- .filter(profile => profile.id && profile.id !== "_example" && profile.enabled !== false)
54
+ .filter(profile => profile.id && !["_example", "default"].includes(profile.id) && profile.enabled !== false)
55
.map(profile => ({
56
key: profile.id,
57
label: profile.title || profile.id,
@@ -127,7 +127,7 @@ export const switcherMethods = {
127
128
getAgentProfileList(activeKey = "", activeLabel = "") {
129
const profiles = [...(this.agentProfiles || [])];
130
- if (activeKey && !profiles.some(profile => profile.key === activeKey)) {
130
+ if (activeKey && activeKey !== "default" && !profiles.some(profile => profile.key === activeKey)) {
131
profiles.unshift({ key: activeKey, label: activeLabel || activeKey });
132
}
133
return profiles;
plugins/_telegram_integration/AGENTS.md
+4
-1
@@ -16,7 +16,10 @@
16
- Treat bot tokens, chat IDs, attachments, and user data as sensitive.
17
- Keep allowed-user, group-mode, project, model, and `/send` controls enforced.
18
- Install Telegram dependencies into the framework runtime only when required.
19
-- Agent profile picker actions change the top-level chat profile and must preserve existing subordinate agent profiles.
19
+- Agent profile picker actions change the top-level chat profile and must
20
+ preserve existing subordinate agent profiles. Picker rows and direct matches
21
+ use the shared presentation catalog while current status may still report an
22
+ existing chat that uses the utility profile.
23
- Model picker status shows the effective preset; clearing a chat override returns to its scoped preset rather than assuming `Default`.
24
25
## Work Guidance
tests/test_agent_editor.py
+30
-2
@@ -1,5 +1,6 @@
1
from __future__ import annotations
2
3
+import asyncio
4
from io import BytesIO
5
import json
6
from pathlib import Path
@@ -1082,7 +1083,10 @@ def test_settings_default_profile_catalog_uses_global_availability(
1083
lambda _project: {
1084
"default": settings.subagents.SubAgentListItem(
1085
name="default", title="Default"
1085
- )
1086
+ ),
1087
+ "agent0": settings.subagents.SubAgentListItem(
1088
+ name="agent0", title="Agent 0"
1089
+ ),
1090
},
1091
)
1092
configured = settings.get_default_settings().copy()
@@ -1091,7 +1095,7 @@ def test_settings_default_profile_catalog_uses_global_availability(
1095
options = settings.convert_out(configured)["additional"]["agent_subdirs"]
1096
1097
assert options == [
1094
- {"value": "default", "label": "Default"},
1098
+ {"value": "agent0", "label": "Agent 0"},
1099
{
1100
"value": "disabled-profile",
1101
"label": "disabled-profile (unavailable)",
@@ -1099,6 +1103,30 @@ def test_settings_default_profile_catalog_uses_global_availability(
1103
]
1104
1105
1106
+def test_shared_profile_catalog_drives_generic_and_web_selectors(
1107
+ monkeypatch: pytest.MonkeyPatch,
1108
+) -> None:
1109
+ from api import agents
1110
+ from helpers import integration_commands, subagents
1111
+
1112
+ profiles = [{"key": "agent0", "label": "Agent 0"}]
1113
+ monkeypatch.setattr(subagents, "get_all_agents_list", lambda: profiles)
1114
+
1115
+ handler = agents.Agents.__new__(agents.Agents)
1116
+ response = asyncio.run(handler.process({"action": "list"}, None))
1117
+ assert [item["key"] for item in response["data"]] == ["agent0"]
1118
+
1119
+ context = SimpleNamespace(
1120
+ agent0=SimpleNamespace(config=SimpleNamespace(profile="default")),
1121
+ is_running=lambda: False,
1122
+ )
1123
+ status = integration_commands._handle_agent(context, "")
1124
+ assert "Current agent: default" in status
1125
+ assert "Agent 0 (agent0)" in status
1126
+ assert "Default (default)" not in status
1127
+ assert "was not found" in integration_commands._handle_agent(context, "default")
1128
+
1129
+
1130
@pytest.mark.parametrize(
1131
("relative_path", "patch", "label"),
1132
(
tests/test_subagent_metadata_merge.py
+12
@@ -132,3 +132,15 @@ def test_available_agents_include_project_profiles_and_project_overrides(
132
133
assert requested == ["demo"]
134
assert list(available) == ["project-only"]
135
+
136
+
137
+def test_all_agents_list_omits_default_utility_profile(
138
+ tmp_path: Path, monkeypatch
139
+) -> None:
140
+ root = tmp_path / "agents"
141
+ _write_profile(root, "default", "title: Default\n")
142
+ _write_profile(root, "agent0", "title: Agent 0\n")
143
+ monkeypatch.setattr(subagents, "get_agents_roots", lambda: [str(root)])
144
+
145
+ assert "default" in subagents._get_agents_list_from_dir(str(root), "default")
146
+ assert subagents.get_all_agents_list() == [{"key": "agent0", "label": "Agent 0"}]