Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion doc/release-check-list.md
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,8 @@ Net effect: UT shrinks the manual matrix to "did the wiring and UI connect", not

### Shell integration and detection

- [ ] `C295` `[new]` `[E2E]` **Detected Autofix clicks remain isolated between tabs:** With errors detected in two tabs, clicking one tab's diagnostics button submits exactly one prompt for that tab and preserves the other tab's pending opt-in. _(E2E: `Feature.AutofixRouting`.)_

- [ ] `C087` `[E2E]` **PowerShell shell integration installed:** Supported PowerShell profiles emit command-finished events, including non-zero marks for PowerShell-level failures on Windows PowerShell 5.1.
- [ ] `C219` `[new]` `[E2E]` **Bash / WSL shell integration installed:** Supported bash and WSL-bash profiles emit command-finished events, and the injected `PROMPT_COMMAND` is safe under `set -u` (no errors in strict-mode shells). _(#340.)_
- [ ] `C250` `[new]` `[E2E]` **Bash PROMPT_COMMAND rewrites preserve semantic prompt boundaries:** In Intelligent Terminal, a user hook that rebuilds `PS1` still produces one ordered `OSC 133;D/A/B` cycle per command; the same user-wide integration script stays inert in other terminals. _(#468; E2E: `Feature.BashPromptIntegration`.)_
Expand Down Expand Up @@ -465,4 +467,4 @@ Net effect: UT shrinks the manual matrix to "did the wiring and UI connect", not
- Slash commands: `tools\wta\src\commands.rs`.
- Session state model: `tools\wta\src\agent_sessions.rs`, `tools\wta\AGENTS.md`.
- Multi-window agent pane architecture: `doc\specs\Multi-window-agent-pane.md`.
- Autofix flow, logging, and runtime layout: `AGENTS.md`.
- Autofix flow, logging, and runtime layout: `AGENTS.md`.
62 changes: 42 additions & 20 deletions src/cascadia/TerminalApp/TerminalPage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3884,6 +3884,7 @@ namespace winrt::TerminalApp::implementation
openAgentPaneForReview();
Json::Value params;
params["pane_id"] = winrt::to_string(paneId);
params["tab_id"] = winrt::to_string(activeTab->StableId());
_RaiseProtocolEvent("autofix_execute_from_detected", params);
break;
}
Expand Down Expand Up @@ -8061,6 +8062,32 @@ namespace winrt::TerminalApp::implementation
return {};
}

std::string TerminalPage::_FindTabIdForSessionId(const std::string_view sessionId)
{
for (const auto& tab : _tabs)
{
const auto tabImpl = _GetTabImpl(tab);
if (!tabImpl)
{
continue;
}
const auto rootPane = tabImpl->GetRootPane();
if (!rootPane)
{
continue;
}
const auto match = rootPane->WalkTree([&](const auto& p) -> std::shared_ptr<Pane> {
const auto control = p->GetTerminalControl();
return (control && _FindSessionIdForControl(control) == sessionId) ? p : nullptr;
});
if (match)
{
return winrt::to_string(tabImpl->StableId());
}
}
return {};
}

