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",