Skip to content

Commit ce2e019

Browse files
committed
feat: File operation auto-approve with glob pattern rules (Auto-approve Part 3) (#216)
* add file operation approve logic * resolve comments * change default rules color * resolve comments * update test cases
1 parent 1ca99c0 commit ce2e019

28 files changed

Lines changed: 2921 additions & 28 deletions

com.microsoft.copilot.eclipse.core/src/com/microsoft/copilot/eclipse/core/Constants.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@ private Constants() {
5757
// Auto-Approve settings
5858
public static final String AUTO_APPROVE_TERMINAL_RULES = "autoApproveTerminalRules";
5959
public static final String AUTO_APPROVE_UNMATCHED_TERMINAL = "autoApproveUnmatchedTerminal";
60+
public static final String AUTO_APPROVE_FILE_OP_RULES = "autoApproveEditRules";
61+
public static final String AUTO_APPROVE_UNMATCHED_FILE_OP = "autoApproveUnmatchedFileOp";
6062

6163
// Base excluded file types shared by both
6264
// Copied from InelliJ, excluded file extension list

com.microsoft.copilot.eclipse.core/src/com/microsoft/copilot/eclipse/core/chat/ConfirmationResult.java

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,34 +14,44 @@
1414
public class ConfirmationResult {
1515

1616
/** Auto-approved, no user confirmation needed. */
17-
public static final ConfirmationResult AUTO_APPROVED = new ConfirmationResult(true, null);
17+
public static final ConfirmationResult AUTO_APPROVED = new ConfirmationResult(true, false, null);
18+
19+
/** Dismissed — malformed or unhandleable request; CLS should be told to skip the tool. */
20+
public static final ConfirmationResult DISMISSED = new ConfirmationResult(false, true, null);
1821

1922
private final boolean autoApproved;
23+
private final boolean dismissed;
2024
private final ConfirmationContent content;
2125

22-
private ConfirmationResult(boolean autoApproved, ConfirmationContent content) {
26+
private ConfirmationResult(boolean autoApproved, boolean dismissed, ConfirmationContent content) {
2327
this.autoApproved = autoApproved;
28+
this.dismissed = dismissed;
2429
this.content = content;
2530
}
2631

2732
/** Creates a result that requires user confirmation with the given content. */
2833
public static ConfirmationResult needsConfirmation(
2934
ConfirmationContent content) {
30-
return new ConfirmationResult(false, content);
35+
return new ConfirmationResult(false, false, content);
3136
}
3237

3338
public boolean isAutoApproved() {
3439
return autoApproved;
3540
}
3641

42+
/** Returns true if the request should be dismissed without showing UI. */
43+
public boolean isDismissed() {
44+
return dismissed;
45+
}
46+
3747
/** Returns the confirmation content, or null if auto-approved or using defaults. */
3848
public ConfirmationContent getContent() {
3949
return content;
4050
}
4151

4252
@Override
4353
public int hashCode() {
44-
return Objects.hash(autoApproved, content);
54+
return Objects.hash(autoApproved, dismissed, content);
4555
}
4656

4757
@Override
@@ -57,13 +67,15 @@ public boolean equals(Object obj) {
5767
}
5868
ConfirmationResult other = (ConfirmationResult) obj;
5969
return autoApproved == other.autoApproved
70+
&& dismissed == other.dismissed
6071
&& Objects.equals(content, other.content);
6172
}
6273

6374
@Override
6475
public String toString() {
6576
return new ToStringBuilder(this)
6677
.append("autoApproved", autoApproved)
78+
.append("dismissed", dismissed)
6779
.append("content", content)
6880
.toString();
6981
}
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT license.
3+
4+
package com.microsoft.copilot.eclipse.core.chat;
5+
6+
import java.util.Objects;
7+
8+
import org.apache.commons.lang3.builder.ToStringBuilder;
9+
10+
/**
11+
* A single file-operation auto-approve rule mapping a glob pattern to an allow/deny decision.
12+
*/
13+
public class FileOperationAutoApproveRule {
14+
private String pattern;
15+
private String description;
16+
private boolean autoApprove;
17+
private transient boolean isDefault;
18+
19+
/**
20+
* Creates a new rule.
21+
*
22+
* @param pattern the glob pattern (e.g., "**\/.github/instructions/*")
23+
* @param description human-readable description of what this pattern matches
24+
* @param autoApprove true to auto-approve, false to always require confirmation
25+
*/
26+
public FileOperationAutoApproveRule(String pattern, String description, boolean autoApprove) {
27+
this(pattern, description, autoApprove, false);
28+
}
29+
30+
/**
31+
* Creates a new rule.
32+
*
33+
* @param pattern the glob pattern
34+
* @param description human-readable description
35+
* @param autoApprove true to auto-approve, false to always require confirmation
36+
* @param isDefault true if this is a CLS default rule (non-removable)
37+
*/
38+
public FileOperationAutoApproveRule(String pattern, String description,
39+
boolean autoApprove, boolean isDefault) {
40+
this.pattern = pattern;
41+
this.description = description;
42+
this.autoApprove = autoApprove;
43+
this.isDefault = isDefault;
44+
}
45+
46+
/** Default constructor for Gson deserialization. */
47+
public FileOperationAutoApproveRule() {
48+
}
49+
50+
public String getPattern() {
51+
return pattern;
52+
}
53+
54+
public void setPattern(String pattern) {
55+
this.pattern = pattern;
56+
}
57+
58+
public String getDescription() {
59+
return description;
60+
}
61+
62+
public void setDescription(String description) {
63+
this.description = description;
64+
}
65+
66+
public boolean isAutoApprove() {
67+
return autoApprove;
68+
}
69+
70+
public void setAutoApprove(boolean autoApprove) {
71+
this.autoApprove = autoApprove;
72+
}
73+
74+
public boolean isDefault() {
75+
return isDefault;
76+
}
77+
78+
public void setDefault(boolean isDefault) {
79+
this.isDefault = isDefault;
80+
}
81+
82+
@Override
83+
public int hashCode() {
84+
return Objects.hash(pattern, description, autoApprove);
85+
}
86+
87+
@Override
88+
public boolean equals(Object obj) {
89+
if (this == obj) {
90+
return true;
91+
}
92+
if (obj == null || getClass() != obj.getClass()) {
93+
return false;
94+
}
95+
FileOperationAutoApproveRule other = (FileOperationAutoApproveRule) obj;
96+
return Objects.equals(pattern, other.pattern)
97+
&& Objects.equals(description, other.description)
98+
&& autoApprove == other.autoApprove;
99+
}
100+
101+
@Override
102+
public String toString() {
103+
return new ToStringBuilder(this)
104+
.append("pattern", pattern)
105+
.append("description", description)
106+
.append("autoApprove", autoApprove)
107+
.toString();
108+
}
109+
}

com.microsoft.copilot.eclipse.core/src/com/microsoft/copilot/eclipse/core/lsp/CopilotLanguageServer.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
import com.microsoft.copilot.eclipse.core.lsp.protocol.DidShowInlineEditParams;
3737
import com.microsoft.copilot.eclipse.core.lsp.protocol.GenerateThinkingTitleParams;
3838
import com.microsoft.copilot.eclipse.core.lsp.protocol.GenerateThinkingTitleResponse;
39+
import com.microsoft.copilot.eclipse.core.lsp.protocol.GetDefaultFileSafetyRulesResult;
3940
import com.microsoft.copilot.eclipse.core.lsp.protocol.LanguageModelToolInformation;
4041
import com.microsoft.copilot.eclipse.core.lsp.protocol.NextEditSuggestionsParams;
4142
import com.microsoft.copilot.eclipse.core.lsp.protocol.NextEditSuggestionsResult;
@@ -285,6 +286,13 @@ public interface CopilotLanguageServer extends LanguageServer {
285286
@JsonRequest("githubApi/searchPR")
286287
CompletableFuture<SearchPrResponse> searchPr(SearchPrParams params);
287288

289+
/**
290+
* Get the default file safety rules from CLS.
291+
*/
292+
@JsonRequest("getDefaultFileSafetyRules")
293+
CompletableFuture<GetDefaultFileSafetyRulesResult> getDefaultFileSafetyRules(
294+
NullParams params);
295+
288296
/**
289297
* Notify that an inline edit was shown.
290298
*/

com.microsoft.copilot.eclipse.core/src/com/microsoft/copilot/eclipse/core/lsp/CopilotLanguageServerConnection.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@
5454
import com.microsoft.copilot.eclipse.core.lsp.protocol.DidShowInlineEditParams;
5555
import com.microsoft.copilot.eclipse.core.lsp.protocol.GenerateThinkingTitleParams;
5656
import com.microsoft.copilot.eclipse.core.lsp.protocol.GenerateThinkingTitleResponse;
57+
import com.microsoft.copilot.eclipse.core.lsp.protocol.GetDefaultFileSafetyRulesResult;
5758
import com.microsoft.copilot.eclipse.core.lsp.protocol.LanguageModelToolInformation;
5859
import com.microsoft.copilot.eclipse.core.lsp.protocol.ModelInfo;
5960
import com.microsoft.copilot.eclipse.core.lsp.protocol.NextEditSuggestionsParams;
@@ -153,6 +154,16 @@ public CompletableFuture<CheckQuotaResult> checkQuota() {
153154
return this.languageServerWrapper.execute(fn);
154155
}
155156

157+
/**
158+
* Get the default file safety rules from CLS.
159+
*/
160+
public CompletableFuture<GetDefaultFileSafetyRulesResult> getDefaultFileSafetyRules() {
161+
Function<LanguageServer, CompletableFuture<GetDefaultFileSafetyRulesResult>> fn =
162+
server -> ((CopilotLanguageServer) server)
163+
.getDefaultFileSafetyRules(new NullParams());
164+
return this.languageServerWrapper.execute(fn);
165+
}
166+
156167
/**
157168
* Get single completion for the given parameters.
158169
*/

com.microsoft.copilot.eclipse.core/src/com/microsoft/copilot/eclipse/core/lsp/protocol/CopilotAgentSettings.java

Lines changed: 107 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ public class CopilotAgentSettings {
2323
@SerializedName("autoApproveUnmatchedTerminal")
2424
private boolean autoApproveUnmatchedTerminal;
2525

26+
@SerializedName("autoApproveUnmatchedFileOp")
27+
private boolean autoApproveUnmatchedFileOp;
28+
2629
// Tells CLS to always send confirmation requests to the editor
2730
@SerializedName("editorHandlesAllConfirmation")
2831
private boolean editorHandlesAllConfirmation = true;
@@ -32,6 +35,7 @@ public class CopilotAgentSettings {
3235
/** Nested tools settings matching CLS agent.tools structure. */
3336
public static class ToolsSettings {
3437
private TerminalSettings terminal;
38+
private EditSettings edit;
3539

3640
/** Gets terminal settings, creating if needed. */
3741
public TerminalSettings getTerminal() {
@@ -40,6 +44,39 @@ public TerminalSettings getTerminal() {
4044
}
4145
return terminal;
4246
}
47+
48+
/** Gets edit settings, creating if needed. */
49+
public EditSettings getEdit() {
50+
if (edit == null) {
51+
edit = new EditSettings();
52+
}
53+
return edit;
54+
}
55+
56+
@Override
57+
public int hashCode() {
58+
return Objects.hash(terminal, edit);
59+
}
60+
61+
@Override
62+
public boolean equals(Object obj) {
63+
if (this == obj) {
64+
return true;
65+
}
66+
if (obj == null || getClass() != obj.getClass()) {
67+
return false;
68+
}
69+
ToolsSettings other = (ToolsSettings) obj;
70+
return Objects.equals(terminal, other.terminal) && Objects.equals(edit, other.edit);
71+
}
72+
73+
@Override
74+
public String toString() {
75+
return new ToStringBuilder(this)
76+
.append("terminal", terminal)
77+
.append("edit", edit)
78+
.toString();
79+
}
4380
}
4481

4582
/** Terminal auto-approve rules: command/pattern -> allow(true)/deny(false). */
@@ -53,6 +90,65 @@ public Map<String, Boolean> getAutoApprove() {
5390
public void setAutoApprove(Map<String, Boolean> autoApprove) {
5491
this.autoApprove = autoApprove;
5592
}
93+
94+
@Override
95+
public int hashCode() {
96+
return Objects.hash(autoApprove);
97+
}
98+
99+
@Override
100+
public boolean equals(Object obj) {
101+
if (this == obj) {
102+
return true;
103+
}
104+
if (obj == null || getClass() != obj.getClass()) {
105+
return false;
106+
}
107+
return Objects.equals(autoApprove, ((TerminalSettings) obj).autoApprove);
108+
}
109+
110+
@Override
111+
public String toString() {
112+
return new ToStringBuilder(this)
113+
.append("autoApprove", autoApprove)
114+
.toString();
115+
}
116+
}
117+
118+
/** Edit (file operation) auto-approve rules: pattern → allow(true)/deny(false). */
119+
public static class EditSettings {
120+
private Map<String, Boolean> autoApprove;
121+
122+
public Map<String, Boolean> getAutoApprove() {
123+
return autoApprove;
124+
}
125+
126+
public void setAutoApprove(Map<String, Boolean> autoApprove) {
127+
this.autoApprove = autoApprove;
128+
}
129+
130+
@Override
131+
public int hashCode() {
132+
return Objects.hash(autoApprove);
133+
}
134+
135+
@Override
136+
public boolean equals(Object obj) {
137+
if (this == obj) {
138+
return true;
139+
}
140+
if (obj == null || getClass() != obj.getClass()) {
141+
return false;
142+
}
143+
return Objects.equals(autoApprove, ((EditSettings) obj).autoApprove);
144+
}
145+
146+
@Override
147+
public String toString() {
148+
return new ToStringBuilder(this)
149+
.append("autoApprove", autoApprove)
150+
.toString();
151+
}
56152
}
57153

58154
public int getAgentMaxRequests() {
@@ -98,6 +194,14 @@ public void setAutoApproveUnmatchedTerminal(boolean autoApproveUnmatchedTerminal
98194
this.autoApproveUnmatchedTerminal = autoApproveUnmatchedTerminal;
99195
}
100196

197+
public boolean isAutoApproveUnmatchedFileOp() {
198+
return autoApproveUnmatchedFileOp;
199+
}
200+
201+
public void setAutoApproveUnmatchedFileOp(boolean autoApproveUnmatchedFileOp) {
202+
this.autoApproveUnmatchedFileOp = autoApproveUnmatchedFileOp;
203+
}
204+
101205
/** Gets tools settings, creating if needed. */
102206
public ToolsSettings getTools() {
103207
if (tools == null) {
@@ -109,7 +213,7 @@ public ToolsSettings getTools() {
109213
@Override
110214
public int hashCode() {
111215
return Objects.hash(agentMaxRequests, enableSkills, transcriptDirectory,
112-
editorHandlesAllConfirmation, autoApproveUnmatchedTerminal, tools);
216+
editorHandlesAllConfirmation, autoApproveUnmatchedTerminal, autoApproveUnmatchedFileOp, tools);
113217
}
114218

115219
@Override
@@ -128,6 +232,7 @@ public boolean equals(Object obj) {
128232
&& Objects.equals(transcriptDirectory, other.transcriptDirectory)
129233
&& editorHandlesAllConfirmation == other.editorHandlesAllConfirmation
130234
&& autoApproveUnmatchedTerminal == other.autoApproveUnmatchedTerminal
235+
&& autoApproveUnmatchedFileOp == other.autoApproveUnmatchedFileOp
131236
&& Objects.equals(tools, other.tools);
132237
}
133238

@@ -139,6 +244,7 @@ public String toString() {
139244
builder.append("transcriptDirectory", transcriptDirectory);
140245
builder.append("editorHandlesAllConfirmation", editorHandlesAllConfirmation);
141246
builder.append("autoApproveUnmatchedTerminal", autoApproveUnmatchedTerminal);
247+
builder.append("autoApproveUnmatchedFileOp", autoApproveUnmatchedFileOp);
142248
builder.append("tools", tools);
143249
return builder.toString();
144250
}

0 commit comments

Comments
 (0)