Skip to content

Commit 32c416b

Browse files
committed
let subagents inherit all the session rules of main agent
1 parent d79186e commit 32c416b

6 files changed

Lines changed: 168 additions & 42 deletions

File tree

com.microsoft.copilot.eclipse.swtbot.test/test-plans/terminal-auto-approve/terminal-auto-approve.md

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -419,3 +419,98 @@ actions in multi-command scenario
419419
**"Always Allow 'hostname'"**.
420420
- `echo` does **NOT** appear — it is globally allowed.
421421
- "Allow all commands in this Session" is still shown.
422+
423+
---
424+
425+
## 13. Subagent inherits parent session approvals
426+
427+
### TC-013: Session approval in main agent applies to subagent calls
428+
429+
**Type:** `Edge Case`
430+
**Priority:** `P1`
431+
432+
#### Preconditions
433+
- No custom allow rules for `echo` or `cat` (only defaults).
434+
- "Auto approve commands not covered by rules" is **unchecked**.
435+
436+
#### Steps
437+
1. In Agent Mode, send a prompt that triggers `echo hello`.
438+
2. Confirmation dialog appears.
439+
3. Click dropdown and select **"Allow 'echo' in this Session"**.
440+
4. The command executes.
441+
5. In the **same conversation**, send a complex prompt that causes the
442+
agent to spawn a **subagent** (e.g., `use a subagent to run echo
443+
from subagent`).
444+
6. The subagent invokes `run_in_terminal` with an `echo` command.
445+
7. Observe that **no confirmation dialog appears** — the session
446+
approval from the main agent carries over to the subagent.
447+
8. Still in the subagent context, the subagent invokes a different
448+
command (e.g., `cat somefile.txt`).
449+
9. Observe that a **confirmation dialog appears**`cat` was not
450+
session-approved.
451+
452+
#### Expected Result
453+
- Subagent tool calls use the parent conversation's session rules.
454+
- Commands approved in the main agent conversation auto-approve in
455+
subagent context.
456+
- Commands NOT approved still require confirmation in subagent context.
457+
458+
#### 📸 Key Screenshots
459+
- [ ] Main agent: approving `echo` in session.
460+
- [ ] Subagent: `echo` auto-approved — no dialog.
461+
- [ ] Subagent: `cat` shows confirmation dialog.
462+
463+
---
464+
465+
### TC-014: Session approval in subagent carries back to main agent
466+
467+
**Type:** `Edge Case`
468+
**Priority:** `P1`
469+
470+
#### Preconditions
471+
- No custom allow rules for `hostname`.
472+
- "Auto approve commands not covered by rules" is **unchecked**.
473+
474+
#### Steps
475+
1. In Agent Mode, send a complex prompt that triggers a **subagent**.
476+
2. The subagent invokes `run_in_terminal` with `hostname`.
477+
3. Confirmation dialog appears.
478+
4. Click dropdown and select **"Allow 'hostname' in this Session"**.
479+
5. The command executes in the subagent context.
480+
6. After the subagent completes, continue in the **same conversation**
481+
with the main agent.
482+
7. Send a prompt that triggers `hostname` again (e.g., `what is my
483+
hostname?`).
484+
8. Observe that **no confirmation dialog appears** — the session
485+
approval made during the subagent call is shared with the main
486+
conversation.
487+
488+
#### Expected Result
489+
- Session approvals made during subagent execution are stored under
490+
the parent conversation's session scope.
491+
- The main agent benefits from approvals granted in subagent context.
492+
493+
#### 📸 Key Screenshots
494+
- [ ] Subagent: approving `hostname` in session.
495+
- [ ] Main agent: `hostname` auto-approved — no dialog.
496+
497+
---
498+
499+
### TC-015: "Allow all commands in this Session" in main agent covers subagent
500+
501+
**Type:** `Edge Case`
502+
**Priority:** `P2`
503+
504+
#### Steps
505+
1. In Agent Mode, trigger any terminal command.
506+
2. Confirmation dialog appears.
507+
3. Select **"Allow all commands in this Session"** from the dropdown.
508+
4. In the **same conversation**, send a prompt that spawns a subagent
509+
which runs multiple different terminal commands.
510+
5. Observe that **none of them** show a confirmation dialog — the
511+
blanket session approval covers subagent calls too.
512+
513+
#### Expected Result
514+
- "Allow all commands in this Session" is a blanket approval that
515+
applies to both main agent and subagent tool calls within the
516+
same conversation.

com.microsoft.copilot.eclipse.ui.test/src/com/microsoft/copilot/eclipse/ui/chat/confirmation/TerminalConfirmationHandlerTests.java

