返回 DeepSeek-Reasonix
approval_choices.go
根目录 / internal / cli / approval_choices.go
1 package cli
2
3 import (
4 "fmt"
5 "strings"
6
7 "reasonix/internal/agent"
8 "reasonix/internal/control"
9 "reasonix/internal/event"
10 "reasonix/internal/i18n"
11 "reasonix/internal/permission"
12 "reasonix/internal/tool"
13 )
14
15 type approvalChoice struct {
16 label string
17 allow bool
18 allowForSession bool
19 exitPlan bool
20 }
21
22 func approvalChoices(a *event.Approval) []approvalChoice {
23 if a == nil {
24 return nil
25 }
26 // Recovery approvals belong to the retired Auto Guard workflow. Keep old
27 // events decodable for history, but never expose actions that could confirm,
28 // retry, or grant authority to a historical recovery record.
29 if isRecoveryApprovalEvent(a) {
30 return nil
31 }
32 fresh := a.Fresh || control.RequiresFreshHumanApprovalTool(a.Tool)
33 var decisions []approvalChoice
34 switch {
35 case a.Tool == planApprovalTool:
36 decisions = []approvalChoice{{allow: true}, {}, {exitPlan: true}}
37 case a.Kind == event.ApprovalKindWriteAccess || a.WriteAccess != nil:
38 decisions = []approvalChoice{{allow: true}, {allow: true, allowForSession: true}, {}}
39 case fresh && freshApprovalAllowsSession(a.Tool):
40 decisions = []approvalChoice{{allow: true}, {allow: true, allowForSession: true}, {}}
41 case fresh:
42 decisions = []approvalChoice{{allow: true}, {}}
43 default:
44 decisions = []approvalChoice{{allow: true}, {allow: true, allowForSession: true}, {}}
45 }
46 labels := approvalChoiceLabels(a)
47 for i := range decisions {
48 if i < len(labels) {
49 decisions[i].label = labels[i]
50 }
51 }
52 return decisions
53 }
54
55 func approvalChoiceLabels(a *event.Approval) []string {
56 if isRecoveryApprovalEvent(a) {
57 return nil
58 }
59 choices := i18n.M.FreshHumanApprovalChoices
60 fresh := a.Fresh || control.RequiresFreshHumanApprovalTool(a.Tool)
61 if a.Tool == planApprovalTool {
62 choices = i18n.M.PlanApprovalChoices
63 } else if !fresh {
64 sessionRule := permission.SessionGrantRuleForScope(a.Tool, a.Subject)
65 choices = fmt.Sprintf(i18n.M.ToolApprovalChoices, sessionRule)
66 }
67 switch a.Tool {
68 case control.SandboxEscapeApprovalTool:
69 choices = i18n.M.SandboxEscapeApprovalChoices
70 case control.ManagedConfigWriteApprovalTool:
71 choices = i18n.M.ConfigWriteApprovalChoices
72 case agent.PlanModeReadOnlyCommandApprovalTool:
73 choices = i18n.M.PlanModeReadOnlyCommandChoices
74 }
75 if a.Kind == event.ApprovalKindWriteAccess || a.WriteAccess != nil {
76 choices = i18n.M.WriteAccessApprovalChoices
77 }
78 if !fresh && tool.IsShellToolName(a.Tool) && permission.BashCommandPrefix(a.Subject) != "" {
79 rule := permission.RememberRuleForScope(a.Tool, a.Subject)
80 choices = fmt.Sprintf(i18n.M.BashPrefixChoices, rule)
81 }
82 var labels []string
83 for line := range strings.SplitSeq(choices, "\n") {
84 line = strings.TrimSpace(line)
85 if len(line) >= 3 && line[0] >= '1' && line[0] <= '9' && line[1] == '.' {
86 labels = append(labels, strings.TrimSpace(line[2:]))
87 }
88 }
89 return labels
90 }
91
92 func writeAccessBannerDetails(a *event.Approval) []string {
93 if a == nil || a.WriteAccess == nil {
94 return nil
95 }
96 wa := a.WriteAccess
97 dirs := wa.DisplayDirectories
98 if len(dirs) == 0 {
99 dirs = wa.Directories
100 }
101 details := make([]string, 0, 4)
102 if len(dirs) > 0 {
103 details = append(details, strings.Join(dirs, ", "))
104 }
105 if wa.BroadHomeAccess {
106 details = append(details, i18n.M.WriteAccessHomeWarning)
107 }
108 if wa.OrdinaryPermissionNeeded {
109 details = append(details, i18n.M.WriteAccessMergedPermissionHint)
110 }
111 if wa.PersistAllowed {
112 details = append(details, i18n.M.WriteAccessProjectHint)
113 }
114 return details
115 }
116
116 lines GO