Skip to content

Commit 305d6e7

Browse files
committed
Merge origin/main into Markdown renderer 2
2 parents 0534e53 + 9b31039 commit 305d6e7

66 files changed

Lines changed: 6035 additions & 1839 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

doc/release-check-list.md

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -134,8 +134,8 @@ Net effect: UT shrinks the manual matrix to "did the wiring and UI connect", not
134134
- [ ] `C054` `[E2E]` **Different positions work:** Open/hide/focus works for bottom, right, left, and top pane positions.
135135
- [ ] `C252` `[new]` `[E2E]` **Agent pane move is isolated per tab and preserves input focus:** `/move` changes only the current tab's runtime pane position, leaves the global setting and sibling tabs unchanged, and returns keyboard focus to the moved agent input. _(#429; E2E: `Feature.AgentPaneMove`.)_
136136
- [ ] `C055` `[E2E]` **Stash preserves chat:** Hiding and restoring the pane preserves helper process, connection state, and chat history.
137-
- [ ] `C056` `[E2E]` **Tab close cleans up:** Closing the owning tab cleans up the helper and does not leave a broken pane.
138-
- [ ] `C247` `[new]` `[E2E]` **Closing a tab mid-turn leaves sibling agent tabs working:** When one tab closes with a prompt in flight, its orphaned result is discarded without terminating the shared agent CLI, and another tab can continue chatting without a restart. _(#419/#425; E2E: `Feature.SharedAgentLifecycle`.)_
137+
- [ ] `C056` `[E2E]` **Tab close cleans up:** Closing the owning tab physically closes its ACP session, cleans up the helper, and does not leave a broken pane.
138+
- [ ] `C247` `[new]` `[E2E]` **Closing a tab mid-turn leaves sibling agent tabs working:** When one tab closes with a prompt in flight, only its ACP session is closed; the shared agent CLI remains alive and another tab can continue chatting without a restart. _(#419/#425; E2E: `Feature.SharedAgentLifecycle`.)_
139139
- [ ] `C215` `[new]` `[E2E]` **Agent panes are not persisted into saved layout:** Saving and restoring a window layout does not resurrect a previously-open agent pane; restored windows come back without an unexpected agent pane. _(#360/#275.)_
140140

141141
### Built-in agent chat matrix
@@ -155,7 +155,10 @@ Net effect: UT shrinks the manual matrix to "did the wiring and UI connect", not
155155
- [ ] `C062` `[E2E]` **Prompt focused appearance is correct:** Input box looks correct when focused.
156156
- [x] `C063` `[UT✓]` `[E2E]` **Prompt out-of-focus appearance is correct:** Input box looks correct when focus leaves the agent pane. _(UT: `render_input_box_intact_when_pane_unfocused` renders with `pane_focused=false` and asserts the input box stays intact — prompt marker + connection placeholder still paint, not blanked/broken; only the caret style dims, input.rs:69/90.)_
157157
- [ ] `C064` `[E2E]` **Typing works:** User can type, edit, and submit prompt text correctly.
158-
- [ ] `C065` `[E2E]` **Paste works:** Pasted multi-line text is handled correctly.
158+
- [ ] `C065` `[E2E]` **Paste works:** Pasted multiline text remains in one agent draft without submitting. _(UT: `agent_paste_text_*`; E2E: `Feature.Paste`.)_
159+
- [ ] `C266` `[new]` `[E2E]` **Ctrl+V pastes into the agent input:** Ctrl+V invokes the structured paste path and inserts clipboard text exactly once instead of typing a literal `v`; Ctrl+Shift+V remains a positive control. _(E2E: `Feature.Paste`.)_
160+
- [ ] `C267` `[new]` `[E2E]` **Ctrl+V paste survives input refocus:** After a physical completed-turn interaction and input-dialog click, Ctrl+V pastes into the visible draft. _(E2E: `Feature.Paste` `PasteRefocus`.)_
161+
- [ ] `C268` `[new]` `[E2E]` **Ctrl+V paste stays isolated to its owner tab:** Pasting into one focused agent input does not mutate a sibling tab's agent draft. _(UT: `agent_paste_text_ignores_wrong_window_and_non_owner_helpers`; E2E: `Feature.Paste` `PasteOwnerIsolation`.)_
159162
- [ ] `C234` `[new]` `[E2E]` **Prompt history recall works:** Up/Down recalls submitted prompts from newest to oldest and moves back toward newer entries. _(#478/#479; E2E: `Feature.PromptHistory`.)_
160163
- [ ] `C235` `[new]` `[E2E]` **Prompt history preserves drafts and multiline prompts:** Reviewing history keeps each multiline prompt intact and restores the current unsent draft afterward. _(#478/#479; E2E: `Feature.PromptHistory`.)_
161164
- [ ] `C236` `[new]` `[E2E]` **Prompt history is isolated per tab:** Each agent tab recalls only prompts submitted in that tab. _(#478/#479; E2E: `Feature.PromptHistory`.)_
@@ -164,6 +167,8 @@ Net effect: UT shrinks the manual matrix to "did the wiring and UI connect", not
164167
- [ ] `C264` `[new]` `[UT✓]` `[E2E]` **Completed turns toggle and select from rendered prompt targets:** A left-button Down/Up anywhere on the same visible prompt row, including its triangle, prefix, text, explicit blank row, or unused row-end space, collapses or expands only that turn and reuses the keyboard selection, highlight, navigation, Esc, and Enter behavior. Hover uses only a hand cursor: action metadata must not add hyperlink underline or tooltip text. Details, separators, drags, overlays, tab changes, scrolling, and stale/offscreen rows do not toggle; clicking the live input dialog clears the history selection and restores draft input. Multiline, wrapped, wide-cell, and double/triple-click paths preserve their expected geometry and text-selection behavior. _(UT: `clicking_multiline_completed_turn_prompt_selects_and_reuses_enter_toggle`, `completed_turn_user_input_hit_spans_full_row_with_wide_cells`, `completed_turn_prompt_rows_expose_state_aware_action_links`, `overlay_preserves_full_rows_and_cell_styles`, `CompletedTurnActionHyperlinksSuppressUnderlines`, `clicking_input_dialog_restores_input_navigation_after_mouse_turn_selection`, `completed_turn_user_input_multi_click_preserves_turn_state_and_text_selection`, `completed_turn_mouse_selection_continues_with_keyboard_navigation`, `completed_turn_triangle_click_ignores_text_drag_and_hidden_chat`, `completed_turn_triangle_hits_follow_visible_scrolled_turns`, `completed_turn_prompt_hits_survive_a_clipped_header_row`; E2E: `Feature.AgentMouse` `CompletedTurnMouse` / `CompletedTurnPromptMouse`; hand cursor and absence of action underline manually verified on the exact deployed Dev build.)_
165168
- [ ] `C242` `[new]` `[UT✓]` `[E2E]` **Mouse wheel scrolls chat without changing the draft:** Wheel input scrolls the chat viewport while Up/Down remain prompt-history controls and the unsent draft stays intact. _(UT: `mouse_wheel_scrolls_chat_without_changing_input_history`; #506; E2E: `Feature.AgentMouse`.)_
166169
- [ ] `C243` `[new]` `[UT✓]` `[E2E]` **Mouse selection copies text and clears after copy:** Double-click selection survives release, `Ctrl+C` copies the exact selected text with confirmation instead of canceling/closing the pane, and the selection is then cleared. _(UT: `mouse_release_does_not_return_text_for_automatic_copy`, `clearing_selection_resets_multi_click_sequence`; #506; E2E: `Feature.AgentMouse`.)_
170+
- [ ] `C265` `[new]` `[UT✓]` `[E2E]` **Right-click copies the agent text selection:** A physical right-click copies the exact WTA-owned selection through the OS clipboard, clears it, shows the existing copied confirmation, and never also pastes; the next right-click follows the no-selection Default Paste path without replaying selected text. _(UT: `right_click_copy_event_is_forwarded`, `right_click_copies_and_clears_text_selection`; E2E: `Feature.AgentMouse` `RightClickCopy`.)_
171+
- [ ] `C269` `[new]` `[UT✓]` `[E2E]` **Right-click without text selection pastes throughout the Chat pane:** A physical right-click on history, input, blank space, or a completed-turn navigation highlight requests Default Paste exactly once into the visible owner draft without submitting; only an actual WTA text selection takes copy precedence. _(UT: `right_click_without_text_selection_requests_owner_default_paste`, `default_paste_request_is_chat_only`; E2E: `Feature.AgentMouse` `RightClickPaste`.)_
167172
- [ ] `C218` `[new]` `[UT✓]` `[E2E]` **Image paste (Alt+V) works:** A copied screenshot (`CF_DIB`/`CF_DIBV5`) appears as a uniquely numbered cyan inline token such as `[image: image-1.png]`; copied image files retain their real file name. The attachment is sent as an ACP image content block on the next prompt. Left/Right cross the token atomically, Backspace/Delete remove the whole token and attachment, and Esc/Ctrl+C clear the whole draft. The action is gated on the agent advertising image support. When the agent does not support images, or the clipboard has no image, it does not paste but surfaces a clear system message rather than silently ignoring the keypress. _(UT: `clipboard_image`, `image_attachment_*`, and `mock_agent_tests` `seen_images` side-channel; #354.)_
168173
- [ ] `C254` `[new]` `[UT✓]` `[E2E]` **Image attachment tokens edit atomically:** Left/Right cross an inline image token as one input unit, and Backspace removes the complete attachment without damaging adjacent prompt text. _(UT: `image_attachment_left_and_right_skip_the_whole_inline_token`, `image_attachment_backspace_in_text_preserves_images`; #536; E2E: `Feature.AgentImageAttachmentEditing`.)_
169174
- [ ] `C066` `[E2E]` **Keyboard navigation works:** Arrow keys, Tab completion, Ctrl combinations, and Esc behave correctly.
@@ -352,6 +357,7 @@ Net effect: UT shrinks the manual matrix to "did the wiring and UI connect", not
352357
- [x] `C166` `[UT✓]` `[E2E]` **Multiple windows do not cross-route:** Events from one window do not mutate another window's agent pane/session UI. _(UT: `wt_event_critical_from_other_tab_does_not_surface_in_owner_tab` — a helper owning tab A DROPS a connection-failure event broadcast from tab B (no banner, no chat, no notification), the exact cross-route isolation contract; helpers filter inbound events by window_id + owner_tab_id.)_
353358
- [ ] `C167` `[E2E]` **Close source window is safe:** Closing a source window after moving a tab does not kill the moved tab's agent state.
354359
- [ ] `C168` `[E2E]` **Close target tab cleans up:** Closing moved tabs cleans up helper/session state without affecting other tabs.
360+
- [ ] `C275` `[E2E]` **Move tab back to source window preserves the same ACP session:** Redocking an agent tab keeps its helper, chat, and ACP session alive and does not emit session teardown.
355361

356362
## 8. Agent hooks and session tracking
357363

@@ -439,4 +445,4 @@ Net effect: UT shrinks the manual matrix to "did the wiring and UI connect", not
439445
- Slash commands: `tools\wta\src\commands.rs`.
440446
- Session state model: `tools\wta\src\agent_sessions.rs`, `tools\wta\AGENTS.md`.
441447
- Multi-window agent pane architecture: `doc\specs\Multi-window-agent-pane.md`.
442-
- Autofix flow, logging, and runtime layout: `AGENTS.md`.
448+
- Autofix flow, logging, and runtime layout: `AGENTS.md`.

src/cascadia/TerminalApp/AgentPaneContent.cpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,19 @@ namespace winrt::TerminalApp::implementation
292292
{
293293
impl->UpdateSettings(settings);
294294
}
295+
296+
const winrt::Microsoft::Terminal::Control::KeyChord ctrlV{ Windows::System::VirtualKeyModifiers::Control, 'V', 0 };
297+
if (const auto actionMap = settings.ActionMap())
298+
{
299+
const auto command = actionMap.GetActionByKeyChord(ctrlV);
300+
const auto isPasteAction = command && command.ActionAndArgs().Action() == ShortcutAction::PasteText;
301+
GetTermControl().EnableAgentPasteShortcutFallback(
302+
!actionMap.IsKeyChordExplicitlyUnbound(ctrlV) && (!command || isPasteAction));
303+
}
304+
else
305+
{
306+
GetTermControl().EnableAgentPasteShortcutFallback(false);
307+
}
295308
}
296309

297310
winrt::Windows::Foundation::Size AgentPaneContent::MinimumSize()

src/cascadia/TerminalApp/Pane.cpp

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3426,15 +3426,11 @@ void Pane::SetAgentChipVisible(bool value)
34263426
}
34273427

34283428
// The connection's session GUID for terminal panes. Returns the empty
3429-
// guid for non-terminal panes (e.g. branch nodes, agent panes, snippets).
3429+
// guid for non-terminal panes (e.g. branch nodes and snippets).
34303430
// Used by Tab to match a protocol-supplied pane id to a Pane.
34313431
winrt::guid Pane::GetSessionId() const
34323432
{
3433-
// Mirror `_getSessionIdFromPane` in TerminalPage.Protocol.cpp:
3434-
// walk content → control → connection and read the SessionId. Using
3435-
// GetContent() (instead of `_content` directly) keeps the non-leaf
3436-
// case to a clean nullptr without needing an explicit leaf check.
3437-
if (const auto termContent = GetContent().try_as<winrt::TerminalApp::TerminalPaneContent>())
3433+
if (const auto termContent = _getTerminalContent())
34383434
{
34393435
if (const auto control = termContent.GetTermControl())
34403436
{

src/cascadia/TerminalApp/SharedWta.cpp

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,13 @@
1111
#include "../inc/WtaProcess.h"
1212
#include "AgentPaneLog.h"
1313

14+
namespace
15+
{
16+
// Must remain strictly greater than WTA's 15-second
17+
// SESSION_CLOSE_TIMEOUT in tools/wta/src/master/mod.rs.
18+
constexpr auto WtaSessionCloseGracePeriod{ std::chrono::seconds{ 16 } };
19+
}
20+
1421
namespace winrt::TerminalApp::implementation::details
1522
{
1623
std::optional<std::wstring> BuildEnvironmentBlock(
@@ -85,15 +92,19 @@ namespace winrt::TerminalApp::implementation
8592
{
8693
SharedWta& SharedWta::Instance()
8794
{
88-
// Magic-static initialization is thread-safe in C++11+.
89-
static SharedWta s_instance;
90-
return s_instance;
95+
// Initialization remains thread-safe, but this process singleton must
96+
// outlive delayed ReleasePaneAfterSessionClose coroutines. At process
97+
// exit Windows closes the Job handle, preserving KILL_ON_JOB_CLOSE
98+
// cleanup for the master and its descendants.
99+
static auto* const s_instance = new SharedWta;
100+
return *s_instance;
91101
}
92102

93103
SharedWta::~SharedWta()
94104
{
95-
// Process is exiting; tear wta down deterministically via
96-
// KILL_ON_JOB_CLOSE rather than letting handles leak.
105+
// Process is exiting, so a graceful per-session close can no longer
106+
// delay app shutdown. KILL_ON_JOB_CLOSE deterministically reclaims the
107+
// master, every agent CLI, and their MCP descendants without orphans.
97108
//
98109
// Wait callback synchronisation: cancel the wait WITH a
99110
// blocking unregister BEFORE we touch the fields it might
@@ -195,6 +206,12 @@ namespace winrt::TerminalApp::implementation
195206
}
196207
}
197208

209+
winrt::fire_and_forget SharedWta::ReleasePaneAfterSessionClose()
210+
{
211+
co_await winrt::resume_after(WtaSessionCloseGracePeriod);
212+
Instance().ReleasePane();
213+
}
214+
198215
bool SharedWta::Restart()
199216
{
200217
std::lock_guard lock{ _mtx };
@@ -442,7 +459,7 @@ namespace winrt::TerminalApp::implementation
442459

443460
// Containment: a Job Object with KILL_ON_JOB_CLOSE binds
444461
// wta's lifetime to ours. When the last pane releases (or
445-
// Terminal exits and the destructor runs), the job handle
462+
// Terminal exits and Windows closes the final handle), the job handle
446463
// drops and the OS terminates wta + every descendant it
447464
// spawned. Any failure here MUST TerminateProcess to avoid
448465
// leaking a suspended-then-uncontained wta.

src/cascadia/TerminalApp/SharedWta.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,12 @@ namespace winrt::TerminalApp::implementation
9999
/// teardown paths that aren't sure whether they acquired).
100100
void ReleasePane();
101101

102+
/// Release a previously acquired reference after the bounded ACP
103+
/// session-close window. Agent-pane Closed events can fire before
104+
/// the owning tab publishes tab_closed, so an immediate final
105+
/// release could terminate wta-master before session/close runs.
106+
static winrt::fire_and_forget ReleasePaneAfterSessionClose();
107+
102108
/// Force-restart the wta-master process, bypassing the
103109
/// `AcquirePane`/`ReleasePane` reference count. Used by the
104110
/// `/restart` slash command path: the caller (TerminalPage)

src/cascadia/TerminalApp/TabManagement.cpp

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -754,14 +754,20 @@ namespace winrt::TerminalApp::implementation
754754
// - Close-pane (last pane): in _HandleClosePaneRequested
755755
// - Close-tab: in _HandleCloseTabRequested
756756

757+
if (!movingAway)
758+
{
759+
// Notify WTA while the agent helper is still alive. Shutdown tears down the
760+
// helper's ConPTY process, so emitting this afterwards races session/close
761+
// against process termination and leaves the ACP session orphaned.
762+
_NotifyAgentTabClosed(closedTabStableId);
763+
}
764+
757765
// Removing the tab from the collection should destroy its control and disconnect its connection,
758766
// but it doesn't always do so. The UI tree may still be holding the control and preventing its destruction.
759767
tab.Shutdown();
760768

761769
if (!movingAway)
762770
{
763-
_NotifyAgentTabClosed(closedTabStableId);
764-
765771
// Preexisting latent leak (made worse by pre-warm): tab close
766772
// goes through `Tab::Shutdown` → `Pane::Shutdown`, which only
767773
// calls `_setPaneContent(nullptr)` on each leaf — it does NOT
@@ -779,9 +785,13 @@ namespace winrt::TerminalApp::implementation
779785
// re-wrapped pane is the new owner), so decrementing here
780786
// would prematurely zero the refcount and tear down the
781787
// master that the dragged pane still depends on.
788+
// `tab_closed` reaches master asynchronously, and physical ACP
789+
// close has a bounded 15-second timeout. Keep the final job-object
790+
// reference alive slightly longer so last-tab/window teardown
791+
// cannot kill wta-master before session/close completes.
782792
for (size_t i = 0; i < agentPanesOnTab; ++i)
783793
{
784-
winrt::TerminalApp::implementation::SharedWta::Instance().ReleasePane();
794+
SharedWta::ReleasePaneAfterSessionClose();
785795
}
786796
}
787797

0 commit comments

Comments
 (0)