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
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,10 @@ Profiles use the global Windows-hosted agent by default. In a profile's
installed in that profile's WSL distro. The picker only lists the Windows host
and that one distro. An explicit profile selection is strict: if that agent
cannot start, the pane reports the failure without switching to another agent.
**Command palette agent** independently selects the agent used by `?<prompt>`
and the interactive delegate action for that profile. Its host or WSL selection
is also strict: if the selected agent is unavailable, delegation reports that
error instead of falling back to another agent or execution environment.

### Agent Management

Expand Down
8 changes: 8 additions & 0 deletions doc/cascadia/profiles.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -3203,6 +3203,14 @@
],
"pattern": "^(|host:[^:]+|wsl:[^:]+:[^:]+)$"
},
"commandPaletteAgent": {
"description": "Selects the agent used for command palette delegation from this profile. An empty value uses the global Windows-hosted delegate agent. An explicit host or WSL selection is strict and never falls back to another agent.",
"type": [
"string",
"null"
],
"pattern": "^(|host:[^:]+|wsl:[^:]+:[^:]+)$"
},
"suppressApplicationTitle": {
"description": "When set to true, tabTitle overrides the default title of the tab and any title change messages from the application will be suppressed. When set to false, tabTitle behaves as normal.",
"type": "boolean",
Expand Down
96 changes: 76 additions & 20 deletions src/cascadia/TerminalApp/TerminalPage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1318,28 +1318,81 @@ namespace winrt::TerminalApp::implementation
return;
}

// Resolve agent CLI from structured settings (acpAgent/acpModel).
// Resolve the command-palette agent from the active pane's profile.
// An empty profile value follows the global delegate setting on the
// Windows host. An explicit profile value selects both the agent and
// its exact execution source; WTA must not infer or fall back from it.
const auto& globals = _settings.GlobalSettings();
const auto agentCliPath = _ResolveEffectiveAgentCliPath(globals, [this]() { return _DetectAgentCli(); });
auto delegateAgent = _ResolveEffectiveDelegateAgent(globals);
auto delegateModel = globals.DelegateModel();
winrt::hstring delegateSource{ L"host" };
winrt::hstring delegateWslDistro;
if (const auto sourceProfile = _GetAgentSourceProfile(_GetFocusedTabImpl()))
{
const auto configuredValue = sourceProfile.CommandPaletteAgent();
const std::wstring_view configured{ configuredValue };
if (!configured.empty())
{
const auto backend = ::Microsoft::Terminal::Settings::Model::AgentPaneBackend::Parse(configured);
if (!backend)
{
_agentPaneLog("ABORT: invalid profile commandPaletteAgent");
return;
}
namespace Registry = ::Microsoft::Terminal::Settings::Model::AgentRegistry;
const auto allowedAgents = Registry::FilteredDelegateAgents();
const auto knownAndAllowed = std::any_of(
allowedAgents.begin(),
allowedAgents.end(),
[&](const auto& agent) {
return agent.id == backend->agentId;
});
// Only an empty profile setting follows the global delegate.
// An explicit unknown or blocked selection must fail closed.
if (!knownAndAllowed)
{
delegateAgent = {};
}
else
{
delegateAgent = winrt::hstring{ backend->agentId };
// There is no profile-scoped model setting — the profile's
// commandPaletteAgent selects only the agent and its exact
// execution source, so the global DelegateModel still
// applies and must not be cleared here.
if (backend->source == ::Microsoft::Terminal::Settings::Model::AgentPaneBackendSource::Wsl)
{
delegateSource = L"wsl";
delegateWslDistro = winrt::hstring{ backend->wslDistro };
}
}
}
}

