88 "charm.land/lipgloss/v2"
99
1010 "github.com/docker/cagent/pkg/runtime"
11- "github.com/docker/cagent/pkg/tui/components/markdown"
12- "github.com/docker/cagent/pkg/tui/components/tool"
11+ "github.com/docker/cagent/pkg/tui/components/messages"
1312 "github.com/docker/cagent/pkg/tui/core"
1413 "github.com/docker/cagent/pkg/tui/core/layout"
1514 "github.com/docker/cagent/pkg/tui/service"
@@ -33,12 +32,41 @@ type toolConfirmationDialog struct {
3332 msg * runtime.ToolCallConfirmationEvent
3433 keyMap toolConfirmationKeyMap
3534 sessionState * service.SessionState
35+ scrollView messages.Model
3636}
3737
3838// SetSize implements [Dialog].
3939func (d * toolConfirmationDialog ) SetSize (width , height int ) tea.Cmd {
4040 d .width = width
4141 d .height = height
42+
43+ // Calculate dialog dimensions
44+ dialogWidth := width * 70 / 100
45+ contentWidth := dialogWidth - 6
46+ maxDialogHeight := (height * 80 ) / 100
47+
48+ titleStyle := styles .DialogTitleStyle .Width (contentWidth )
49+ title := titleStyle .Render ("Tool Confirmation" )
50+ titleHeight := lipgloss .Height (title )
51+
52+ separatorWidth := max (contentWidth - 10 , 20 )
53+ separator := styles .DialogSeparatorStyle .
54+ Align (lipgloss .Center ).
55+ Width (contentWidth ).
56+ Render (strings .Repeat ("─" , separatorWidth ))
57+ separatorHeight := lipgloss .Height (separator )
58+
59+ question := styles .DialogQuestionStyle .Width (contentWidth ).Render ("Do you want to allow this tool call?" )
60+ questionHeight := lipgloss .Height (question )
61+
62+ options := styles .DialogOptionsStyle .Width (contentWidth ).Render ("[Y]es [N]o [A]ll (approve all tools this session)" )
63+ optionsHeight := lipgloss .Height (options )
64+
65+ // Calculate available height for scroll view
66+ // Total = maxDialogHeight - title - separator - 2 empty lines - question - empty line - options - 4 (dialog padding/border)
67+ availableHeight := max (maxDialogHeight - titleHeight - separatorHeight - 2 - questionHeight - 1 - optionsHeight - 4 , 5 )
68+ d .scrollView .SetSize (contentWidth , availableHeight )
69+
4270 return nil
4371}
4472
@@ -69,16 +97,28 @@ func defaultToolConfirmationKeyMap() toolConfirmationKeyMap {
6997
7098// NewToolConfirmationDialog creates a new tool confirmation dialog
7199func NewToolConfirmationDialog (msg * runtime.ToolCallConfirmationEvent , sessionState * service.SessionState ) Dialog {
100+ // Create scrollable view with initial size (will be updated in SetSize)
101+ scrollView := messages .NewScrollableView (100 , 20 , sessionState )
102+
103+ // Add the tool call message to the view
104+ scrollView .AddOrUpdateToolCall (
105+ "" , // agentName - empty for dialog context
106+ msg .ToolCall ,
107+ msg .ToolDefinition ,
108+ types .ToolStatusConfirmation ,
109+ )
110+
72111 return & toolConfirmationDialog {
73112 msg : msg ,
74113 sessionState : sessionState ,
75114 keyMap : defaultToolConfirmationKeyMap (),
115+ scrollView : scrollView ,
76116 }
77117}
78118
79119// Init initializes the tool confirmation dialog
80120func (d * toolConfirmationDialog ) Init () tea.Cmd {
81- return nil
121+ return d . scrollView . Init ()
82122}
83123
84124// Update handles messages for the tool confirmation dialog
@@ -87,7 +127,8 @@ func (d *toolConfirmationDialog) Update(msg tea.Msg) (layout.Model, tea.Cmd) {
87127 case tea.WindowSizeMsg :
88128 d .width = msg .Width
89129 d .height = msg .Height
90- return d , nil
130+ cmd := d .SetSize (msg .Width , msg .Height )
131+ return d , cmd
91132
92133 case tea.KeyPressMsg :
93134 switch {
@@ -102,6 +143,20 @@ func (d *toolConfirmationDialog) Update(msg tea.Msg) (layout.Model, tea.Cmd) {
102143 if msg .String () == "ctrl+c" {
103144 return d , tea .Quit
104145 }
146+
147+ // Forward scrolling keys to the scroll view
148+ switch msg .String () {
149+ case "up" , "k" , "down" , "j" , "pgup" , "pgdown" , "home" , "end" :
150+ updatedScrollView , cmd := d .scrollView .Update (msg )
151+ d .scrollView = updatedScrollView .(messages.Model )
152+ return d , cmd
153+ }
154+
155+ case tea.MouseWheelMsg :
156+ // Forward mouse wheel events to scroll view
157+ updatedScrollView , cmd := d .scrollView .Update (msg )
158+ d .scrollView = updatedScrollView .(messages.Model )
159+ return d , cmd
105160 }
106161
107162 return d , nil
@@ -114,9 +169,6 @@ func (d *toolConfirmationDialog) View() string {
114169 // Content width (accounting for padding and borders)
115170 contentWidth := dialogWidth - 6
116171
117- // Calculate max height (80% of screen height)
118- maxDialogHeight := (d .height * 80 ) / 100
119-
120172 dialogStyle := styles .DialogStyle .Width (dialogWidth )
121173
122174 // Title
@@ -130,15 +182,8 @@ func (d *toolConfirmationDialog) View() string {
130182 Width (contentWidth ).
131183 Render (strings .Repeat ("─" , separatorWidth ))
132184
133- a := types.Message {
134- ToolCall : d .msg .ToolCall ,
135- ToolDefinition : d .msg .ToolDefinition ,
136- Type : types .MessageTypeToolCall ,
137- ToolStatus : types .ToolStatusConfirmation ,
138- }
139- view := tool .New (& a , markdown .NewRenderer (contentWidth ), d .sessionState )
140- view .SetSize (contentWidth , 0 )
141- argumentsSection := view .View ()
185+ // Get scrollable tool call view
186+ argumentsSection := d .scrollView .View ()
142187
143188 question := styles .DialogQuestionStyle .Width (contentWidth ).Render ("Do you want to allow this tool call?" )
144189 options := styles .DialogOptionsStyle .Width (contentWidth ).Render ("[Y]es [N]o [A]ll (approve all tools this session)" )
@@ -154,45 +199,9 @@ func (d *toolConfirmationDialog) View() string {
154199
155200 content := lipgloss .JoinVertical (lipgloss .Left , parts ... )
156201
157- // Apply max height constraint if needed
158- contentHeight := lipgloss .Height (content )
159- if contentHeight > maxDialogHeight - 4 { // Account for dialog padding/border
160- // Limit the arguments section height
161- availableHeight := maxDialogHeight - 4 - lipgloss .Height (title ) - lipgloss .Height (separator ) - lipgloss .Height (question ) - lipgloss .Height (options ) - 4 // spacing
162- if availableHeight > 0 && argumentsSection != "" {
163- argumentsSection = d .truncateToHeight (argumentsSection , availableHeight )
164-
165- // Rebuild content with truncated arguments
166- parts = []string {title , separator }
167- if argumentsSection != "" {
168- parts = append (parts , "" , argumentsSection )
169- }
170- parts = append (parts , "" , question , "" , options )
171- content = lipgloss .JoinVertical (lipgloss .Left , parts ... )
172- }
173- }
174-
175202 return dialogStyle .Render (content )
176203}
177204
178- // truncateToHeight truncates content to fit within the specified height,
179- // adding an ellipsis indicator at the end
180- func (d * toolConfirmationDialog ) truncateToHeight (content string , maxHeight int ) string {
181- if maxHeight <= 0 {
182- return ""
183- }
184-
185- lines := strings .Split (content , "\n " )
186- if len (lines ) <= maxHeight {
187- return content
188- }
189-
190- // Reserve last line for truncation indicator
191- truncatedLines := lines [:maxHeight - 1 ]
192- truncatedLines = append (truncatedLines , styles .MutedStyle .Render ("... (content truncated)" ))
193- return strings .Join (truncatedLines , "\n " )
194- }
195-
196205// Position calculates the position to center the dialog
197206func (d * toolConfirmationDialog ) Position () (row , col int ) {
198207 dialogWidth := d .width * 70 / 100
0 commit comments