Skip to content
Open
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
549 changes: 549 additions & 0 deletions doc/specs/deterministic-pane-actions.md

Large diffs are not rendered by default.

15 changes: 4 additions & 11 deletions src/cascadia/TerminalApp/Pane.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3426,22 +3426,15 @@ void Pane::SetAgentChipVisible(bool value)
}

// The connection's session GUID for terminal panes. Returns the empty
// guid for non-terminal panes (e.g. branch nodes, agent panes, snippets).
// guid for non-terminal panes (e.g. branch nodes and snippets).
// Used by Tab to match a protocol-supplied pane id to a Pane.
winrt::guid Pane::GetSessionId() const
{
// Mirror `_getSessionIdFromPane` in TerminalPage.Protocol.cpp:
// walk content → control → connection and read the SessionId. Using
// GetContent() (instead of `_content` directly) keeps the non-leaf
// case to a clean nullptr without needing an explicit leaf check.
if (const auto termContent = GetContent().try_as<winrt::TerminalApp::TerminalPaneContent>())
if (const auto control = GetTerminalControl())
{
if (const auto control = termContent.GetTermControl())
if (const auto conn = control.Connection())
{
if (const auto conn = control.Connection())
{
return conn.SessionId();
}
return conn.SessionId();
}
}
return {};
Expand Down
23 changes: 23 additions & 0 deletions src/cascadia/TerminalApp/Tab.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -761,6 +761,7 @@ namespace winrt::TerminalApp::implementation
// possible that the focus events won't propagate immediately. Updating
// the focus here will give the same effect though.
_UpdateActivePane(newPane);
_RaisePaneCatalogChanged();

return { original, newPane };
}
Expand Down Expand Up @@ -835,6 +836,7 @@ namespace winrt::TerminalApp::implementation

// After split, Close Pane Menu Item should be visible
_closePaneMenuItem.Visibility(WUX::Visibility::Visible);
_RaisePaneCatalogChanged();

return { originalTree, pane };
}
Expand Down Expand Up @@ -862,6 +864,7 @@ namespace winrt::TerminalApp::implementation
{
// Just make sure that the remaining pane is marked active
_UpdateActivePane(_rootPane->GetActivePane());
_RaisePaneCatalogChanged();

return pane;
}
Expand Down Expand Up @@ -945,6 +948,7 @@ namespace winrt::TerminalApp::implementation
{
_UpdateActivePane(focus);
}
_RaisePaneCatalogChanged();
}

// Method Description:
Expand Down Expand Up @@ -1085,6 +1089,10 @@ namespace winrt::TerminalApp::implementation
_changingActivePane = true;
const auto res = _rootPane->SwapPanes(_activePane, neighbor);
_changingActivePane = false;
if (res)
{
_RaisePaneCatalogChanged();
}
return res;
}

Expand Down Expand Up @@ -1227,6 +1235,7 @@ namespace winrt::TerminalApp::implementation
if (const auto tab = weakThis.get())
{
tab->UpdateTitle();
tab->_RaisePaneCatalogChanged();
}
});

Expand Down Expand Up @@ -1278,6 +1287,7 @@ namespace winrt::TerminalApp::implementation
if (auto tab{ weakThisCopy.get() })
{
tab->_UpdateConnectionClosedState();
tab->_RaisePaneCatalogChanged();
}
});

Expand All @@ -1289,6 +1299,7 @@ namespace winrt::TerminalApp::implementation
if (auto tab{ weakThisCopy.get() })
{
tab->_RecalculateAndApplyReadOnly();
tab->_RaisePaneCatalogChanged();
}
});

Expand Down Expand Up @@ -1534,6 +1545,7 @@ namespace winrt::TerminalApp::implementation

// Raise our own ActivePaneChanged event.
ActivePaneChanged.raise(*this, nullptr);
_RaisePaneCatalogChanged();

// If the new active pane is a terminal, tell other interested panes
// what the new active pane is.
Expand Down Expand Up @@ -1609,6 +1621,11 @@ namespace winrt::TerminalApp::implementation
_UpdateAgentChipVisibility();
}

