nub/internal/ui/tui/permission.go
Tom a97013d876 initial commit
- v 0.1.0 siehe CHANGELOG.md
2026-07-25 11:08:01 +02:00

82 lines
2.3 KiB
Go

package tui
import (
"context"
"encoding/json"
"fmt"
"strings"
tea "github.com/charmbracelet/bubbletea"
)
// permissionRequest kommt vom Backend-Loop (E-11: "ask"-Modus) über
// Model.permCh. resp erwartet genau eine Antwort; requestPermission blockiert
// darauf im Loop-Goroutine, bis die TUI eine Taste verarbeitet hat.
type permissionRequest struct {
tool string
input json.RawMessage
resp chan bool
}
type permissionRequestMsg struct{ req permissionRequest }
func listenPermissionRequests(ch <-chan permissionRequest) tea.Cmd {
return func() tea.Msg {
req, ok := <-ch
if !ok {
return nil
}
return permissionRequestMsg{req: req}
}
}
// requestPermission ist der Hook, den loop.RequestPermission aufruft (siehe
// New()). Läuft auf dem Goroutine des Backend-Loops, nicht dem der TUI —
// deshalb reine Channel-Kommunikation statt direktem Feldzugriff auf m.
func (m *Model) requestPermission(ctx context.Context, toolName string, input json.RawMessage) bool {
resp := make(chan bool, 1)
req := permissionRequest{tool: toolName, input: input, resp: resp}
select {
case m.permCh <- req:
case <-ctx.Done():
return false
}
select {
case decision := <-resp:
return decision
case <-ctx.Done():
return false
}
}
func (m *Model) handlePermissionKey(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
req := m.permReq
m.permReq = nil
allow := msg.String() == "y" || msg.String() == "Y"
req.resp <- allow
verb := "denied"
if allow {
verb = "allowed"
}
m.pushCommand(fmt.Sprintf("permission %s: %s", verb, req.tool))
return m, nil
}
// renderPermissionRequest baut den Transkript-Block für eine offene
// Rückfrage — bewusst kein Vollbild-Overlay (das hat vorher den Kontext
// verdeckt, den man für die Entscheidung braucht), sondern ein eigener,
// umrandeter Eintrag im normalen Nachrichtenfluss. Viewport, Eingabefeld und
// Statuszeile bleiben sichtbar; die Statuszeile zeigt zusätzlich einen
// Hinweis (siehe renderStatusLine).
func renderPermissionRequest(toolName string, input json.RawMessage) string {
in := strings.TrimSpace(prettyJSON(input))
if len(in) > 300 {
in = in[:300] + "…"
}
body := fmt.Sprintf("⚠ Rückfrage: %s\n%s\n\n[y] erlauben [n / Esc / beliebige andere Taste] ablehnen", toolName, in)
return stylePermissionBox.Render(body)
}