@@ -8,6 +8,9 @@ import { BaseApprovalModal } from "./BaseApprovalModal";
88export class ToolApprovalModal extends BaseApprovalModal < ToolApprovalDecision > {
99 private toolName : string ;
1010 private args : Record < string , any > ;
11+ private editedQuery : string | null = null ;
12+ private queryTextarea : HTMLTextAreaElement | null = null ;
13+ private approveBtn : HTMLButtonElement | null = null ;
1114
1215 constructor ( app : App , toolName : string , args : Record < string , any > , modelName : string = "AI" ) {
1316 super ( app , modelName ) ;
@@ -126,6 +129,34 @@ export class ToolApprovalModal extends BaseApprovalModal<ToolApprovalDecision> {
126129 return "Approve and Execute" ;
127130 }
128131
132+ protected override renderActionButtons ( container : HTMLElement ) : void {
133+ const buttonContainer = container . createDiv ( ) ;
134+ buttonContainer . style . display = "flex" ;
135+ buttonContainer . style . gap = "8px" ;
136+ buttonContainer . style . justifyContent = "flex-end" ;
137+ buttonContainer . style . marginTop = "20px" ;
138+
139+ const cancelBtn = buttonContainer . createEl ( "button" , { text : this . getCancelText ( ) } ) ;
140+ this . styleCancelButton ( cancelBtn ) ;
141+ cancelBtn . onclick = ( ) => {
142+ this . result = this . buildCancelledResult ( ) ;
143+ this . resolveModalPromise ( this . result ) ;
144+ this . close ( ) ;
145+ } ;
146+
147+ this . approveBtn = buttonContainer . createEl ( "button" , { text : this . getApproveText ( ) } ) ;
148+ this . styleApproveButton ( this . approveBtn ) ;
149+
150+ // Initial validation
151+ this . validateApproveButton ( ) ;
152+
153+ this . approveBtn . onclick = ( ) => {
154+ this . result = this . buildApprovedResult ( ) ;
155+ this . resolveModalPromise ( this . result ) ;
156+ this . close ( ) ;
157+ } ;
158+ }
159+
129160 protected buildApprovedResult ( ) : ToolApprovalDecision {
130161 return {
131162 approvalId : this . toolName ,
@@ -141,6 +172,27 @@ export class ToolApprovalModal extends BaseApprovalModal<ToolApprovalDecision> {
141172 } ;
142173 }
143174
175+ /**
176+ * Validate query and enable/disable approve button
177+ */
178+ private validateApproveButton ( ) : void {
179+ if ( ! this . approveBtn ) return ;
180+
181+ // For search tools, require non-empty query
182+ if ( ( this . toolName === "vault_search" || this . toolName === "web_search" ) && this . queryTextarea ) {
183+ const query = this . queryTextarea . value . trim ( ) ;
184+ const isValid = query . length > 0 ;
185+ this . approveBtn . disabled = ! isValid ;
186+ this . approveBtn . style . opacity = isValid ? "1" : "0.5" ;
187+ this . approveBtn . style . cursor = isValid ? "pointer" : "not-allowed" ;
188+ } else {
189+ // Other tools - always enabled
190+ this . approveBtn . disabled = false ;
191+ this . approveBtn . style . opacity = "1" ;
192+ this . approveBtn . style . cursor = "pointer" ;
193+ }
194+ }
195+
144196 /**
145197 * Get modified arguments based on user selections
146198 */
@@ -160,6 +212,14 @@ export class ToolApprovalModal extends BaseApprovalModal<ToolApprovalDecision> {
160212 } ;
161213 }
162214
215+ // For vault_search and web_search, include edited query if changed
216+ if ( ( this . toolName === "vault_search" || this . toolName === "web_search" ) && this . editedQuery ) {
217+ return {
218+ ...baseArgs ,
219+ query : this . editedQuery ,
220+ } ;
221+ }
222+
163223 return baseArgs ;
164224 }
165225
@@ -198,10 +258,33 @@ export class ToolApprovalModal extends BaseApprovalModal<ToolApprovalDecision> {
198258
199259 // List for search queries
200260 if ( ( this . toolName === "vault_search" || this . toolName === "web_search" ) && query ) {
201- const list = container . createEl ( "ul" ) ;
202- list . style . margin = "0 0 16px 20px" ;
203- list . style . lineHeight = "1.5" ;
204- list . createEl ( "li" , { text : `"${ query } "` } ) ;
261+ // Label for textarea
262+ const label = container . createEl ( "label" , { text : "Search query:" } ) ;
263+ label . style . display = "block" ;
264+ label . style . marginBottom = "8px" ;
265+ label . style . fontWeight = "500" ;
266+ label . style . opacity = "0.7" ;
267+
268+ // Editable textarea for query
269+ this . queryTextarea = container . createEl ( "textarea" ) ;
270+ this . queryTextarea . value = query ;
271+ this . queryTextarea . style . width = "100%" ;
272+ this . queryTextarea . style . minHeight = "80px" ;
273+ this . queryTextarea . style . padding = "8px" ;
274+ this . queryTextarea . style . borderRadius = "4px" ;
275+ this . queryTextarea . style . border = "1px solid var(--background-modifier-border)" ;
276+ this . queryTextarea . style . backgroundColor = "var(--background-secondary)" ;
277+ this . queryTextarea . style . color = "var(--text-normal)" ;
278+ this . queryTextarea . style . fontSize = "0.95em" ;
279+ this . queryTextarea . style . fontFamily = "var(--font-interface)" ;
280+ this . queryTextarea . style . resize = "vertical" ;
281+ this . queryTextarea . style . marginBottom = "16px" ;
282+
283+ // Track query changes
284+ this . queryTextarea . addEventListener ( "input" , ( ) => {
285+ this . editedQuery = this . queryTextarea ! . value . trim ( ) ;
286+ this . validateApproveButton ( ) ;
287+ } ) ;
205288 } else if ( this . toolName === "file_read" ) {
206289 // For file_read, still just one line since files are shown in selection below
207290 const noteEl = container . createEl ( "p" , {
0 commit comments