feat(tui-go): grant scope picker (A) + standing-grants viewer

Surface the new cross-session grant scopes in the TUI.

- Approve-always (A) now opens a scope picker instead of immediately
  creating a SESSION grant: choose this session / this project / everywhere.
  SESSION stays the default (A then enter/s = old behaviour); p and g
  create PROJECT / GLOBAL grants. The grant + approval are sent on confirm
  (esc cancels, leaving the call pending).
- New "grants" palette command + G shortcut open a standing-grants viewer
  (OverlayGrants) listing the active PROJECT/GLOBAL grants — scope, tool,
  permitted tiers, project path — with x/enter to revoke. The server's
  RevokeGrant reply (a fresh grant.list) refreshes it in place.
- protocol.go: TypeGrantList + GrantDto; RevokeGrant/ListGrants encoders;
  CreateGrant now documents the wider scopes. server.go decodes grant.list
  into m.grants and treats it as a non-session global reply.
- render-matrix coverage: grant.list classified as a non-rendering query
  reply (populates the overlay, not the transcript).
- demo.go: grants + grant-scope preview kinds.

go build / vet / test green; rendered both overlays via cmd/preview.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-21 22:35:01 +00:00
parent c36d41b9d5
commit 8df0ec750c
7 changed files with 252 additions and 10 deletions
+7 -1
View File
@@ -333,6 +333,12 @@ func (m *Model) applyServer(msg protocol.ServerMessage) {
if m.fileIndex >= len(m.filteredFiles()) {
m.fileIndex = 0
}
case protocol.TypeGrantList:
m.grants = msg.Grants
m.grantsLoading = false
if m.grantIndex >= len(m.grants) {
m.grantIndex = 0
}
case protocol.TypeConfigSnapshot:
m.configFields = msg.ConfigFields
m.configRestart = msg.ConfigRestartRequired
@@ -378,7 +384,7 @@ func sessionIDOf(msg protocol.ServerMessage) string {
protocol.TypeWorkflowList, protocol.TypeRouterResponse,
protocol.TypeModelChanged, protocol.TypeModelList, protocol.TypeResourceStatus,
protocol.TypeArtifactList, protocol.TypeConfigSnapshot, protocol.TypeSessionStats,
protocol.TypeIdeaList, protocol.TypeFileList:
protocol.TypeIdeaList, protocol.TypeFileList, protocol.TypeGrantList:
return ""
default:
return msg.SessionID