Redesign session MCP tools around user intent - #683
Conversation
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
There was a problem hiding this comment.
Pull request overview
This PR redesigns the Session MCP surface in WTA from mechanically shaped terminal_* actions to intent-based tools (run_command, open_workspace, run_command_in_workspace, delegate_task) while preserving the trusted routing/confirmation boundary between agents and the user’s terminal.
Changes:
- Replaces legacy
terminal_send/terminal_open/terminal_open_and_sendnaming across prompts, docs, unit tests, and E2E fixtures with intent-based tool names and payloads. - Updates WTA’s ACP client and Session MCP plumbing to recognize the new tool set and tighten silent-approval handling around trusted server-qualified tool names.
- Extends/adjusts schema validation and coverage tests to enforce the new flat payload contract and reject removed tool names.
Reviewed changes
Copilot reviewed 17 out of 17 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| tools/wta/src/protocol/acp/prompt.rs | Updates embedded prompt tests to assert intent-tool naming and absence of removed tool names. |
| tools/wta/src/protocol/acp/mock_agent_tests.rs | Updates mock ACP test to reference run_command tool name. |
| tools/wta/src/protocol/acp/client.rs | Updates Session MCP tool enumeration, permission handling trust logic, and associated tests for new tool names/payloads. |
| tools/wta/src/master/session_mcp.rs | Adjusts tool-name length-budget test for the new longest action name. |
| tools/wta/src/agent_tools/session_mcp.rs | Updates tool listing expectations; adds tests rejecting removed tools and preserving arguments at MCP boundary. |
| tools/wta/src/agent_tools/action_proposal/schema.rs | Introduces intent-specific flat payload schemas, validation, and mapping into existing internal action model; expands schema/tests accordingly. |
| tools/wta/prompts/terminal-agent.md | Rewrites agent guidance to select actions by intent (run_command/workspace/delegation). |
| tools/wta/prompts/auto-fix.md | Updates autofix handoff to require exactly one run_command call. |
| tools/wta/OVERVIEW.md | Updates overview documentation to list new Session MCP tool names. |
| tools/wta/AGENTS.md | Updates WTA agent documentation to list new Session MCP tool names. |
| test/e2e/tests/Feature.AgentProtocolExperience.Tests.ps1 | Renames and updates E2E test text to reflect run_command_in_workspace/split_direction semantics. |
| test/e2e/README.md | Updates E2E suite description to reference intent-based terminal actions. |
| test/e2e/fixtures/Mock-AcpProposalAgent.ps1 | Updates proposal mock fixture to call run_command with summary/command. |
| test/e2e/fixtures/Mock-AcpInteractionAgent.ps1 | Updates interaction mock fixture to call run_command_in_workspace with new arguments. |
| doc/specs/WTA-terminal-action-proposals.md | Updates spec to document the new tool set, payload tables, and internal mapping. |
| doc/release-check-list.md | Updates release checklist item wording to match the new tool contract and fields. |
| AGENTS.md | Updates repo-level agent architecture doc to list the new Session MCP tool names. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 7bf01f6f-59c6-43fa-b68d-ffa432fde56d
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 17 out of 17 changed files in this pull request and generated 1 comment.
Suppressed comments (2)
tools/wta/src/protocol/acp/client.rs:1231
request_permissionintends to silently approve only tool calls whose permission title contains the validated per-session MCP server name, butsession_mcp_tool_from_titlecurrently treats the staticintelligent_terminal/<tool>form as a valid server name (viaserver_name_matches). That means an unrelated provider tool could set its permission title tointelligent_terminal/run_commandand potentially bypass the normal permission UI.
let session_mcp_tool = session_mcp_tool_from_title(args.tool_call.fields.title.as_deref())
.or_else(|| self.hidden_session_mcp_tool(&session_id, &tool_call_id));
tools/wta/src/agent_tools/action_proposal/schema.rs:562
- If
reasonis rejected when it is the empty string, the advertised JSON schema should also require a non-empty value so agents can rely on the schema as the sole authority.
fn mcp_reason_property() -> serde_json::Value {
serde_json::json!({
"type": "string",
"maxLength": MAX_RATIONALE_CHARS,
"description": "Why the operation is needed. Omit when the summary is sufficient."
})
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 7bf01f6f-59c6-43fa-b68d-ffa432fde56d
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 7bf01f6f-59c6-43fa-b68d-ffa432fde56d
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 18 out of 18 changed files in this pull request and generated 1 comment.
Suppressed comments (6)
tools/wta/src/protocol/acp/client.rs:1194
request_permissionsilently resolves permissions based on a title match, andserver_name_matchestreats the static aliasintelligent_terminalas a valid session MCP server name. That means an agent can spoofUse MCP tool: intelligent_terminal/<tool>in a permission title and potentially bypass the normal permission UI even when the request is not actually for the per-session MCP server. Consider restricting silent approval to per-sessionintellterm_<id>-style names (and still allowing hidden-call correlation), so only the session-scoped identity can trigger auto-approval.
// bare `run_command` from an unrelated provider tool must not bypass
// the normal permission UI merely because its payload has the same
// shape.
let session_mcp_tool = session_mcp_tool_from_title(args.tool_call.fields.title.as_deref())
.or_else(|| self.hidden_session_mcp_tool(&session_id, &tool_call_id));
tools/wta/src/agent_tools/action_proposal/schema.rs:578
- The schema for optional
reasonallows whitespace-only text (minLength: 1), but runtime rejects it when present (validate_mcp_optional_nonempty_text). Adding a non-whitespace pattern keeps the schema and runtime behavior aligned.
serde_json::json!({
"type": "string",
"minLength": 1,
"maxLength": MAX_RATIONALE_CHARS,
"description": "Why the operation is needed. Omit when the summary is sufficient."
tools/wta/src/agent_tools/action_proposal/schema.rs:587
commandis runtime-validated to reject whitespace-only values, but the schema currently permits them viaminLength: 1. Adding a non-whitespace pattern prevents schema-valid-but-runtime-invalid tool calls.
serde_json::json!({
"type": "string",
"minLength": 1,
"maxLength": MAX_INPUT_CHARS,
"description": format!("Exact shell command to run {destination}.")
tools/wta/src/agent_tools/action_proposal/schema.rs:603
working_directoryis runtime-validated to reject whitespace-only values when present, but the schema currently allows them. Adding a non-whitespace pattern keeps the schema consistent with runtime validation.
"type": "string",
"minLength": 1,
"maxLength": MAX_INPUT_CHARS,
"description": "Working directory for the new workspace. Omit to use the terminal's default behavior."
},
tools/wta/src/agent_tools/action_proposal/schema.rs:620
profileis runtime-validated to reject whitespace-only values when present, but the schema currently allows them. Adding a non-whitespace pattern keeps the advertised schema aligned with runtime behavior.
serde_json::json!({
"type": "string",
"minLength": 1,
"maxLength": MAX_TITLE_CHARS,
"description": "Terminal profile to use for the new workspace."
tools/wta/src/agent_tools/action_proposal/schema.rs:665
taskis runtime-validated to reject whitespace-only values, but its schema currently allows them (minLength: 1). Adding a non-whitespace pattern prevents schema-valid payloads from being rejected at runtime.
"type": "string",
"minLength": 1,
"maxLength": MAX_INPUT_CHARS,
"description": "Self-contained task for the configured delegate agent, including the goal, relevant context, constraints, and completion criteria."
}),
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 7bf01f6f-59c6-43fa-b68d-ffa432fde56d
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 18 out of 18 changed files in this pull request and generated 1 comment.
Suppressed comments (3)
Previously missed (1) — in code that hasn't changed since the last review.
tools/wta/src/agent_tools/session_mcp.rs:311
- This test asserts the user-input schema via a hard-coded tool index (
/result/tools/4), which makes it brittle if tool ordering changes (even when the contract is still correct). It’s more robust to find the tool object byname == USER_INPUT_TOOL_NAMEand then read itsinputSchema.
assert!(!names.contains(&"request_terminal_actions"));
let user_input_schema = response
.pointer("/result/tools/4/inputSchema")
.expect("user input schema");
tools/wta/src/protocol/acp/client.rs:1194
- The silent-approval path trusts
session_mcp_tool_from_title(...), which currently recognizesUse MCP tool: intelligent_terminal/<tool>as a valid Session MCP title (seeserver_name_matches). Because permission titles are agent-authored, this lets a tool permission be spoofed to bypass the normal permission UI. Consider only treating titles that include the per-sessionintellterm_...server name as eligible for silent approval, and rely on the hidden-call correlation for everything else.
// Permission titles are agent-authored. Only a title carrying the
// validated per-session MCP server name, or a tool call already
// correlated through the hidden-call map, is trusted for silent
// approval. The public action names are intentionally generic and a
// bare `run_command` from an unrelated provider tool must not bypass
// the normal permission UI merely because its payload has the same
// shape.
let session_mcp_tool = session_mcp_tool_from_title(args.tool_call.fields.title.as_deref())
.or_else(|| self.hidden_session_mcp_tool(&session_id, &tool_call_id));
tools/wta/src/protocol/acp/client.rs:1519
- Same concern as the ToolCall branch:
ToolCallUpdatecan be hidden based on an agent-authored title that matchessession_mcp_tool_from_title. If you keep supporting the stableintelligent_terminal/...permission-title shapes for compatibility, consider not using them for auto-hiding transcript tool calls unless the per-sessionintellterm_...prefix is present or the tool_call_id was already correlated.
acp::schema::v1::SessionUpdate::ToolCallUpdate(update) => {
let tool_call_id = update.tool_call_id.to_string();
if let Some(tool) = session_mcp_tool_from_title(update.fields.title.as_deref()) {
self.hide_session_mcp_tool_call(&sid, &tool_call_id, tool);
return Ok(());
}
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 7bf01f6f-59c6-43fa-b68d-ffa432fde56d
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 18 out of 18 changed files in this pull request and generated no new comments.
Suppressed comments (1)
tools/wta/src/protocol/acp/client.rs:1204
- This comment says the title must carry the “validated per-session MCP server name”, but
server_name_matchesalso accepts the legacy stable aliasintelligent_terminal(not per-session). Either the comment should mention the legacy alias, or the logic should be tightened to require the per-sessionintellterm_<id>name to match the wording.
// Permission titles are agent-authored. Only a title carrying the
// validated per-session MCP server name, or a tool call already
// correlated through the hidden-call map, is trusted for silent
// approval. The public action names are intentionally generic and a
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 18 out of 18 changed files in this pull request and generated no new comments.
Suppressed comments (1)
tools/wta/src/protocol/acp/client.rs:1209
- This still treats an agent-authored title as trusted identity.
server_name_matchesonly validates the spelling ofintellterm_<20 hex>(and also accepts the stableintelligent_terminalalias); it does not verify that this is the MCP server name assigned tosession_id. An unrelated provider tool can therefore use a title such asintellterm_00000000000000000000/run_command, receive silentAllowOnce, and have its call hidden, bypassing the normal permission UI. Please compare against the exact server name bound to this ACP session, or silently approve only a call ID established through trusted Session MCP correlation.
let session_mcp_tool = session_mcp_tool_from_title(args.tool_call.fields.title.as_deref())
.or_else(|| self.hidden_session_mcp_tool(&session_id, &tool_call_id));
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 7bf01f6f-59c6-43fa-b68d-ffa432fde56d
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 18 out of 18 changed files in this pull request and generated 1 comment.
Suppressed comments (1)
Previously missed (1) — in code that hasn't changed since the last review.
tools/wta/prompts/terminal-agent.md:34
- The Command Resolver section is not always injected:
command_resolver_invocationreturnsNonefor supported planner contexts with no applicable source (for examplewsl:Ubuntu, as asserted inprompt_context.rs:1044-1049). In those sessions this instruction requires a nonexistent invocation and provides no fallback, so unfamiliar-command requests are left without an executable grounding path. Make resolver use conditional and restore a shell-native read-only fallback.
If the user asks how to use, run, install, find, or troubleshoot an unfamiliar command-like identifier, use the exact injected **Command Resolver Invocation** before guessing, asking what tool it belongs to, or searching configuration files. Skip resolution only when context clearly identifies the name as a setting, file, API, or code symbol. Interpret the resolver status literally and do not run the final command merely to test it. Command-resolution probes are investigation, not user-visible actions.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 7bf01f6f-59c6-43fa-b68d-ffa432fde56d
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 18 out of 18 changed files in this pull request and generated no new comments.
Suppressed comments (3)
Previously missed (3) — in code that hasn't changed since the last review.
tools/wta/src/agent_tools/action_proposal/schema.rs:589
- The advertised schema accepts payloads that the MCP path rejects before parsing. For example, a
run_commandwith a 200-charactersummaryand an 8,000-character ASCIIcommandsatisfies this schema but serializes to 8,227 bytes, exceedingMAX_PAYLOAD_BYTES(8,192); non-ASCII input diverges even sooner. Since the prompts tell agents the schema is the sole authority, align the field limits and byte cap so every schema-valid combination is transportable, and add a boundary regression test.
"maxLength": MAX_INPUT_CHARS,
tools/wta/src/agent_tools/action_proposal/schema.rs:570
summaryis described as only the user-visible operation summary, but every workspace parser also copies it into the internal actiontitle;coordinator.rs:484-488and596-600then pass that value towt_create_tabas the destination tab title. This gives one public field two observable meanings, contrary to the PR's single-meaning contract, and prevents callers from choosing a card summary independently of the tab name. Keep the summary only on the recommendation or expose a separately described workspace-title field.
"description": "Concise user-visible summary of the proposed operation."
tools/wta/src/agent_tools/action_proposal/schema.rs:611
placementcan benew_tab, but the internal action contract explicitly ignores direction for tab targets (coordinator.rs:80-82,96-97). Because the parser intentionally accepts this combination and agents are told the schema is authoritative, the description must state that this field only affectsnew_split; otherwise a schema-valid value appears to control a new tab while having no effect.
"description": "Preferred split direction hint; auto lets Intelligent Terminal choose."
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 7bf01f6f-59c6-43fa-b68d-ffa432fde56d
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 7bf01f6f-59c6-43fa-b68d-ffa432fde56d
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 20 out of 20 changed files in this pull request and generated 2 comments.
Suppressed comments (1)
tools/wta/src/protocol/acp/client.rs:1206
session_mcp_tool_from_titledoes not actually validate the namespace against this session's advertised MCP server:server_name_matchesaccepts any syntactically validintellterm_<16 hex>value (andintelligent_terminal). Because the title is agent-authored, an unrelated provider tool can use one of these titles while a proposal turn is active;validate_mcp_permissionthen checks only the session/turn and this path silently selectsAllowOnce, bypassing the normal permission UI. Compare the namespace with trusted per-session server metadata, or require an already-correlated tool-call ID, before granting silent approval.
let session_mcp_tool = session_mcp_tool_from_title(args.tool_call.fields.title.as_deref())
.or_else(|| self.hidden_session_mcp_tool(&session_id, &tool_call_id));
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 7bf01f6f-59c6-43fa-b68d-ffa432fde56d
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 20 out of 20 changed files in this pull request and generated 1 comment.
Suppressed comments (1)
tools/wta/src/protocol/acp/client.rs:1206
- The namespace check does not validate the per-session server name:
server_name_matchesacceptsintelligent_terminalor any syntactically validintellterm_<16 hex>value. Because this title is agent-authored, an unrelated provider tool can spoof such a title and receive silentAllowOnce, bypassing the normal permission UI;validate_mcp_permissiononly verifies that the session has an issued turn. Bind permission recognition to the exact MCP server name configured for this ACP session (or another trusted correlation), rather than trusting the title format.
let session_mcp_tool = session_mcp_tool_from_title(args.tool_call.fields.title.as_deref())
.or_else(|| self.hidden_session_mcp_tool(&session_id, &tool_call_id));
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 7bf01f6f-59c6-43fa-b68d-ffa432fde56d
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 20 out of 20 changed files in this pull request and generated no new comments.
Suppressed comments (2)
Previously missed (2) — in code that hasn't changed since the last review.
tools/wta/src/agent_tools/action_proposal/schema.rs:430
- Mapping a non-delegated
create_workspacecommand toOpenAndSendmakes the new intent render as delegation. The existing card renderer substitutesrecommendations.agent_fallback("agent") wheneveragentisNone(tools/wta/src/ui/recommendations.rs:256-264), and the transcript formatter emitsOpen ... and run agent(tools/wta/src/app.rs:6159-6170). As a result, an ordinary shell workspace is mislabeled to the user. Preserve whether this is shell execution versus delegation in the internal/rendering path and render the former as a command, not an agent task.
let action = match wire.command {
Some(command) => ProposalActionWire::OpenAndSend {
target,
input: command,
delegate: false,
cwd: wire.working_directory,
title,
direction: wire.split_direction.map(McpSplitDirectionWire::into_string),
test/e2e/tests/Feature.AgentProtocolExperience.Tests.ps1:187
- This fixed delay violates the E2E harness convention that
Wait-Untilis the only sanctioned wait mechanism (test/e2e/ItE2E/Private/Core.ps1:102-126) and can still sample before a slow shell has rendered. Replace it with a bounded polling/observation condition tied to pane capture readiness while asserting that the sentinel never appears.
Start-Sleep -Seconds 2
Summary
terminal_send,terminal_open, andterminal_open_and_sendMCP tools with intent-based command, workspace, and delegation toolsSession MCP tools
run_command_in_current_shellcreate_workspace(optionally initialized with one command)delegate_task_in_new_workspacerequest_user_input(unchanged clarification tool)Validation
cargo test --target x86_64-pc-windows-msvc --manifest-path tools/wta/Cargo.toml