Skip to content

Commit 0c2062b

Browse files
authored
Redesign session MCP tools around user intent (#683)
* Redesign session MCP tools around user intent Replace mechanical terminal send/open actions with distinct command, workspace, and delegation tools whose names and fields match user intent. Keep schemas strict, routing trusted, and prompts, permissions, docs, and E2E fixtures aligned. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 7bf01f6f-59c6-43fa-b68d-ffa432fde56d * Simplify the terminal agent prompt Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 7bf01f6f-59c6-43fa-b68d-ffa432fde56d * Resolve unfamiliar command-like identifiers first Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 7bf01f6f-59c6-43fa-b68d-ffa432fde56d * Address Session MCP review feedback Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 7bf01f6f-59c6-43fa-b68d-ffa432fde56d * Align MCP text schemas with runtime validation Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 7bf01f6f-59c6-43fa-b68d-ffa432fde56d * Require dynamic Session MCP titles for hiding Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 7bf01f6f-59c6-43fa-b68d-ffa432fde56d * Clarify terminal action execution ownership Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 7bf01f6f-59c6-43fa-b68d-ffa432fde56d * Describe Session MCP actions by user outcome Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 7bf01f6f-59c6-43fa-b68d-ffa432fde56d * Finalize intent-based Session MCP actions Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 7bf01f6f-59c6-43fa-b68d-ffa432fde56d * Address Session MCP review feedback Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 7bf01f6f-59c6-43fa-b68d-ffa432fde56d * Clarify Session MCP workspace fields Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 7bf01f6f-59c6-43fa-b68d-ffa432fde56d * Use public delegation error wording Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 7bf01f6f-59c6-43fa-b68d-ffa432fde56d --------- Copilot-Session: 7bf01f6f-59c6-43fa-b68d-ffa432fde56d
1 parent afd2bea commit 0c2062b

20 files changed

Lines changed: 1616 additions & 420 deletions

AGENTS.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@ Agent or human CLI --> wta/wtcli --> COM IProtocolServer --> Windows Terminal
2525
using the same key share one process and multiplex sessions through it.
2626
- **WT Protocol** is the terminal-control boundary. `wtcli.exe` activates
2727
`IProtocolServer` through the package COM registration.
28-
- **Session MCP** exposes `terminal_send`, `terminal_open`,
29-
`terminal_open_and_send`, and `request_user_input`.
28+
- **Session MCP** exposes `run_command_in_current_shell`, `create_workspace`,
29+
`delegate_task_in_new_workspace`, and `request_user_input`.
3030
It routes requests to the owning helper and never executes terminal actions
3131
itself.
3232
- Agent panes are ordinary `ConptyConnection` panes hosting `wta-helper`; C++

doc/release-check-list.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,9 @@ Net effect: UT shrinks the manual matrix to "did the wiring and UI connect", not
203203
- [ ] `C260` `[new]` `[UT✓]` `[E2E]` **Leaving BYOK restarts and restores cloud models:** Clearing the custom model selection rebuilds the shared agent stack, reconnects Copilot without provider overrides, and restores its native cloud catalog. _(UT: clean cloud discovery and settings rebuild tests; #447; E2E: `Feature.ByokProvider`.)_
204204
- [ ] `C261` `[new]` `[UT✓]` `[E2E]` **Compact height keeps the recommendation and input usable:** At the real Agent Pane splitter minimum, the selected recommendation summary, Run/Insert actions, and editable input remain visible, and Insert still targets the owning shell without discarding its draft. _(UT: compact action-panel planning, recommendation rendering, and minimum-size tests; #580; E2E: `Feature.AgentCompactLayout`.)_
205205
- [ ] `C258` `[new]` `[UT✓]` `[E2E]` **Proposal MCP routing is isolated per tab:** Each ACP session receives a distinct proposal MCP server identity, and tool calls route only to that session's owning Helper even after another tab connects. _(UT: `server_configs_isolate_session_identity_and_capability`, proposal MCP capability routing tests; #560; E2E: `Feature.ProposalMcpRouting`.)_
206-
- [ ] `C284` `[new]` `[UT✓]` `[E2E]` **Tab-targeted terminal actions accept a direction hint across Session MCP:** A validated `open_and_send` request may retain a direction hint when its destination is a new tab. _(#599; E2E: `Feature.AgentProtocolExperience`.)_
206+
- [ ] `C284` `[new]` `[UT✓]` `[E2E]` **New-tab command workspaces accept a split-direction hint across Session MCP:** A validated `create_workspace` request with a command may retain a `split_direction` hint when its placement is `new_tab`. _(#599; E2E: `Feature.AgentProtocolExperience`.)_
207+
- [ ] `C285` `[new]` `[UT✓]` `[E2E]` **Empty workspaces open without sending a command across Session MCP:** After confirmation, `create_workspace` with `command` omitted creates a new empty terminal tab without typing the proposal summary into its shell. _(#683; E2E: `Feature.AgentProtocolExperience`.)_
208+
- [ ] `C286` `[new]` `[UT✓]` `[E2E]` **Delegated tasks reach the configured agent in a new workspace across Session MCP:** After confirmation, `delegate_task_in_new_workspace` opens a new terminal tab with the configured delegate command and passes it the exact requested task. _(#683; E2E: `Feature.AgentProtocolExperience`.)_
207209
- [x] `C081` `[UT✓]` **Unknown slash command is safe:** Unknown `/command` does not lose user input or crash.
208210
- [ ] `C225` `[E2E]` **`/agent` picker works:** `/agent` opens a keyboard-operable picker containing the current installed/allowed agents, and selecting the current agent is a safe no-op.
209211
- [ ] `C241` `[new]` `[E2E]` **`/agent` completion selection is safe:** Enter activates the highlighted matching agent without rebuilding the pane or changing the global default when it is already selected. _(#487; E2E: `Feature.PerTabAgent`.)_

doc/specs/WTA-terminal-action-proposals.md

Lines changed: 63 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -13,19 +13,19 @@ and every eligible ACP session receives a distinct bearer capability:
1313

1414
```text
1515
ACP session
16-
-> HTTP MCP: intellterm_<public-id>/{terminal_send,terminal_open,terminal_open_and_send,request_user_input}
16+
-> HTTP MCP: intellterm_<public-id>/{run_command_in_current_shell,create_workspace,delegate_task_in_new_workspace,request_user_input}
1717
-> wta-master capability -> ACP SessionId
1818
-> session_to_helper -> existing master/helper ACP pipe
1919
-> owning Helper
20-
-> terminal_* -> recommendation card -> wtcli/COM executor
20+
-> action tool -> recommendation card -> wtcli/COM executor
2121
-> request_user_input -> blocking choice/freeform modal -> structured answer
2222
```
2323

2424
The endpoint presents typed terminal actions for review and blocking
2525
clarification questions. It cannot read or mutate Windows Terminal. The
2626
existing card confirmation is the sole mutation boundary.
2727

28-
The `terminal_*` tools return as soon as the Helper commits its card;
28+
The three action tools return as soon as the Helper commits its card;
2929
confirmation or cancellation then happens independently. `request_user_input`
3030
deliberately keeps the MCP call open until the user answers, cancels, the
3131
caller disconnects, or the ten-minute timeout expires.
@@ -90,46 +90,82 @@ Server name:
9090
intellterm_<public-id>
9191
```
9292

93-
Tools:
93+
Tools (four total):
9494

9595
```text
96-
terminal_send
97-
terminal_open
98-
terminal_open_and_send
96+
run_command_in_current_shell
97+
create_workspace
98+
delegate_task_in_new_workspace
9999
request_user_input
100100
```
101101

102+
The first three are terminal-action proposal tools. `request_user_input` is a
103+
separate blocking clarification tool and does not create a terminal-action
104+
proposal.
105+
102106
The server supports MCP `initialize`, `ping`, `tools/list`, and `tools/call`
103107
over stateless Streamable HTTP JSON-RPC. POST responses use JSON or HTTP 202
104108
for notifications; GET and DELETE return 405 because server-initiated streams
105109
are unnecessary. It exposes no terminal read or execution tools.
106110

107-
Input, for `terminal_send`:
111+
Input, for `run_command_in_current_shell`:
108112

109113
```json
110114
{
111-
"title": "Run tests",
112-
"rationale": "Verify the current change.",
113-
"input": "cargo test"
115+
"summary": "Run tests",
116+
"reason": "Verify the current change.",
117+
"command": "cargo test"
114118
}
115119
```
116120

117-
Each MCP call proposes exactly one action. One tool per action shape, rather
118-
than a single tool with a `type` discriminator, so each schema advertises
119-
exactly the fields that action accepts and `additionalProperties: false`
120-
rejects a field belonging to another action. The action tools are:
121-
122-
- `terminal_send`: submit input to the trusted active pane;
123-
- `terminal_open`: open an empty tab or panel;
124-
- `terminal_open_and_send`: open a tab or panel and submit input there.
121+
Each terminal-action MCP call proposes exactly one user-visible terminal
122+
outcome. The tools separate current-shell execution, workspace creation, and
123+
delegation rather than exposing a mechanical action discriminator. Each
124+
schema advertises exactly the fields that intent accepts, and
125+
`additionalProperties: false` rejects fields belonging to another intent. The
126+
action tools are:
127+
128+
- `run_command_in_current_shell`: propose one shell command for the trusted
129+
active pane;
130+
- `create_workspace`: create a new terminal workspace, optionally initialized
131+
with one shell command;
132+
- `delegate_task_in_new_workspace`: start the configured delegate agent in a
133+
new workspace with a self-contained task.
134+
135+
The exact public payloads are:
136+
137+
| Tool | Required | Optional |
138+
|---|---|---|
139+
| `run_command_in_current_shell` | `summary`, `command` | `reason` |
140+
| `create_workspace` | `summary`, `placement` | `reason`, `command`, `working_directory`, `split_direction`, `profile` |
141+
| `delegate_task_in_new_workspace` | `summary`, `task`, `placement` | `reason`, `working_directory`, `split_direction` |
142+
143+
`placement` is `new_tab` or `new_split`. `split_direction` is `right`, `left`,
144+
`up`, `down`, or `auto`; it affects only `new_split` and is accepted but
145+
ignored for `new_tab` compatibility. `create_workspace.command` is optional:
146+
omitting it creates an empty workspace, while supplying it runs that command
147+
after creation. `delegate_task_in_new_workspace` deliberately exposes neither
148+
a profile nor an agent selector. The Helper always substitutes the configured
149+
delegate agent and rejects the request when no valid delegate is configured. A
150+
model cannot name an arbitrary agent.
151+
152+
The Helper maps these public intents into the existing internal action model:
153+
154+
| Public tool | Internal action |
155+
|---|---|
156+
| `run_command_in_current_shell` | `RecommendedAction::Send` |
157+
| `create_workspace` without `command` | `RecommendedAction::Open` |
158+
| `create_workspace` with `command` | `RecommendedAction::OpenAndSend` with delegation disabled |
159+
| `delegate_task_in_new_workspace` | `RecommendedAction::OpenAndSend` with delegation enabled and no profile |
125160

126-
The open tools may include `cwd`, `profile`, and panel `direction`. The
127-
user-facing `title` also becomes the requested destination title.
128-
`terminal_open_and_send` may set `delegate: true`; the Helper substitutes the
129-
configured delegate agent. A model cannot name an arbitrary agent.
161+
`new_tab` maps to the existing `Tab` target and `new_split` maps to `Panel`.
162+
The user-facing `summary` becomes the recommendation title and, for workspace
163+
tools targeting `new_tab`, the initial tab title. `working_directory`,
164+
`split_direction`, and `profile` map to the existing internal fields without
165+
changing trusted active-pane routing.
130166

131-
Autofix uses the same tools but the Helper supplies the trusted Autofix origin
132-
and requires a `terminal_send` action.
167+
Autofix uses only `run_command_in_current_shell`; the Helper supplies the
168+
trusted Autofix origin and requires the resulting internal `Send` action.
133169

134170
Tool result statuses:
135171

@@ -195,8 +231,8 @@ execution path.
195231
Permission remains an optional compatibility preflight. Some agents call MCP
196232
without requesting permission.
197233

198-
When an adapter requests permission for one of the
199-
`intellterm_<public-id>/terminal_*` action tools, the Helper:
234+
When an adapter requests permission for one of the three terminal-action tools
235+
advertised by an `intellterm_<public-id>` server, the Helper:
200236

201237
1. verifies the trusted ACP SessionId owns the current issued turn;
202238
2. silently selects `AllowOnce`; and

doc/specs/llm-agent-event-integration.md

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -446,16 +446,22 @@ exit $EXIT_CODE
446446
447447
### Pattern C: Native ACP hooks (zero-effort for ACP agents)
448448
449-
Agents that speak ACP already have structured tool-use lifecycle. The ACP host
450-
(`wta` TUI mode) can emit events on their behalf — no agent modification needed:
449+
ACP agents can call the Session MCP terminal-action tools, but this path is a
450+
proposal handoff rather than an immediate execution/completion lifecycle:
451451
452452
```
453-
ACP agent calls tool "run_command"
454-
→ wta emits agent.tool.invoked {tool: "run_command", args_summary: "cargo test"}
455-
→ wta executes in pane
456-
→ wta emits agent.tool.completed {tool: "run_command", exit_code: 0, duration_ms: 4200}
453+
ACP agent calls tool "run_command_in_current_shell"
454+
→ Session MCP validates and routes the request to the owning Helper
455+
→ Helper creates and commits a confirmation card
456+
→ MCP returns accepted
457+
→ user later confirms or cancels the card
458+
→ on confirmation, the existing terminal executor runs the action
457459
```
458460
461+
`agent.tool.invoked` can represent the MCP call and proposal handoff. WTA
462+
currently receives no executor completion callback carrying an exit code or
463+
duration, so this path cannot emit `agent.tool.completed`.
464+
459465
### Pattern D: MCP tool (for MCP-connected agents)
460466
461467
Expose `send_event` as an MCP tool so agents calling `wta mcp` can publish events

test/e2e/README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ authenticated ACP agents. Current status (run on the Store package):
1919
| `Feature.FreFlow.Tests.ps1` | §0 FRE overlay click-through (Next→Save, privacy link, close-safety) | 5 |
2020
| `Feature.FreExecutionPolicy.Tests.ps1` | §0 FRE execution-policy verdict (deterministic via registry; **Dev**, auto-skips) | 3 (1 conditional skip) |
2121
| `Feature.AgentPaneInteraction.Tests.ps1` | open/hide/focus, input/rendering, slash, Copilot chat | 14 |
22-
| `Feature.AgentProtocolExperience.Tests.ps1` | PRs #599/#601/#606/#610/#611/#612/#616/#634: terminal actions, ACP tool/transcript rendering, clarification input, session configuration, model title, and replacement cleanup across the deployed helper/master boundary | 6 |
22+
| `Feature.AgentProtocolExperience.Tests.ps1` | PRs #599/#601/#606/#610/#611/#612/#616/#634/#683: intent-based terminal actions (including empty workspaces and configured delegation), ACP tool/transcript rendering, clarification input, session configuration, model title, and replacement cleanup across the deployed helper/master boundary | 8 |
2323
| `Feature.AgentImageAttachmentEditing.Tests.ps1` | PR #536: inline image tokens move and delete atomically while preserving adjacent prompt text | 1 |
2424
| `Feature.AgentModelSync.Tests.ps1` | PR #538: ACP config-option updates replace stale session model state in the active picker | 1 |
2525
| `Feature.AgentModelLifecycle.Tests.ps1` | PR #554: `/model` hot-apply and Settings-driven model restart/reconnect lifecycle | 2 |
@@ -54,11 +54,11 @@ authenticated ACP agents. Current status (run on the Store package):
5454
| `Feature.AgentChat.Tests.ps1` / `Feature.AgentPopup.Tests.ps1` | agent chat + `/` popup/menu interaction | 1 + 3 |
5555
| `Feature.AgentPaneMove.Tests.ps1` | PR #429: `/move` stays per-tab, preserves global position, and restores agent input focus | 1 |
5656

57-
**Coverage: 139 of 141 automatable `[E2E]` checklist items are implemented.**
58-
**Test status: 124 baseline feature cases pass + 3 documented skips** (`wta sessions list` is
57+
**Coverage: 141 of 143 automatable `[E2E]` checklist items are implemented.**
58+
**Test status: 126 baseline feature cases pass + 3 documented skips** (`wta sessions list` is
5959
identity-gated — see `Feature.SessionList.Tests.ps1`), plus 2 PR #481 WSL-backend cases and 2
6060
PR #488 delegate-source cases that run only when a runnable distro (and, for the #481 chat
61-
case, an installed+authenticated native agent) is available. The 139 implemented checklist
61+
case, an installed+authenticated native agent) is available. The 141 implemented checklist
6262
items map to the baseline cases plus the deterministic settings/persistence assertions. The
6363
remaining new items are the two profile agent picker UIs; they stay explicit E2E work rather
6464
than being falsely credited by the JSON-level runtime tests. Other

test/e2e/fixtures/Mock-AcpInteractionAgent.ps1

Lines changed: 53 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -104,10 +104,12 @@ function Invoke-UserInputTool {
104104
Invoke-RestMethod -Method Post -Uri $Server.url -Headers $headers -ContentType 'application/json' -Body $body
105105
}
106106

107-
function Invoke-TerminalActionTool {
107+
function Invoke-SessionMcpTool {
108108
param(
109109
[Parameter(Mandatory)]$Server,
110-
[Parameter(Mandatory)][string]$Marker
110+
[Parameter(Mandatory)][string]$Name,
111+
[Parameter(Mandatory)][hashtable]$Arguments,
112+
[Parameter(Mandatory)][int]$Id
111113
)
112114

113115
$headers = @{ 'mcp-protocol-version' = '2025-06-18' }
@@ -118,16 +120,11 @@ function Invoke-TerminalActionTool {
118120
}
119121
$body = @{
120122
jsonrpc = '2.0'
121-
id = 2
123+
id = $Id
122124
method = 'tools/call'
123125
params = @{
124-
name = 'terminal_open_and_send'
125-
arguments = @{
126-
title = "Direction $Marker"
127-
input = "echo $Marker"
128-
target = 'tab'
129-
direction = 'auto'
130-
}
126+
name = $Name
127+
arguments = $Arguments
131128
}
132129
} | ConvertTo-Json -Depth 12 -Compress
133130
Invoke-RestMethod -Method Post -Uri $Server.url -Headers $headers -ContentType 'application/json' -Body $body
@@ -157,8 +154,9 @@ while ($null -ne ($line = [Console]::In.ReadLine())) {
157154
'session/new' {
158155
$sessionCounter++
159156
$sessionId = "interaction-$PID-$sessionCounter"
160-
$sessionMcpServers[$sessionId] = @($request.params.mcpServers) | Select-Object -First 1
161-
Write-FixtureLog -Message "session/new|$sessionId"
157+
$server = @($request.params.mcpServers) | Select-Object -First 1
158+
$sessionMcpServers[$sessionId] = $server
159+
Write-FixtureLog -Message "session/new|$sessionId|mcp_server=$([string]$server.name)"
162160
Send-AcpMessage @{
163161
jsonrpc = '2.0'
164162
id = $request.id
@@ -282,7 +280,12 @@ while ($null -ne ($line = [Console]::In.ReadLine())) {
282280
throw 'session/new did not provide a Session MCP server'
283281
}
284282
$marker = $Matches.marker
285-
$response = Invoke-TerminalActionTool -Server $server -Marker $marker
283+
$response = Invoke-SessionMcpTool -Server $server -Name 'create_workspace' -Id 2 -Arguments @{
284+
summary = "Direction $marker"
285+
command = "echo $marker"
286+
placement = 'new_tab'
287+
split_direction = 'auto'
288+
}
286289
$result = $response.result.structuredContent | ConvertTo-Json -Depth 20 -Compress
287290
Write-FixtureLog -Message "tab-direction-result|$result"
288291
Send-AcpMessage @{
@@ -291,6 +294,43 @@ while ($null -ne ($line = [Console]::In.ReadLine())) {
291294
result = @{ stopReason = 'end_turn' }
292295
}
293296
}
297+
elseif ($promptText -match 'EMPTY_WORKSPACE_(?<marker>[A-F0-9]+)') {
298+
$server = $sessionMcpServers[$sessionId]
299+
if (-not $server) {
300+
throw 'session/new did not provide a Session MCP server'
301+
}
302+
$marker = $Matches.marker
303+
$response = Invoke-SessionMcpTool -Server $server -Name 'create_workspace' -Id 3 -Arguments @{
304+
summary = "Empty workspace EMPTY_COMMAND_SENTINEL_$marker"
305+
placement = 'new_tab'
306+
}
307+
$result = $response.result.structuredContent | ConvertTo-Json -Depth 20 -Compress
308+
Write-FixtureLog -Message "empty-workspace-result|$result"
309+
Send-AcpMessage @{
310+
jsonrpc = '2.0'
311+
id = $request.id
312+
result = @{ stopReason = 'end_turn' }
313+
}
314+
}
315+
elseif ($promptText -match 'DELEGATE_WORKSPACE_(?<marker>[A-F0-9]+)') {
316+
$server = $sessionMcpServers[$sessionId]
317+
if (-not $server) {
318+
throw 'session/new did not provide a Session MCP server'
319+
}
320+
$marker = $Matches.marker
321+
$response = Invoke-SessionMcpTool -Server $server -Name 'delegate_task_in_new_workspace' -Id 4 -Arguments @{
322+
summary = "Delegate workspace $marker"
323+
task = "DELEGATED_TASK_$marker"
324+
placement = 'new_tab'
325+
}
326+
$result = $response.result.structuredContent | ConvertTo-Json -Depth 20 -Compress
327+
Write-FixtureLog -Message "delegate-workspace-result|$result"
328+
Send-AcpMessage @{
329+
jsonrpc = '2.0'
330+
id = $request.id
331+
result = @{ stopReason = 'end_turn' }
332+
}
333+
}
294334
else {
295335
Send-TextUpdate -SessionId $sessionId -Text "ACK:$promptText"
296336
Send-AcpMessage @{

test/e2e/fixtures/Mock-AcpProposalAgent.ps1

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,10 @@ function Invoke-ProposalTool {
3737
id = 1
3838
method = 'tools/call'
3939
params = @{
40-
name = 'terminal_send'
40+
name = 'run_command_in_current_shell'
4141
arguments = @{
42-
title = "Run echo $Marker"
43-
input = "echo $Marker"
42+
summary = "Run echo $Marker"
43+
command = "echo $Marker"
4444
}
4545
}
4646
} | ConvertTo-Json -Depth 12 -Compress

0 commit comments

Comments
 (0)