void TerminalPage::_RegisterTerminalEvents(TermControl term)
{
term.RaiseNotice({ this, &TerminalPage::_ControlNoticeRaisedHandler });
Expand Down Expand Up @@ -8093,35 +8120,34 @@ namespace winrt::TerminalApp::implementation
// Forward VT sequences and connection state changes to protocol clients.
// This is unconditional — if no pipe client is listening, the event raise is a noop.
//
// We capture a weak ref to the TermControl and resolve the connection SessionId
// at event-fire time, because at _RegisterTerminalEvents time the Pane hasn't
// been created yet (TermControl is set up before the Pane wraps it).
// Capture the connection SessionId now. It is stable for the control's
// lifetime and lets the background VT callback avoid carrying a
// TermControl weak reference across threads.
//
// VtSequenceReceived fires on the connection reader thread (background).
// The dispatched continuation calls `_FindTabIdForControl`, which walks
// The dispatched continuation calls `_FindTabIdForSessionId`, which walks
// `_tabs` and has UI thread affinity, so the event raise has to run on
// the UI thread. `_FindSessionIdForControl` itself is thread-safe
// (only reads `Connection().SessionId()`) and could be called inline,
// but the rest of the work in this handler is gated on `_FindTabIdForControl`
// and the protocol event raise, so we just defer the whole body.
// (only reads `Connection().SessionId()`). The tab lookup and protocol
// event raise remain on the UI thread.
{
winrt::weak_ref<TermControl> weakTerm{ term };
const auto paneIdStr = _FindSessionIdForControl(term);
Comment thread
vanzue marked this conversation as resolved.

term.VtSequenceReceived(
[weakThis = get_weak(), weakTerm](auto&&, const winrt::hstring& seq) {
[weakThis = get_weak(), paneIdStr](auto&&, const winrt::hstring& seq) {
auto strongThis = weakThis.get();
if (!strongThis)
if (!strongThis || paneIdStr.empty())
return;

// Dispatch to UI thread for the `_FindTabIdForControl` walk
// Dispatch to UI thread for the `_FindTabIdForSessionId` walk
// of `_tabs` and the protocol event raise. Fire-and-forget —
// don't block the connection reader thread.
strongThis->Dispatcher().RunAsync(
winrt::Windows::UI::Core::CoreDispatcherPriority::Normal,
[weakThis, weakTerm, seq]() {
[weakThis, paneIdStr, seq]() {
auto page = weakThis.get();
auto term2 = weakTerm.get();
if (!page || !term2)
if (!page)
return;

// GPO-blocked gate: when administrator policy
Expand Down Expand Up @@ -8161,10 +8187,7 @@ namespace winrt::TerminalApp::implementation
return;
}

const auto paneIdStr = page->_FindSessionIdForControl(term2);
if (paneIdStr.empty())
return;
const auto tabIdStr = page->_FindTabIdForControl(term2);
const auto tabIdStr = page->_FindTabIdForSessionId(paneIdStr);

if (isAgentEvent)
{
Expand All @@ -8180,13 +8203,12 @@ namespace winrt::TerminalApp::implementation
{
const auto eventName = agentParams["event"].asString();
const auto agentSessionId = agentParams.get("agent_session_id", "").asString();
if (const auto connection = term2.Connection())
if (const auto paneSessionId = _TryParsePaneSessionId(paneIdStr))
{
// This event arrived in-band on this
// pane's own VT stream, so the pane is
// the origin by construction — there is
// no reported `pane_id` to distrust.
const auto paneSessionId = connection.SessionId();
if ((eventName == "agent.session.started" || eventName == "agent.session.start") &&
!agentSessionId.empty() &&
!agentSessionId.starts_with("sidekick-"))
Expand All @@ -8197,7 +8219,7 @@ namespace winrt::TerminalApp::implementation
if (!resumeCommandline.empty())
{
page->_paneAgentSessions.insert_or_assign(
paneSessionId,
*paneSessionId,
_PaneAgentSession{
winrt::to_hstring(agentSessionId),
winrt::to_hstring(agentParams.get("cli_source", "").asString()),
Expand Down
1 change: 1 addition & 0 deletions src/cascadia/TerminalApp/TerminalPage.h
Original file line number Diff line number Diff line change
Expand Up @@ -880,6 +880,7 @@ namespace winrt::TerminalApp::implementation
void _RegisterTerminalEvents(Microsoft::Terminal::Control::TermControl term);
std::string _FindSessionIdForControl(const Microsoft::Terminal::Control::TermControl& control);
std::string _FindTabIdForControl(const Microsoft::Terminal::Control::TermControl& control);
std::string _FindTabIdForSessionId(std::string_view sessionId);
void _RegisterTabEvents(Tab& hostingTab);

void _DismissTabContextMenus();
Expand Down
6 changes: 4 additions & 2 deletions test/e2e/ItE2E/Public/Agent.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -213,11 +213,13 @@ function Wait-TerminalActionProposal {
recommendation card directly or first presents the provider's normal permission
UI. With -ReturnOnPermission, the latter returns Mode=Permission without selecting
an option; the caller must simulate an explicit user choice.
Use -PaneSessionId to pin the rendered MCP card when several tabs have helpers.
#>
[CmdletBinding()] param(
[Parameter(Mandatory, ValueFromPipeline)]$App,
[int]$TimeoutSec = 45,
[switch]$ReturnOnPermission
[switch]$ReturnOnPermission,
[string]$PaneSessionId
)
process {
Wait-Until -TimeoutSec $TimeoutSec -IntervalSec 0.5 -Because 'a pending terminal-action proposal' -Condition {
Expand All @@ -229,7 +231,7 @@ function Wait-TerminalActionProposal {
return Get-CimInstance Win32_Process -Filter "ProcessId = $($candidate.ProcessId)" -ErrorAction SilentlyContinue |
Where-Object { $_.CommandLine -match '(?i)(?:^|\s)propose-terminal-actions(?:\s|$)' }
}
$paneText = Get-AgentPaneText -App $App -MaxLines 60
$paneText = Get-AgentPaneText -App $App -MaxLines 60 -PaneSessionId $PaneSessionId
if ($paneText -match (Get-RecommendationCardRegex)) {
return [pscustomobject]@{ Mode = 'Mcp'; Ready = $true }
}
Expand Down
3 changes: 2 additions & 1 deletion test/e2e/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,9 @@ authenticated ACP agents. Current status (run on the Store package):
| `Feature.AgentSelectAll.Tests.ps1` | Plain Ctrl+A selects the current WTA-rendered frame; Ctrl+C copies through the existing clipboard path and clears selection without stale replay | 1 |
| `Feature.PromptHistory.Tests.ps1` | PR #478: per-tab Up/Down prompt recall, draft restoration, and multiline preservation; PR #614: completed-turn collapse/expand rendering | 4 |
| `Feature.CompletedTurnSelection.Tests.ps1` | Completed-turn Tab/Up/Down selection keeps focused history inside the chat viewport | 1 |
| `Feature.AutofixPane.Tests.ps1` | Direct Helper Autofix proposal card render/insert/run/reject/target/stashed + across layout | 10 |
| `Feature.AutofixPane.Tests.ps1` | Direct Helper Autofix proposal card render/insert/run/reject/target/stashed + across layout + WSL shell identity and Linux fixes | 12 (2 WSL-gated) |
| `Feature.AutofixParser.Tests.ps1` | issue #474: PowerShell ParserError-to-Autofix pipeline + success/handled-error/blank-input negative controls | 4 |
| `Feature.AutofixRouting.Tests.ps1` | Two Detected tabs: real diagnostics clicks submit only to the selected tab's ACP session and preserve the other tab's opt-in | 1 |
| `Feature.CommandResolution.Tests.ps1` | PR #418: packaged WTA resolves PowerShell profile-only aliases to their real targets | 1 |
| `Feature.SessionList.Tests.ps1` | session view (button + `/sessions` slash), session states, view switching (incl. draft-preservation), focus/restore | 13 (+1 skip) |
| `Feature.NonAsciiCwd.Tests.ps1` | issue #641: a non-ASCII starting directory survives `wtcli` argv → COM → `CreateProcessW`, so the resume launch path connects and starts in that directory | 2 |
Expand Down
11 changes: 7 additions & 4 deletions test/e2e/tests/Feature.AutofixPane.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,10 @@ Describe 'Feature: autofix in a WSL pane (OSC 9001;ShellType end-to-end)' -Tag '

# Agent pane on the (now active) WSL tab so autofix cards render here.
Open-AgentPane -App $script:app | Out-Null
Wait-AgentReady -App $script:app -TimeoutSec 60 | Should -BeTrue -Because 'the agent pane must be connected for WSL autofix to render cards'
$script:wslAgent = Wait-Until -TimeoutSec 30 -Because 'the WSL tab helper identity' -Condition {
Get-AgentPaneSession -App $script:app -OwnerPaneSessionId $script:wslSid
}
Wait-AgentReady -App $script:app -PaneSessionId $script:wslAgent.PaneSessionId -TimeoutSec 60 | Should -BeTrue -Because 'the agent pane must be connected for WSL autofix to render cards'
}
catch {
Write-ItLog -Level WARN -Message "WSL autofix setup failed (build without WSL-capable CreateTab / OSC 9001, or no WSL shell integration): $_"
Expand Down Expand Up @@ -257,14 +260,14 @@ Describe 'Feature: autofix in a WSL pane (OSC 9001;ShellType end-to-end)' -Tag '
Invoke-FailingCommand -App $script:app -SessionId $script:wslSid -Command 'sl -la' | Out-Null
Wait-Autofix -Listener $listener -TimeoutSec 45 | Out-Null
} finally { Stop-WtEventListener -Listener $listener }
$pending = Wait-TerminalActionProposal -App $script:app -TimeoutSec 30 -ReturnOnPermission
$pending = Wait-TerminalActionProposal -App $script:app -PaneSessionId $script:wslAgent.PaneSessionId -TimeoutSec 30 -ReturnOnPermission
$pending | Should -Not -BeNullOrEmpty -Because 'WSL Autofix must submit a Direct Helper Proposal'
if ($pending.Mode -eq 'Permission') {
# Explicit test-user selection of the provider's allow option.
Send-AgentKey -App $script:app -Key Y | Out-Null
Send-AgentKey -App $script:app -PaneSessionId $script:wslAgent.PaneSessionId -Key Y | Out-Null
}
$cardText = Wait-Until -TimeoutSec 60 -IntervalSec 1 -Because 'a visible WSL Autofix recommendation card' -Condition {
$text = Get-AgentPaneText -App $script:app -MaxLines 60
$text = Get-AgentPaneText -App $script:app -PaneSessionId $script:wslAgent.PaneSessionId -MaxLines 60
if ($text -match (Get-RecommendationCardRegex)) { $text }
}
Assert-AI -Claim 'The suggested fix command uses Linux/bash shell syntax (e.g. ls, grep, cat, forward-slash paths). It is NOT a Windows PowerShell command (no Get-ChildItem / Select-String / cmdlet-style Verb-Noun).' -Context $cardText
Expand Down
Loading
Loading