// If no agent resolved and an AllowedAgents policy is active, bail out.
// This covers both "policy blocks ALL agents" and "policy allows some
// agents but none are installed" — in either case we must not launch
// WTA without --agent, because WTA's own fallback detection would
// bypass GPO and pick an unauthorized agent (e.g. copilot).
if (agentCliPath.empty() && AgentPolicy::IsAllowedAgentsPolicyConfigured())
// `wta delegate` is invoked with an exact delegate command below, so
// absence is an error. Do not let WTA derive a replacement from the
// ACP agent command.
if (delegateAgent.empty())
{
_agentPaneLog("ABORT: delegation blocked by GPO — no agents allowed");
if (auto tip{ FindName(L"WindowIdToast").try_as<MUX::Controls::TeachingTip>() })
_agentPaneLog("ABORT: no allowed delegate agent configured");
if (AgentPolicy::IsAllowedAgentsPolicyConfigured())
Comment thread
DDKinger marked this conversation as resolved.
{
_UpdateTeachingTipTheme(tip.try_as<winrt::Windows::UI::Xaml::FrameworkElement>());
tip.Title(RS_(L"AgentBlockedByPolicyTitle"));
tip.Subtitle(RS_(L"AgentBlockedByPolicySubtitle"));
tip.IsOpen(true);
if (auto tip{ FindName(L"WindowIdToast").try_as<MUX::Controls::TeachingTip>() })
{
_UpdateTeachingTipTheme(tip.try_as<winrt::Windows::UI::Xaml::FrameworkElement>());
tip.Title(RS_(L"AgentBlockedByPolicyTitle"));
tip.Subtitle(RS_(L"AgentBlockedByPolicySubtitle"));
tip.IsOpen(true);
}
}
return;
}

// The ACP command is retained for backward-compatible WTA context, but
// it is never used as a delegate fallback because --delegate-agent and
// --delegate-source are always supplied.
const auto agentCliPath = _ResolveEffectiveAgentCliPath(globals, [this]() { return _DetectAgentCli(); });

// Helper: escape and quote an argument for the command line.
auto quoteArg = [](std::wstring_view arg) -> std::wstring {
std::wstring escaped{ arg };
Expand All @@ -1350,7 +1403,9 @@ namespace winrt::TerminalApp::implementation
return L"\"" + escaped + L"\"";
};

// Build: wta [--language <lang>] delegate --agent <agent> --delegate-agent <delegate> "<prompt>"
// Build: wta [--language <lang>] delegate --agent <agent>
// --delegate-agent <delegate> --delegate-source <host|wsl>
// [--delegate-wsl-distro <distro>] "<prompt>"
//
// `--language` is a top-level Cli flag, so it must appear *before* the
// `delegate` subcommand — otherwise clap rejects it and the process
Expand All @@ -1370,12 +1425,12 @@ namespace winrt::TerminalApp::implementation
cmdline += L" --agent " + quoteArg(std::wstring_view{ agentCliPath });
}

const auto delegateAgent = _ResolveEffectiveDelegateAgent(globals);
if (!delegateAgent.empty())
cmdline += L" --delegate-agent " + quoteArg(std::wstring_view{ delegateAgent });
cmdline += L" --delegate-source " + quoteArg(std::wstring_view{ delegateSource });
if (!delegateWslDistro.empty())
{
cmdline += L" --delegate-agent " + quoteArg(std::wstring_view{ delegateAgent });
cmdline += L" --delegate-wsl-distro " + quoteArg(std::wstring_view{ delegateWslDistro });
}
const auto delegateModel = globals.DelegateModel();
if (!delegateModel.empty())
{
cmdline += L" --delegate-model " + quoteArg(std::wstring_view{ delegateModel });
Expand Down Expand Up @@ -1972,7 +2027,8 @@ namespace winrt::TerminalApp::implementation
}
else if (sourceProfile)
{
const auto configured = std::wstring_view{ sourceProfile.AgentPaneBackend() };
const auto configuredValue = sourceProfile.AgentPaneBackend();
const std::wstring_view configured{ configuredValue };
hasProfileBackend = !configured.empty();
if (const auto backend = ::Microsoft::Terminal::Settings::Model::AgentPaneBackend::Parse(configured))
{
Expand Down
Loading
Loading