Open agent permissions from slash commands

Register /permissions in the commands plugin and route WebUI invocations to the current profile's Agent Editor. Reject the internal utility profile and cover discovery plus emitted editor payloads.

Alessandro committed Aug 11, 2026 at 17:33 UTC a4f7a1352a5e0825db0ea89da3f2f9cbc92fc51e
5 files changed +86 -1
plugins/_commands/AGENTS.md
+2
@@ -35,6 +35,8 @@
35 - `/stop` uses the same shared cancellation operation as the composer Stop button, including progress cleanup and terminal logging.
36 - `/profile` opens Manage agents without arguments, keeps existing profile
37 selection, and creates through Agent Editor when given a name and instructions.
38 +- `/permissions` opens Edit agent for the current WebUI profile; the A0 Connector
39 + owns its separate Textual permissions screen for the same command name.
40 - Built-in `/computer-use on|off` emits a bounded `computer_use` effect. WebUI
41 only directs the user to Host access in A0 Launcher or the same command in A0
42 CLI; it never changes a Launcher gateway lease from Agent Zero page content.
plugins/_commands/commands/connector_commands.py
+25
@@ -35,6 +35,8 @@ def run(payload: dict[str, Any]) -> dict[str, Any]:
35 return _handle_project(context, raw_args)
36 if command == "profile":
37 return _handle_profile(context, raw_args, arguments)
38 + if command == "permissions":
39 + return _handle_permissions(context)
40 if command == "plugins":
41 return _effects({"type": "open_modal", "path": "/components/plugins/list/plugin-list.html"})
42 if command == "compact":
@@ -152,6 +154,29 @@ def _handle_profile(
154 )
155
156
157 +def _handle_permissions(context: AgentContext | None) -> dict[str, Any]:
158 + error = _require_context(context)
159 + if error:
160 + return _effects(_toast(error, level="error"))
161 + profile_id = str(getattr(context.config, "profile", "") or "").strip()
162 + if not profile_id:
163 + return _effects(_toast("The current agent profile is unavailable.", level="error"))
164 + if profile_id == "default":
165 + return _effects(
166 + _toast(
167 + "The Default utility profile has no editable permissions.",
168 + level="error",
169 + )
170 + )
171 + return _effects(
172 + {
173 + "type": "open_agent_editor",
174 + "view": "edit",
175 + "profile_id": profile_id,
176 + }
177 + )
178 +
179 +
180 def _handle_models(context: AgentContext | None, raw_args: str) -> dict[str, Any]:
181 return _effects({"type": "open_plugin_config", "plugin": "_model_config"})
182
plugins/_commands/commands/permissions.command.yaml new
+4
@@ -0,0 +1,4 @@
1 +name: permissions
2 +description: Edit permissions for the current agent profile.
3 +type: script
4 +script_path: connector_commands.py
plugins/_commands/tests/test_commands_plugin.py
+46
@@ -310,6 +310,52 @@ def test_profile_command_opens_agent_manager() -> None:
310 }
311
312
313 +def test_permissions_command_edits_the_current_agent(
314 + monkeypatch: pytest.MonkeyPatch,
315 +) -> None:
316 + context = SimpleNamespace(config=SimpleNamespace(profile="developer"))
317 + monkeypatch.setattr(connector_commands, "_context", lambda _context_id: context)
318 + result = connector_commands.run(
319 + {
320 + "invocation": {"command_name": "permissions", "raw_arguments": ""},
321 + "context": {"context_id": "ctx-1"},
322 + }
323 + )
324 +
325 + assert result == {
326 + "text": "",
327 + "effects": [
328 + {
329 + "type": "open_agent_editor",
330 + "view": "edit",
331 + "profile_id": "developer",
332 + }
333 + ],
334 + }
335 +
336 +
337 +def test_permissions_command_refuses_the_internal_default(
338 + monkeypatch: pytest.MonkeyPatch,
339 +) -> None:
340 + context = SimpleNamespace(config=SimpleNamespace(profile="default"))
341 + monkeypatch.setattr(connector_commands, "_context", lambda _context_id: context)
342 +
343 + result = connector_commands.run(
344 + {
345 + "invocation": {"command_name": "permissions", "raw_arguments": ""},
346 + "context": {"context_id": "ctx-1"},
347 + }
348 + )
349 +
350 + assert result["effects"] == [
351 + {
352 + "type": "toast",
353 + "message": "The Default utility profile has no editable permissions.",
354 + "level": "error",
355 + }
356 + ]
357 +
358 +
359 def test_profile_command_quick_creates_with_the_agent_editor(
360 monkeypatch: pytest.MonkeyPatch,
361 ) -> None:
plugins/_commands/tests/test_plugin_command_discovery.py
+9 -1
@@ -155,7 +155,15 @@ def test_builtin_commands_use_canonical_names_only():
155 discovered = commands_helper._discover_builtin_commands()
156 names = {command["name"] for command in discovered}
157
158 - assert {"attach", "computer-use", "models", "plugins", "project", "stop"} <= names
158 + assert {
159 + "attach",
160 + "computer-use",
161 + "models",
162 + "permissions",
163 + "plugins",
164 + "project",
165 + "stop",
166 + } <= names
167 assert {
168 "computer",
169 "cu",