Route WTA CLI terminal-action proposals directly to Helpers - #484
Route WTA CLI terminal-action proposals directly to Helpers#484vanzue wants to merge 21 commits into
Conversation
Define a typed, helper-bound CLI proposal flow that preserves existing card confirmation without reintroducing MCP. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: d998a7af-5b49-496b-8b59-ea48f40e50c8
There was a problem hiding this comment.
Pull request overview
This PR adds a design/specification document that replaces the previously proposed MCP-based typed terminal-action proposal transport with a WTA CLI contract, while preserving the existing recommendation-card confirmation flow and wtcli/COM execution boundary.
Changes:
- Define a
wta propose-terminal-actions --payload-base64 <...>CLI-based proposal submission contract (proposal-only; non-mutating). - Specify helper-local hardened transport (per-helper pipe + short-lived one-use capability) and freshness/origin policies for Terminal Agent vs. Autofix.
- Outline phased rollout, compatibility fallback to assistant-text JSON, and validation expectations.
Comments suppressed due to low confidence (3)
doc/specs/WTA-CLI-terminal-action-proposals.md:78
- The proposed
create_terminalargv example uses--payload-base64even though the payload is specified as base64url-encoded later. To keep the schema consistent for agents and validators, use--payload-base64urlhere as well.
2. The agent requests create_terminal for direct argv:
command = "wta"
args = ["propose-terminal-actions", "--payload-base64", "..."]
3. wta-master routes create_terminal by ACP session id to the owning helper.
doc/specs/WTA-CLI-terminal-action-proposals.md:215
- Unclear term: “App” isn’t defined elsewhere in the doc, and could be read as “Windows Terminal app” rather than the helper-side logic. Consider rephrasing to “The helper remains authoritative…” for clarity.
App remains authoritative for state that the ACP client does not own:
doc/specs/WTA-CLI-terminal-action-proposals.md:109
- The CLI contract uses
--payload-base64even though the payload is specified as base64url-encoded. Renaming the flag to--payload-base64urlwould make the encoding requirement unambiguous for callers.
- --payload-base64
- <base64url-encoded UTF-8 JSON>
Make agent sessions execute WTA directly, then use a prompt-scoped route token and the existing master ACP pipe to reach the owning helper. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: d998a7af-5b49-496b-8b59-ea48f40e50c8
|
Architecture correction: agent sessions are now expected to execute WTA directly. The revised spec removes the helper-proxied |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 1 out of 1 changed files in this pull request and generated 1 comment.
Comments suppressed due to low confidence (3)
doc/specs/WTA-CLI-terminal-action-proposals.md:202
- This list calls the action
send_input, but the current recommendation-card action name issend. Aligning the proposal wire schema with the existing action name will reduce schema drift across specs, prompts, and implementation.
- `send_input`
doc/specs/WTA-CLI-terminal-action-proposals.md:244
- In the Terminal Agent origin policy section, the action name is
send_input, but existing RecommendationSet policy/docs usesend. Keeping the same action name across specs will make the contract easier to implement and reason about.
- `send_input` targets the active pane captured for the prompt.
doc/specs/WTA-CLI-terminal-action-proposals.md:251
- In the Autofix policy,
send_inputdiverges from the existing RecommendationSet action name (send). Unless the new proposal wire schema intentionally renames actions, consider usingsendhere as well for consistency.
- Exactly one choice with exactly one `send_input` action.
Replace master token routing with per-Helper proposal channels, canonical silent AllowOnce handling, secure direct named pipes, and two-phase validation/final feedback. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: d998a7af-5b49-496b-8b59-ea48f40e50c8
This comment has been minimized.
This comment has been minimized.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: d998a7af-5b49-496b-8b59-ea48f40e50c8
…al-action-proposals
Make the direct helper channel the only card-producing path and hide its internal proposal tool calls from chat while preserving normal tool call rendering. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 7743e270-ccd3-40be-b382-42639b800d46
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 7743e270-ccd3-40be-b382-42639b800d46 # Conflicts: # tools/wta/src/app_tests.rs # tools/wta/src/app_turn.rs # tools/wta/src/protocol/acp/client.rs # tools/wta/src/protocol/acp/mock_agent_tests.rs # tools/wta/src/ui/chat.rs
This comment has been minimized.
This comment has been minimized.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 7743e270-ccd3-40be-b382-42639b800d46
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: a828e781-6e7b-4356-9e3d-c952d092587e
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 32 out of 33 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (3)
tools/wta/src/app_turn.rs:654
- Clearing active_direct_proposal_id immediately after the user confirms will cause any remaining AgentMessageChunk text (arriving before AgentMessageEnd) to be dropped, even though turn_observe_chunk explicitly tries to keep trailing chunks visible for direct proposals. Consider keeping the ID until AgentMessageEnd, then clearing it when the turn is finalized.
tab.active_direct_proposal_id = None;
tools/wta/src/proposal_pipe.rs:165
- Oversized proposal payloads should be retryable: the agent can usually correct this by sending a smaller payload. Returning retryable=false here prevents the agent from attempting a corrected submission even though it’s a recoverable validation failure.
return write_validation_failure(
&mut write_half,
ProposalValidationStatus::InvalidSchema,
format!("payload exceeds the {MAX_PAYLOAD_BYTES}-byte inline limit"),
false,
tools/wta/src/app_turn.rs:533
- active_direct_proposal_id should be cleared once the direct-proposal turn is finalized (after trailing details are committed) to avoid accepting stray late chunks indefinitely. Clearing it here ensures the extra Surfaced-chunk passthrough only applies until AgentMessageEnd.
This issue also appears on line 654 of the same file.
fn turn_commit_trailing_direct_proposal_details(&mut self, session_id: &str) {
let tab = self.session_tab_mut(session_id);
let trailing = tab.current_turn_details();
if let Some(completed) = tab.completed_turns.last_mut() {
completed.details.extend(trailing);
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: a828e781-6e7b-4356-9e3d-c952d092587e
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: a828e781-6e7b-4356-9e3d-c952d092587e
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 33 out of 34 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (1)
tools/wta/src/main.rs:1189
run_propose_terminal_actionsflushes stdout after the validation JSON line, but not after the final JSON line. If the agent reads stdout over a pipe, the final response can be buffered and delayed, breaking the “two JSON Lines” contract.
let final_response: proposal_pipe::ProposalFinalResponse =
read_proposal_response(&mut reader).await?;
println!("{}", serde_json::to_string(&final_response)?);
Ok(())
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 33 out of 34 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (2)
tools/wta/src/proposal_pipe.rs:329
read_frameenforcesMAX_FRAME_BYTESbefore stripping the line terminator, which effectively reduces the maximum JSON frame size by 1 byte and can also reject a max-sized CRLF-terminated frame. SinceMAX_FRAME_BYTESis defined as the maximum encoded JSON-line size, trim\n/optional\rfirst and then enforce the limit on the JSON payload bytes; also increase thetake()bound to allow CRLF at the limit.
if frame.len() > MAX_FRAME_BYTES {
anyhow::bail!("proposal request frame exceeds {MAX_FRAME_BYTES} bytes");
}
if frame.last() != Some(&b'\n') {
anyhow::bail!("proposal request frame is not newline terminated");
tools/wta/src/protocol/acp/spawn.rs:270
proposal_cli_path()?makes agent spawn fail hard ifLOCALAPPDATA/current_exe()can't be resolved (or other alias resolution issues), which can take down the whole agent pane even though proposals are an optional capability. This should be best-effort: warn and continue spawning the agent withoutWTA_CLI_PATHrather than returning an error fromspawn_agent_process.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: a828e781-6e7b-4356-9e3d-c952d092587e
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 35 out of 36 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (1)
tools/wta/src/proposal_invocation.rs:56
validate_payloadcurrently equates “compact JSON” withserde_json::to_string(Value) == payload. Withserde_json = "1"(nopreserve_orderfeature), object key order is not preserved on re-encode, so payloads that are already whitespace-free but have a different key order (or different-but-equivalent escaping) will be rejected and the helper will silently cancel proposal permissions.
This makes the direct-proposal contract unnecessarily brittle for agents, and it’s not implied by the docs (they require compact JSON, not a specific key ordering). Suggest validating “no whitespace outside strings” + “valid JSON” instead, and leaving exact-byte matching to the digest arming/check.
fn validate_payload(payload: &str) -> Result<(), &'static str> {
if payload.is_empty() || payload.len() > MAX_PAYLOAD_BYTES {
return Err("proposal payload is empty or too large");
}
let value: serde_json::Value =
serde_json::from_str(payload).map_err(|_| "proposal payload is invalid JSON")?;
let compact =
serde_json::to_string(&value).map_err(|_| "proposal payload could not be encoded")?;
if compact != payload {
return Err("proposal payload must be compact JSON");
}
Ok(())
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: a828e781-6e7b-4356-9e3d-c952d092587e
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 35 out of 36 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (3)
tools/wta/src/terminal_action_proposal.rs:392
- Similarly,
OpenAndSendforwardscwd,title, andprofilewithout trimming or bounds checks. Since these fields are model-controlled, they should be filtered for empties and capped like the other free-text fields to avoid unexpected downstream behavior and unbounded UI/log growth.
tools/wta/src/cli/proposals.rs:41 - The CLI flushes stdout after printing the validation JSON, but not after printing the final JSON line. If stdout is piped (typical for agents), the final response can be buffered and never observed reliably by the caller before process exit.
let final_response: crate::proposal_pipe::ProposalFinalResponse =
read_response(&mut reader).await?;
println!("{}", serde_json::to_string(&final_response)?);
Ok(())
tools/wta/src/terminal_action_proposal.rs:387
- The proposal wire format documents size bounds to prevent UI/log bloat, but
Opencurrently forwardscwd,title, andprofilewithout trimming or length checks. This allows empty strings (Some("")) and unbounded text to flow into the RecommendationSet.
This issue also appears on line 388 of the same file.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: a828e781-6e7b-4356-9e3d-c952d092587e
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 35 out of 36 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (1)
tools/wta/src/cli/proposals.rs:41
- The CLI prints the final JSON line but does not flush stdout afterward. If the agent process reads stdout via a pipe, the final response can remain buffered, causing the agent to hang waiting for the completion line.
let final_response: crate::proposal_pipe::ProposalFinalResponse =
read_response(&mut reader).await?;
println!("{}", serde_json::to_string(&final_response)?);
Ok(())
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: a828e781-6e7b-4356-9e3d-c952d092587e
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 35 out of 36 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (1)
tools/wta/Cargo.toml:56
- A new direct Rust dependency (
sha2) was added, which changes the WTA dependency graph. Per tools/wta/AGENTS.md, this requires regenerating and committingtools/wta/cgmanifest.jsonand the<!-- BEGIN wta-rust-deps -->block in/NOTICE.mdviapwsh -File .\build\scripts\Generate-WtaThirdPartyNotices.ps1, in the same commit as the Cargo change.
uuid = { version = "1", features = ["v4"] }
sha2 = "0.10"
rust-i18n = "3"
Summary
v1.<helper-instance>.<turn-nonce>channel and one canonical PowerShell invocation into each ACP Agent promptAllowOncein the Helper, binding the approved payload digest before the CLI can connectEnd-to-end flow
Proposal payloads, channels, digests, acknowledgements, and final results never transit
wta-master.Contract and security
Canonical invocation:
AwaitingUserproposal before executor enqueueAgent scope
All ACP Agents use one Direct Proposal implementation. Every Agent receives the same prompt-scoped channel and canonical command, and every valid permission envelope enters the same Helper-owned validation and Recommendation Card pipeline. Direct Proposal is the sole card-producing path; incompatible permission envelopes fail closed and expose the integration issue.
Validation
x86_64-pc-windows-msvcWTA build completedwta.exeApp Execution Alias created and invoked successfully from external PowerShellRelationships