void Tab::_RaisePaneCatalogChanged()
{
PaneCatalogChanged.raise(*this, nullptr);
}

void Tab::_UpdateMenuItemStates()
{
// Terminal-specific menu items
Expand Down Expand Up @@ -1712,6 +1729,7 @@ namespace winrt::TerminalApp::implementation
}
}
}
tab->_RaisePaneCatalogChanged();
}
});

Expand Down Expand Up @@ -1741,6 +1759,7 @@ namespace winrt::TerminalApp::implementation
break;
}
}
tab->_RaisePaneCatalogChanged();
}
}
});
Expand Down Expand Up @@ -2346,6 +2365,7 @@ namespace winrt::TerminalApp::implementation
_UpdateActivePane(focusTarget);
focusTarget->SetActive();
}
_RaisePaneCatalogChanged();
}

// Method Description:
Expand All @@ -2371,6 +2391,7 @@ namespace winrt::TerminalApp::implementation
// Restore the pane in the XAML tree.
parent->RestorePane(_hiddenPane);
_hiddenPane = nullptr;
_RaisePaneCatalogChanged();
}

bool Tab::HasHiddenPane()
Expand Down Expand Up @@ -2589,6 +2610,7 @@ namespace winrt::TerminalApp::implementation
}
}
}
_RaisePaneCatalogChanged();
}

// Re-attach a previously-stashed agent pane. `direction` is accepted
Expand Down Expand Up @@ -2633,6 +2655,7 @@ namespace winrt::TerminalApp::implementation
{
_UpdateActivePane(agentPane);
}
_RaisePaneCatalogChanged();

// CRITICAL: synchronous Focus(Programmatic) is unreliable on
// freshly-re-parented or freshly-spawned TermControls. The element
Expand Down
2 changes: 2 additions & 0 deletions src/cascadia/TerminalApp/Tab.h
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,7 @@ namespace winrt::TerminalApp::implementation
til::typed_event<TerminalApp::TerminalPaneContent> RestartTerminalRequested;

til::typed_event<TerminalApp::Tab, IInspectable> ActivePaneChanged;
til::typed_event<TerminalApp::Tab, IInspectable> PaneCatalogChanged;
til::event<winrt::delegate<>> TabRaiseVisualBell;
til::event<winrt::delegate<winrt::hstring /*title*/, winrt::hstring /*body*/, winrt::TerminalApp::IPaneContent /*content*/>> TabToastNotificationRequested;
til::typed_event<IInspectable, IInspectable> TaskbarProgressChanged;
Expand Down Expand Up @@ -329,6 +330,7 @@ namespace winrt::TerminalApp::implementation
void _AttachEventHandlersToPane(std::shared_ptr<Pane> pane);

void _UpdateActivePane(std::shared_ptr<Pane> pane);
void _RaisePaneCatalogChanged();
void _UpdateMenuItemStates();
void _UpdateAgentChipVisibility();

Expand Down
82 changes: 82 additions & 0 deletions src/cascadia/TerminalApp/TerminalPage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1777,6 +1777,76 @@ namespace winrt::TerminalApp::implementation
winrt::to_hstring(Json::writeString(wb, evt)));
}

