Files
freemo 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
docs: add v3.7.0 cycle-2 documentation for UKO runtime, TUI permissions, database handler, and thought blocks
- 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
2026-04-02 17:35:41 +00:00

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