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:
@@ -56,6 +56,7 @@ const (
|
||||
TypeSessionStats = "session.stats"
|
||||
TypeIdeaList = "idea.list"
|
||||
TypeFileList = "file.list"
|
||||
TypeGrantList = "grant.list"
|
||||
)
|
||||
|
||||
// ServerMessage is a flat decode of every server->client variant. Field names
|
||||
@@ -154,6 +155,20 @@ type ServerMessage struct {
|
||||
|
||||
// file.list — the session workspace's file paths (for the @ file-ref picker)
|
||||
Paths []string `json:"paths"`
|
||||
|
||||
// grant.list — the active cross-session (PROJECT/GLOBAL) standing grants
|
||||
Grants []GrantDto `json:"grants"`
|
||||
}
|
||||
|
||||
// GrantDto is one standing cross-session grant. Mirrors the Kotlin GrantDto.
|
||||
type GrantDto struct {
|
||||
GrantID string `json:"grantId"`
|
||||
Scope string `json:"scope"` // "PROJECT" | "GLOBAL"
|
||||
ToolName string `json:"toolName"`
|
||||
ProjectID string `json:"projectId"`
|
||||
Tiers []string `json:"tiers"`
|
||||
Reason string `json:"reason"`
|
||||
ExpiresAtMs *int64 `json:"expiresAtMs"`
|
||||
}
|
||||
|
||||
// IdeaDto is one idea on the cross-session board. Mirrors the Kotlin IdeaDto.
|
||||
@@ -457,7 +472,8 @@ func ClearModelPin() []byte {
|
||||
}
|
||||
|
||||
// CreateGrant pre-authorizes a tool/tier so future calls skip the approval gate.
|
||||
// scope is "SESSION" or "STAGE"; permittedTiers are tier names like "T3".
|
||||
// scope is "SESSION", "PROJECT", or "GLOBAL"; permittedTiers are tier names like "T3".
|
||||
// For PROJECT/GLOBAL the server derives the projectId from the session's workspace.
|
||||
func CreateGrant(sessionID, scope string, permittedTiers []string, reason, toolName string) []byte {
|
||||
return encode("CreateGrant", map[string]any{
|
||||
"sessionId": sessionID,
|
||||
@@ -470,6 +486,16 @@ func CreateGrant(sessionID, scope string, permittedTiers []string, reason, toolN
|
||||
})
|
||||
}
|
||||
|
||||
// RevokeGrant revokes a standing (PROJECT/GLOBAL) grant by id; replies with a fresh grant.list.
|
||||
func RevokeGrant(grantID string) []byte {
|
||||
return encode("RevokeGrant", map[string]any{"grantId": grantID})
|
||||
}
|
||||
|
||||
// ListGrants requests the active cross-session grants (replied to with a grant.list).
|
||||
func ListGrants() []byte {
|
||||
return encode("ListGrants", map[string]any{})
|
||||
}
|
||||
|
||||
// Hello is the first frame sent on every (re)connect. It carries the client's
|
||||
// working directory so the server can bind a workspace before any session starts.
|
||||
func Hello(workingDir string) []byte {
|
||||
|
||||
Reference in New Issue
Block a user