void TerminalPage::_PublishPaneCatalog(const winrt::com_ptr<Tab>& tab)
{
if (!tab)
{
return;
}

if (!tab->FindAgentPane())
{
return;
}

const auto tabId = tab->StableId();
Json::Value params{ Json::objectValue };
params["window_id"] = std::to_string(_WindowProperties.WindowId());
params["tab_id"] = winrt::to_string(tabId);
params["generation"] = Json::UInt64{ ++_paneCatalogGenerations[tabId] };

Json::Value panes{ Json::arrayValue };
const auto root = tab->GetRootPane();
const auto activePane = tab->GetActivePane();
const bool activeIsAgent = activePane && activePane->IsAgentPane();
uint32_t ordinal = 0;
if (root)
{
root->WalkTree([&](const std::shared_ptr<Pane>& pane) {
if (!pane || !pane->GetContent())
{
return;
}
const auto sessionId = pane->GetSessionId();
if (sessionId == winrt::guid{})
{
return;
}

Json::Value descriptor{ Json::objectValue };
descriptor["session_id"] = winrt::to_string(::Microsoft::Console::Utils::GuidToString(sessionId));
descriptor["ordinal"] = ++ordinal;
descriptor["title"] = winrt::to_string(pane->GetContent().Title());
descriptor["is_active"] = activeIsAgent ? pane->IsSourceOfAgentPane() : pane == activePane;
descriptor["is_agent_pane"] = pane->IsAgentPane();
descriptor["visible"] = !pane->IsHidden();
descriptor["read_only"] = pane->GetContent().ReadOnly();

if (const auto profile = pane->GetProfile())
{
descriptor["profile"] = winrt::to_string(profile.Name());
}
else
{
descriptor["profile"] = "";
}
if (const auto control = pane->GetTerminalControl())
{
descriptor["cwd"] = winrt::to_string(control.WorkingDirectory());
descriptor["shell"] = winrt::to_string(control.ShellName());
}
else
{
descriptor["cwd"] = "";
descriptor["shell"] = "";
}
panes.append(std::move(descriptor));
});
}
params["panes"] = std::move(panes);
_RaiseProtocolEvent("pane_catalog_changed", params);
}

// Close the agent pane in a specific tab, if it has one.
//
// Under the helper+master architecture, wta-helper processes are
Expand Down Expand Up @@ -1917,6 +1987,7 @@ namespace winrt::TerminalApp::implementation
tabParams["tab_id"] = winrt::to_string(tabId);
tabParams["window_id"] = std::to_string(_WindowProperties.WindowId());
_RaiseProtocolEvent("tab_closed", tabParams);
_paneCatalogGenerations.erase(tabId);
}

// Explicitly emit `connection_state:closed` for every terminal leaf
Expand Down Expand Up @@ -5495,6 +5566,7 @@ namespace winrt::TerminalApp::implementation
_UpdateBottomBarState();
}
}
_PublishPaneCatalog(targetTab);
}

// Inbound event from WTA: {method:"close_agent_pane", params:{tab_id}}.
Expand Down Expand Up @@ -6728,6 +6800,16 @@ namespace winrt::TerminalApp::implementation
hostingTab.TaskbarProgressChanged({ get_weak(), &TerminalPage::_SetTaskbarProgressHandler });

hostingTab.RestartTerminalRequested({ get_weak(), &TerminalPage::_restartPaneConnection });

hostingTab.PaneCatalogChanged([weakTab, weakThis](auto&&, auto&&) {
if (const auto page = weakThis.get())
{
if (const auto tab = weakTab.get())
{
page->_PublishPaneCatalog(tab);
}
}
});
}