Lines changed: 29 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ void evaluate_autoApprovedWhenAllSubCommandsMatchAllowRules() {
108108
InvokeClientToolConfirmationParams params =
109109
buildParams(new String[]{"echo hello"}, new String[]{"echo"},
110110
"echo hello");
111-
ConfirmationResult result = handler.evaluate(params);
111+
ConfirmationResult result = handler.evaluate(params, CONV_ID);
112112

113113
assertTrue(result.isAutoApproved());
114114
}
@@ -121,7 +121,7 @@ void evaluate_needsConfirmationWhenDenyRuleMatches() {
121121
InvokeClientToolConfirmationParams params =
122122
buildParams(new String[]{"rm -rf /"}, new String[]{"rm"},
123123
"rm -rf /");
124-
ConfirmationResult result = handler.evaluate(params);
124+
ConfirmationResult result = handler.evaluate(params, CONV_ID);
125125

126126
assertFalse(result.isAutoApproved());
127127
assertNotNull(result.getContent());
@@ -135,7 +135,7 @@ void evaluate_needsConfirmationWhenNoRulesMatchAndUnmatchedFalse() {
135135
InvokeClientToolConfirmationParams params =
136136
buildParams(new String[]{"ls -la"}, new String[]{"ls"},
137137
"ls -la");
138-
ConfirmationResult result = handler.evaluate(params);
138+
ConfirmationResult result = handler.evaluate(params, CONV_ID);
139139

140140
assertFalse(result.isAutoApproved());
141141
}
@@ -148,7 +148,7 @@ void evaluate_autoApprovedWhenNoRulesMatchAndUnmatchedTrue() {
148148
InvokeClientToolConfirmationParams params =
149149
buildParams(new String[]{"ls -la"}, new String[]{"ls"},
150150
"ls -la");
151-
ConfirmationResult result = handler.evaluate(params);
151+
ConfirmationResult result = handler.evaluate(params, CONV_ID);
152152

153153
assertTrue(result.isAutoApproved());
154154
}
@@ -159,7 +159,7 @@ void evaluate_needsConfirmationWhenSubCommandsNull() {
159159

160160
InvokeClientToolConfirmationParams params =
161161
buildParams(null, null, "echo hello");
162-
ConfirmationResult result = handler.evaluate(params);
162+
ConfirmationResult result = handler.evaluate(params, CONV_ID);
163163

164164
assertFalse(result.isAutoApproved());
165165
}
@@ -170,7 +170,7 @@ void evaluate_needsConfirmationWhenSubCommandsEmpty() {
170170

171171
InvokeClientToolConfirmationParams params =
172172
buildParams(new String[]{}, null, "echo hello");
173-
ConfirmationResult result = handler.evaluate(params);
173+
ConfirmationResult result = handler.evaluate(params, CONV_ID);
174174

175175
assertFalse(result.isAutoApproved());
176176
}
@@ -182,7 +182,7 @@ void evaluate_emptyRulesUsesUnmatchedSetting() {
182182

183183
InvokeClientToolConfirmationParams params =
184184
buildParams(new String[]{"ls"}, new String[]{"ls"}, "ls");
185-
ConfirmationResult result = handler.evaluate(params);
185+
ConfirmationResult result = handler.evaluate(params, CONV_ID);
186186

187187
assertTrue(result.isAutoApproved());
188188
}
@@ -200,9 +200,9 @@ void persistDecision_acceptAllSession_autoApprovesSubsequent() {
200200

201201
ConfirmationAction allSession = buildSessionAction(
202202
TerminalConfirmationHandler.Action.ACCEPT_ALL_SESSION);
203-
handler.persistDecision(allSession, params);
203+
handler.persistDecision(allSession, params, CONV_ID);
204204

205-
ConfirmationResult result = handler.evaluate(params);
205+
ConfirmationResult result = handler.evaluate(params, CONV_ID);
206206
assertTrue(result.isAutoApproved());
207207
}
208208

@@ -217,9 +217,9 @@ void persistDecision_acceptNamesSession_autoApprovesMatchingNames() {
217217

218218
ConfirmationAction namesSession = buildSessionAction(
219219
TerminalConfirmationHandler.Action.ACCEPT_NAMES_SESSION);
220-
handler.persistDecision(namesSession, params);
220+
handler.persistDecision(namesSession, params, CONV_ID);
221221

222-
ConfirmationResult result = handler.evaluate(params);
222+
ConfirmationResult result = handler.evaluate(params, CONV_ID);
223223
assertTrue(result.isAutoApproved());
224224
}
225225

@@ -234,9 +234,9 @@ void persistDecision_acceptExactSession_autoApprovesMatchingCommand() {
234234

235235
ConfirmationAction exactSession = buildSessionAction(
236236
TerminalConfirmationHandler.Action.ACCEPT_EXACT_SESSION);
237-
handler.persistDecision(exactSession, params);
237+
handler.persistDecision(exactSession, params, CONV_ID);
238238

239-
ConfirmationResult result = handler.evaluate(params);
239+
ConfirmationResult result = handler.evaluate(params, CONV_ID);
240240
assertTrue(result.isAutoApproved());
241241
}
242242

@@ -251,11 +251,11 @@ void clearSession_removesApprovalsForConversation() {
251251

252252
ConfirmationAction allSession = buildSessionAction(
253253
TerminalConfirmationHandler.Action.ACCEPT_ALL_SESSION);
254-
handler.persistDecision(allSession, params);
254+
handler.persistDecision(allSession, params, CONV_ID);
255255

256256
handler.clearSession(CONV_ID);
257257

258-
ConfirmationResult result = handler.evaluate(params);
258+
ConfirmationResult result = handler.evaluate(params, CONV_ID);
259259
assertFalse(result.isAutoApproved());
260260
}
261261

@@ -270,11 +270,11 @@ void clearSession_doesNotAffectOtherConversation() {
270270

271271
ConfirmationAction allSession = buildSessionAction(
272272
TerminalConfirmationHandler.Action.ACCEPT_ALL_SESSION);
273-
handler.persistDecision(allSession, params);
273+
handler.persistDecision(allSession, params, CONV_ID);
274274

275275
handler.clearSession("other-conv");
276276

277-
ConfirmationResult result = handler.evaluate(params);
277+
ConfirmationResult result = handler.evaluate(params, CONV_ID);
278278
assertTrue(result.isAutoApproved());
279279
}
280280

@@ -288,7 +288,7 @@ void buildContent_alwaysHasAllowOnceAsPrimary() {
288288
InvokeClientToolConfirmationParams params =
289289
buildParams(new String[]{"echo"}, new String[]{"echo"},
290290
"echo hello");
291-
ConfirmationResult result = handler.evaluate(params);
291+
ConfirmationResult result = handler.evaluate(params, CONV_ID);
292292

293293
ConfirmationContent content = result.getContent();
294294
assertNotNull(content);
@@ -306,7 +306,7 @@ void buildContent_alwaysHasSkipAsDismiss() {
306306
InvokeClientToolConfirmationParams params =
307307
buildParams(new String[]{"echo"}, new String[]{"echo"},
308308
"echo hello");
309-
ConfirmationResult result = handler.evaluate(params);
309+
ConfirmationResult result = handler.evaluate(params, CONV_ID);
310310

311311
List<ConfirmationAction> actions = result.getContent().getActions();
312312
ConfirmationAction last = actions.get(actions.size() - 1);
@@ -321,7 +321,7 @@ void buildContent_hasAllowAllSessionAction() {
321321
InvokeClientToolConfirmationParams params =
322322
buildParams(new String[]{"echo"}, new String[]{"echo"},
323323
"echo hello");
324-
ConfirmationResult result = handler.evaluate(params);
324+
ConfirmationResult result = handler.evaluate(params, CONV_ID);
325325

326326
List<ConfirmationAction> actions = result.getContent().getActions();
327327
boolean hasAllSession = actions.stream().anyMatch(a ->
@@ -341,7 +341,7 @@ void buildContent_hasCommandNameActionsWhenNamesPresent() {
341341
InvokeClientToolConfirmationParams params =
342342
buildParams(new String[]{"echo"}, new String[]{"echo"},
343343
"echo hello");
344-
ConfirmationResult result = handler.evaluate(params);
344+
ConfirmationResult result = handler.evaluate(params, CONV_ID);
345345

346346
List<ConfirmationAction> actions = result.getContent().getActions();
347347
boolean hasNamesSession = actions.stream().anyMatch(a ->
@@ -363,7 +363,7 @@ void buildContent_hasExactCommandActionsWhenDifferentFromName() {
363363
InvokeClientToolConfirmationParams params =
364364
buildParams(new String[]{"echo"}, new String[]{"echo"},
365365
"echo hello");
366-
ConfirmationResult result = handler.evaluate(params);
366+
ConfirmationResult result = handler.evaluate(params, CONV_ID);
367367

368368
List<ConfirmationAction> actions = result.getContent().getActions();
369369
boolean hasExactSession = actions.stream().anyMatch(a ->
@@ -384,7 +384,7 @@ void buildContent_noExactActionsWhenSingleSubCommandEqualsName() {
384384
// commandLine equals the single commandName
385385
InvokeClientToolConfirmationParams params =
386386
buildParams(new String[]{"echo"}, new String[]{"echo"}, "echo");
387-
ConfirmationResult result = handler.evaluate(params);
387+
ConfirmationResult result = handler.evaluate(params, CONV_ID);
388388

389389
List<ConfirmationAction> actions = result.getContent().getActions();
390390
boolean hasExact = actions.stream().anyMatch(a ->
@@ -403,7 +403,7 @@ void buildContent_actionScopesAreCorrect() {
403403
InvokeClientToolConfirmationParams params =
404404
buildParams(new String[]{"echo"}, new String[]{"echo"},
405405
"echo hello");
406-
ConfirmationResult result = handler.evaluate(params);
406+
ConfirmationResult result = handler.evaluate(params, CONV_ID);
407407

408408
List<ConfirmationAction> actions = result.getContent().getActions();
409409

@@ -490,15 +490,15 @@ void buildContent_filtersSessionApprovedNamesFromActions() {
490490
handler.persistDecision(
491491
buildSessionAction(
492492
TerminalConfirmationHandler.Action.ACCEPT_NAMES_SESSION),
493-
approveParams);
493+
approveParams, CONV_ID);
494494

495495
// Now evaluate "echo hello && curl example.com"
496496
InvokeClientToolConfirmationParams params =
497497
buildParams(
498498
new String[]{"echo hello", "curl example.com"},
499499
new String[]{"echo", "curl"},
500500
"echo hello && curl example.com");
501-
ConfirmationResult result = handler.evaluate(params);
501+
ConfirmationResult result = handler.evaluate(params, CONV_ID);
502502

503503
assertFalse(result.isAutoApproved());
504504
List<ConfirmationAction> actions = result.getContent().getActions();
@@ -525,7 +525,7 @@ void buildContent_filtersGlobalApprovedNamesFromActions() {
525525
new String[]{"echo hello", "hostname"},
526526
new String[]{"echo", "hostname"},
527527
"echo hello && hostname");
528-
ConfirmationResult result = handler.evaluate(params);
528+
ConfirmationResult result = handler.evaluate(params, CONV_ID);
529529

530530
assertFalse(result.isAutoApproved());
531531
List<ConfirmationAction> actions = result.getContent().getActions();
@@ -552,7 +552,7 @@ void buildContent_allNamesApproved_autoApproves() {
552552
handler.persistDecision(
553553
buildSessionAction(
554554
TerminalConfirmationHandler.Action.ACCEPT_NAMES_SESSION),
555-
approveParams);
555+
approveParams, CONV_ID);
556556

557557
// Evaluate "echo hello && curl example.com"
558558
// echo = global allow, curl = session allow → all approved
@@ -561,7 +561,7 @@ void buildContent_allNamesApproved_autoApproves() {
561561
new String[]{"echo hello", "curl example.com"},
562562
new String[]{"echo", "curl"},
563563
"echo hello && curl example.com");
564-
ConfirmationResult result = handler.evaluate(params);
564+
ConfirmationResult result = handler.evaluate(params, CONV_ID);
565565

566566
assertTrue(result.isAutoApproved());
567567
}

com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/confirmation/ConfirmationHandler.java

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,20 +18,26 @@ public interface ConfirmationHandler {
1818
* Implementations should check both global rules and session memory.
1919
*
2020
* @param params the confirmation request parameters from CLS
21+
* @param sessionConversationId the conversation ID to use for session-scoped
22+
* lookups (may differ from params.getConversationId() when called from a
23+
* subagent context)
2124
* @return the confirmation result
2225
*/
23-
ConfirmationResult evaluate(InvokeClientToolConfirmationParams params);
26+
ConfirmationResult evaluate(InvokeClientToolConfirmationParams params,
27+
String sessionConversationId);
2428

2529
/**
2630
* Persists a user's decision based on the action scope.
2731
* SESSION actions are stored in-memory per conversation;
2832
* GLOBAL actions are written to preferences.
2933
*
3034
* @param action the user's selected action with metadata
31-
* @param params the original confirmation params (for conversationId etc.)
35+
* @param params the original confirmation params (for command data etc.)
36+
* @param sessionConversationId the conversation ID to use for session storage
3237
*/
3338
default void persistDecision(ConfirmationAction action,
34-
InvokeClientToolConfirmationParams params) {
39+
InvokeClientToolConfirmationParams params,
40+
String sessionConversationId) {
3541
// no-op by default
3642
}
3743

0 commit comments

Comments
 (0)