0d4c5b6ff1
CI / lint (push) Failing after 24s
CI / build (push) Successful in 14s
CI / helm (push) Successful in 25s
CI / typecheck (push) Successful in 49s
CI / coverage (push) Has been skipped
CI / benchmark-regression (push) Has been skipped
CI / unit_tests (push) Failing after 1m45s
CI / integration_tests (push) Has been cancelled
CI / security (push) Has been cancelled
CI / quality (push) Has been cancelled
CI / e2e_tests (push) Has been cancelled
CI / benchmark-publish (push) Has been cancelled
CI / status-check (push) Has been cancelled
CI / docker (push) Has been cancelled
- CHANGELOG.md: extend [3.7.0] section with 15 new entries covering PermissionsScreen, ThoughtBlock, UKO runtime services, ACMS Phase 2 protocol aliases, DevcontainerHandler protocol completion, DatabaseResourceHandler CRUD/checkpoint, estimation lifecycle hook, user_identity domain events, PLAN_APPLIED/PLAN_CANCELLED enrichment, server serve subcommand, async audit refactor, and CLI flag fixes - README.md: extend Highlights with permissions screen, thought blocks, UKO runtime, database handler, and estimation lifecycle - docs/reference/uko_runtime.md: new reference for UKOQueryInterface, UKOInferenceEngine, and UKOGraphPersistence (issue #891) - docs/reference/tui_permissions.md: new reference for PermissionsScreen, PermissionRequestService, and all domain models (issue #996) - docs/reference/tui_thought_block.md: new reference for ThoughtBlock domain model and ThoughtBlockWidget (issue #1001) - docs/reference/database_handler.md: new reference for DatabaseResourceHandler CRUD and checkpoint methods (issue #1241) - docs/reference/devcontainer_resources.md: add DevcontainerHandler protocol methods section (delete, list_children, diff, create_sandbox) (issue #1242) - docs/reference/tui.md: extend architecture table and add Permissions Screen and Actor Thought Blocks sections ISSUES CLOSED: #1393
230 lines
8.4 KiB
Markdown
230 lines
8.4 KiB
Markdown
# TUI — Permissions Screen
|
|
|
|
The Permissions Screen is a full-screen TUI overlay that presents tool
|
|
permission requests to the user before a tool is allowed to modify resources.
|
|
It displays a file list on the left and a diff view on the right, and records
|
|
the user's decision for the current session.
|
|
|
|
Introduced in v3.7.0 (issue #996). See also [`tui.md`](tui.md) for the
|
|
overall TUI architecture.
|
|
|
|
**Module**: `cleveragents.tui.permissions`
|
|
|
|
---
|
|
|
|
## Overview
|
|
|
|
When an actor requests a write operation on a resource, the TUI raises the
|
|
`PermissionsScreen` overlay. The user reviews the proposed changes and
|
|
decides whether to allow or reject the operation.
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────────────┐
|
|
│ Tool: local/file-write Resource: local/api-service │
|
|
├──────────────────────┬──────────────────────────────────────────┤
|
|
│ Files │ Diff view │
|
|
│ ───── │ ───────── │
|
|
│ M src/api/main.py │ --- a/src/api/main.py │
|
|
│ A src/api/utils.py │ +++ b/src/api/main.py │
|
|
│ │ @@ -10,6 +10,8 @@ │
|
|
│ │ def run(): │
|
|
│ │ + logger.info("starting") │
|
|
│ │ + setup_tracing() │
|
|
│ │ app.start() │
|
|
├──────────────────────┴──────────────────────────────────────────┤
|
|
│ [a] Allow once [A] Allow always [r] Reject once [R] Reject │
|
|
│ [d] Toggle diff mode (unified / side-by-side / context) │
|
|
└─────────────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
---
|
|
|
|
## Keyboard Bindings
|
|
|
|
| Key | Action |
|
|
|-----|--------|
|
|
| `a` | Allow this request once |
|
|
| `A` | Allow all requests from this tool for the session |
|
|
| `r` | Reject this request once |
|
|
| `R` | Reject all requests from this tool for the session |
|
|
| `d` | Cycle diff display mode (unified → side-by-side → context) |
|
|
| `↑` / `↓` | Navigate the file list |
|
|
|
|
---
|
|
|
|
## Domain Models
|
|
|
|
### `DiffDisplayMode`
|
|
|
|
`StrEnum` controlling how diffs are rendered.
|
|
|
|
| Value | Description |
|
|
|-------|-------------|
|
|
| `unified` | Standard unified diff with `+`/`-` lines (default) |
|
|
| `side_by_side` | Two-column view — old content left, new content right |
|
|
| `context` | Shows only changed lines with 3 lines of surrounding context |
|
|
|
|
### `FileChangeType`
|
|
|
|
`StrEnum` for the type of file change.
|
|
|
|
| Value | Description |
|
|
|-------|-------------|
|
|
| `M` | File was modified |
|
|
| `A` | File was added |
|
|
| `D` | File was deleted |
|
|
|
|
### `PermissionDecision`
|
|
|
|
`StrEnum` for the user's decision.
|
|
|
|
| Value | Description |
|
|
|-------|-------------|
|
|
| `allow_once` | Allow this operation once |
|
|
| `allow_always` | Allow all operations from this tool for the session |
|
|
| `reject_once` | Reject this operation once |
|
|
| `reject_always` | Reject all operations from this tool for the session |
|
|
|
|
### `PermissionRequest`
|
|
|
|
Pydantic model representing a single file change within a tool permission
|
|
request.
|
|
|
|
| Field | Type | Description |
|
|
|-------|------|-------------|
|
|
| `path` | `str` | File path being changed (non-empty) |
|
|
| `change_type` | `FileChangeType` | Type of change (M/A/D) |
|
|
| `before_content` | `str \| None` | Content before the change (`None` for new files) |
|
|
| `after_content` | `str \| None` | Content after the change (`None` for deleted files) |
|
|
|
|
**Diff methods:**
|
|
|
|
| Method | Returns | Description |
|
|
|--------|---------|-------------|
|
|
| `unified_diff(*, context_lines=3)` | `str` | Standard unified diff |
|
|
| `side_by_side_diff()` | `tuple[list[str], list[str]]` | `(left_lines, right_lines)` |
|
|
| `context_diff(*, context_lines=3)` | `str` | Context diff |
|
|
| `render_diff(mode, *, context_lines=3)` | `str` | Render in the specified mode |
|
|
|
|
### `ToolPermissionRequest`
|
|
|
|
Pydantic model for a complete tool permission request with multiple file
|
|
changes.
|
|
|
|
| Field | Type | Description |
|
|
|-------|------|-------------|
|
|
| `request_id` | `str` | Unique identifier for this request |
|
|
| `tool_name` | `str` | Name of the tool requesting permission |
|
|
| `resource_name` | `str` | Name of the resource being modified |
|
|
| `changes` | `list[PermissionRequest]` | File changes in this request |
|
|
| `decision` | `PermissionDecision \| None` | User decision (`None` if pending) |
|
|
|
|
**Properties:**
|
|
|
|
| Property | Type | Description |
|
|
|----------|------|-------------|
|
|
| `is_pending` | `bool` | `True` if no decision has been made |
|
|
| `is_allowed` | `bool` | `True` if the request was allowed |
|
|
| `is_rejected` | `bool` | `True` if the request was rejected |
|
|
|
|
**Methods:**
|
|
|
|
| Method | Returns | Description |
|
|
|--------|---------|-------------|
|
|
| `apply_decision(decision)` | `ToolPermissionRequest` | Returns a new request with the decision applied |
|
|
|
|
---
|
|
|
|
## PermissionRequestService
|
|
|
|
**Module**: `cleveragents.tui.permissions.service`
|
|
|
|
In-memory service that manages the permission request queue and persists
|
|
session-scoped decisions (`allow_always` / `reject_always`).
|
|
|
|
### Methods
|
|
|
|
| Method | Returns | Description |
|
|
|--------|---------|-------------|
|
|
| `enqueue(request)` | `None` | Add a request to the queue; auto-resolves if a session decision exists |
|
|
| `get(request_id)` | `ToolPermissionRequest \| None` | Return a request by ID |
|
|
| `pending_requests()` | `list[ToolPermissionRequest]` | All undecided requests |
|
|
| `all_requests()` | `list[ToolPermissionRequest]` | All requests (pending and decided) |
|
|
| `record_decision(request_id, decision)` | `ToolPermissionRequest \| None` | Apply a decision; records session-scoped decisions for `allow_always` / `reject_always` |
|
|
| `get_session_decision(tool_name)` | `PermissionDecision \| None` | Return the session-scoped decision for a tool |
|
|
| `clear_session_decision(tool_name)` | `bool` | Remove a session-scoped decision |
|
|
| `clear_all()` | `None` | Clear all requests and session decisions |
|
|
|
|
### Session-Scoped Decisions
|
|
|
|
When the user presses `A` (allow-always) or `R` (reject-always), the
|
|
`PermissionRequestService` records the decision against the tool name.
|
|
Subsequent requests from the same tool are auto-resolved immediately when
|
|
`enqueue()` is called, without showing the `PermissionsScreen`.
|
|
|
|
---
|
|
|
|
## PermissionsScreen Widget
|
|
|
|
**Module**: `cleveragents.tui.permissions.screen`
|
|
|
|
The `PermissionsScreen` class is a Textual `Screen` subclass that renders
|
|
the split-pane layout. It is instantiated by the main TUI app when a
|
|
`ToolPermissionRequest` arrives.
|
|
|
|
### Diff Mode Cycling
|
|
|
|
The diff display mode cycles through `unified → side_by_side → context`
|
|
each time the user presses `d`. The current mode is displayed in the
|
|
status bar.
|
|
|
|
---
|
|
|
|
## Usage Example
|
|
|
|
```python
|
|
from cleveragents.tui.permissions.models import (
|
|
DiffDisplayMode,
|
|
FileChangeType,
|
|
PermissionDecision,
|
|
PermissionRequest,
|
|
ToolPermissionRequest,
|
|
)
|
|
from cleveragents.tui.permissions.service import PermissionRequestService
|
|
|
|
service = PermissionRequestService()
|
|
|
|
# Build a permission request
|
|
request = ToolPermissionRequest(
|
|
request_id="req-001",
|
|
tool_name="local/file-write",
|
|
resource_name="local/api-service",
|
|
changes=[
|
|
PermissionRequest(
|
|
path="src/api/main.py",
|
|
change_type=FileChangeType.MODIFIED,
|
|
before_content="def run():\n app.start()\n",
|
|
after_content="def run():\n logger.info('starting')\n app.start()\n",
|
|
)
|
|
],
|
|
)
|
|
|
|
# Enqueue the request
|
|
service.enqueue(request)
|
|
|
|
# Render the diff
|
|
change = request.changes[0]
|
|
print(change.render_diff(DiffDisplayMode.UNIFIED))
|
|
|
|
# Record a decision
|
|
service.record_decision("req-001", PermissionDecision.ALLOW_ONCE)
|
|
```
|
|
|
|
---
|
|
|
|
## Related Documentation
|
|
|
|
- [TUI Reference](tui.md) — overall TUI architecture and key bindings
|
|
- [Permissions](permissions.md) — permission system and role bindings
|
|
- [Safety Profiles](safety_profiles.md) — automation profile safety levels
|