// Method Description:
Expand Down
2 changes: 2 additions & 0 deletions src/cascadia/TerminalApp/TerminalPage.h
Original file line number Diff line number Diff line change
Expand Up @@ -518,6 +518,7 @@ namespace winrt::TerminalApp::implementation
// distinguishes them. Entries are consumed on read and otherwise
// expire after a few seconds.
std::unordered_map<winrt::hstring, std::chrono::steady_clock::time_point> _agentPaneRestartSuppression;
std::unordered_map<winrt::hstring, uint64_t> _paneCatalogGenerations;
AgentSettingsSnapshot _CaptureAgentSettingsSnapshot() const;
// Compares only agent-CLI *identity* fields — the change that forces
// a master respawn. Model/delegate changes are handled by
Expand All @@ -532,6 +533,7 @@ namespace winrt::TerminalApp::implementation
// ProtocolVtSequenceReceived. Single source of the wta protocol-event
// wire shape — callers just supply the method name and a params object.
void _RaiseProtocolEvent(std::string_view method, const Json::Value& params);
void _PublishPaneCatalog(const winrt::com_ptr<Tab>& tab);
void _TeardownAgentPane(const winrt::com_ptr<Tab>& tab, bool suppressMasterRestart = true);
void _RebuildAgentStack();
// Scoped per-tab rebuild after a tab's agent override changes
Expand Down
17 changes: 17 additions & 0 deletions tools/wta/locales/af-ZA.yml
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,14 @@ input.image_attachments: "📎 Beelde: %{items}" # {Locked="📎","%{items}"}
# ── Opdrag-opspring (src/ui/command_popup.rs) ─────────────────────────────────
# {Locked="/"} - skuinsstreep-opdrag voorvoegsel, moet ASCII bly
commands.popup_title: " / opdragte "
delegate_picker.title: " Delegeer bestemming "
delegate_picker.tab: "Nuwe oortjie"
delegate_picker.panel: "Nuwe paneel"
delegate_picker.intent_required: "Beskryf die werk om te delegeer."
delegate_picker.pane_not_allowed: "/delegate skep altyd 'n nuwe bestemming en aanvaar nie @pane nie."
delegate_picker.not_configured: "Geen afgevaardigde agent is opgestel nie."
delegate_picker.no_parent_pane: "Geen werkvenster is beskikbaar om te verdeel nie."
delegate_picker.launch_failed: "Kon nie die afgevaardigde begin nie: %{error}"
commands.help_title: " Hulp "
commands.help_header: "Skuinsstreep-opdragte — tik / in die invoerveld."
commands.help_escape_hint: " //text Stuur 'n letterlike '/' (ontsnap die opdragontleder)" # {Locked="//text","/"}
Expand All @@ -69,6 +77,7 @@ commands.help_close_hint: " Esc Maak hierdie hulp toe" # {Locked="Esc"}
# {Locked="Enter","Esc","↵","↑","↓","←","→"} - sleutelname en Enter-sleutel-glief, moenie vertaal nie
recommendations.nav_hint: "(↑ ↓ om te navigeer • ← → om knoppies te wissel • Enter om te kies • Esc om te kanselleer)"
recommendations.button_run_command: "[ Voer opdrag uit ]"
recommendations.button_send_to_delegate: "[ Stuur na afgevaardigde ]"
recommendations.button_insert_in_terminal: "Voeg in Terminaal in"
recommendations.button_open_in_new_tab: "Maak oop in Nuwe Oortjie ↵"
recommendations.button_open_in_new_panel: "Maak oop in Nuwe Paneel ↵"
Expand Down Expand Up @@ -200,12 +209,20 @@ commands.restart.summary: "Restart agent"
commands.stop.summary: "Cancel the in-flight prompt"
commands.sessions.summary: "Open the historical sessions picker (Ctrl+Shift+/)" # {Locked="Ctrl+Shift+/"}
commands.agent.summary: "Kies die agent vir hierdie oortjie (/agent <id> om direk oor te skakel)" # {Locked="/agent","<id>"}
commands.command.summary: "Create a terminal command card from an intent (/command <intent>)" # {Locked="/command","<intent>"}
command_card.intent_required: "Describe the terminal command you want Copilot to prepare."
commands.delegate.summary: "Berei 'n taak voor en maak 'n afgevaardigde agent oop (/delegate <intent>)"

# ── Aanbevelingsuitvoering (src/coordinator.rs) ───────────────────────────
system.choice_execution_failed: "Keuse %{choice} het misluk: %{error}"

