Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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