# ── Agent picker (src/ui/agent_popup.rs) ────────────────────────────────────
# {Locked="↑","↓","Enter","Esc"} - key names, do not translate
pane_picker.title: "Kies terminaalpaneel (↑ ↓ • Enter • Esc)"
pane_picker.empty: "Geen skryfbare terminaalvensters is beskikbaar nie."
pane_picker.target_unavailable: "Die geselekteerde paneel is nie meer beskikbaar nie."
direct_command.command_required: "Enter a command after $." # {Locked="$"}
direct_command.execution_unavailable: "Terminal command execution is unavailable."
agent_picker.title: "Kies agent (↑ ↓ • Enter • Esc)" # Agent means an AI coding agent
# Shown when no policy-allowed AI agent CLI is installed on the machine.
system.no_agents: "Geen beskikbare agent-CLI is op hierdie masjien gevind nie." # {Locked="CLI"}
Expand Down
17 changes: 17 additions & 0 deletions tools/wta/locales/am-ET.yml
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,14 @@ input.image_attachments: "📎 ምስሎች: %{items}" # {Locked="📎","%{item
# ── Command popup (src/ui/command_popup.rs) ─────────────────────────────────
# {Locked="/"} - slash-command prefix, must remain ASCII
commands.popup_title: " / commands "
delegate_picker.title: " መድረሻን ውክልና። "
delegate_picker.tab: " አዲስ ትር"
delegate_picker.panel: " አዲስ ፓነል"
delegate_picker.intent_required: " የውክልና ሥራውን ይግለጹ።"
delegate_picker.pane_not_allowed: "/delegate ሁልጊዜ አዲስ መድረሻ ይፈጥራል እና @pane አይቀበልም።"
delegate_picker.not_configured: " ምንም የውክልና ወኪል አልተዋቀረም።"
delegate_picker.no_parent_pane: " ለመከፋፈል ምንም የሚሰራ መስኮት የለም።"
delegate_picker.launch_failed: " ልዑካንን ማስጀመር አልተቻለም፦ %{error}"
commands.help_title: " Help "
commands.help_header: "Slash commands — type / in the input box."
commands.help_escape_hint: " //text Send a literal '/' (escapes the command parser)" # {Locked="//text","/"}
Expand All @@ -69,6 +77,7 @@ commands.help_close_hint: " Esc Close this help" # {Locked="Esc"}
# {Locked="Enter","Esc","↵","↑","↓","←","→"} - key names and Return-key glyph, do not translate
recommendations.nav_hint: "(↑ ↓ ለማሰስ • ← → አዝራሮችን ለመቀየር • Enter ለመምረጥ • Esc ለመሰረዝ)"
recommendations.button_run_command: "[ Run command ]"
recommendations.button_send_to_delegate: "[ ወደ ልዑካን ላክ]"
recommendations.button_insert_in_terminal: "Insert in Terminal"
recommendations.button_open_in_new_tab: "Open in New Tab ↵"
recommendations.button_open_in_new_panel: "Open in New Panel ↵"
Expand Down Expand Up @@ -200,12 +209,20 @@ commands.restart.summary: "Restart agent"
commands.stop.summary: "Cancel the in-flight prompt"
commands.sessions.summary: "Open the historical sessions picker (Ctrl+Shift+/)" # {Locked="Ctrl+Shift+/"}
commands.agent.summary: "ለዚህ ትር ሞዴሉን ይምረጡ (በቀጥታ ለመቀየር /agent <id>)" # {Locked="/agent","<id>"}
commands.command.summary: "Create a terminal command card from an intent (/command <intent>)" # {Locked="/command","<intent>"}
command_card.intent_required: "Describe the terminal command you want Copilot to prepare."
commands.delegate.summary: "አንድ ተግባር አዘጋጁ እና የውክልና ወኪል ይክፈቱ (/delegate <intent>)"

# ── የምክር አፈጻጸም (src/coordinator.rs) ───────────────────────────
system.choice_execution_failed: "ምርጫ %{choice} አልተሳካም፦ %{error}"

# ── Agent picker (src/ui/agent_popup.rs) ────────────────────────────────────
# {Locked="↑","↓","Enter","Esc"} - key names, do not translate
pane_picker.title: "የተርሚናል መቃን ምረጥ (↑ ↓ • Enter • Esc)"
pane_picker.empty: " ምንም ሊጻፍ የሚችል ተርሚናል ፓነሎች አይገኙም።"
pane_picker.target_unavailable: "የተመረጠው ፓነል ከአሁን በኋላ አይገኝም።"
direct_command.command_required: "Enter a command after $." # {Locked="$"}
direct_command.execution_unavailable: "Terminal command execution is unavailable."
agent_picker.title: "ኤጅንት ይምረጡ (↑ ↓ • Enter • Esc)" # Agent means an AI coding agent
# Shown when no policy-allowed AI agent CLI is installed on the machine.
system.no_agents: "ምንም የሚገኝ የኤጅንት CLI በዚህ ማሽን ላይ አልተገኘም።" # {Locked="CLI"}
Expand Down
Loading
Loading