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
15 changes: 11 additions & 4 deletions plugins/agent/composer/ui/Composer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,11 @@ export function Composer(props: ComposerViewInput<JSX.Element>) {
// Desktop only: the terminal keeps its start pane as it is.
const noConnections = () => !platform.isTerminal && (props.needsConnection?.() ?? false);
const pending = () => props.pending?.() ?? null;
// External CLIs run outside the session sandbox, so its setting would not apply to them.
const externalAgent = () => {
const backend = props.session() ? props.session()!.backend() : (pending()?.backend() ?? null);
return backend?.kind === 'executor' && backend.selection.executor === 'external';
};
let pendingDrop: ((event: { uris: string[] }) => void) | null = null;
props.dropFiles?.((event) => {
const owner = props.session();
Expand Down Expand Up @@ -860,10 +865,12 @@ export function Composer(props: ComposerViewInput<JSX.Element>) {
>
<Icon name="plus" color={C.secondary} />
</Button>
<SessionPermissionsSelect
owner={props.session()?.permissions ?? null}
pending={pending()?.permissions ?? null}
/>
<If when={!externalAgent()}>
<SessionPermissionsSelect
owner={props.session()?.permissions ?? null}
pending={pending()?.permissions ?? null}
/>
</If>
{/* After the sandbox, so leaving the start pane moves neither. */}
<If when={!platform.isTerminal && hero()}>{workspaceControl()}</If>
<If when={props.session() !== null || pending() !== null}>
Expand Down
12 changes: 12 additions & 0 deletions products/maka/terminal-shell/tests/real_host/external_agents.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ use std::{path::Path, sync::OnceLock};

const ACKNOWLEDGE: &str = "I understand and accept the external agent permissions";
const CONFIRM: &str = "Confirm permissions and enable";
const SANDBOX: &str = "Sandbox and approvals";

async fn fixture() -> Fixture {
let node = tokio::task::spawn_blocking(|| {
Expand Down Expand Up @@ -241,6 +242,14 @@ async fn toggle_runtime(view: &mut AppView, client: &Client, id: &str, name: &st
);
}

fn composer_sandbox(view: &AppView) -> Option<bool> {
let nodes = view.accessibility_snapshot();
nodes
.iter()
.any(|node| node.label == "Choose model and effort")
.then(|| nodes.iter().any(|node| node.label == SANDBOX))
}

async fn model_choices(view: &mut AppView) {
click(view, "Choose model and effort").await;
wait_view_state(view, |view, _| {
Expand Down Expand Up @@ -352,6 +361,8 @@ async fn external_agents_ui_consent_native_catalog_cache_and_failure_isolation()
observed["session"]["backend"].pointer("/selection/executor"),
Some(&json!("external"))
);
// External CLIs run outside the session sandbox, so the composer must not offer its setting.
wait_view_state(&mut view, |view, _| composer_sandbox(view) == Some(false)).await;

open_settings(&mut view).await;
update_worker_state(fixture.directory.path(), |state| {
Expand All @@ -376,6 +387,7 @@ async fn external_agents_ui_consent_native_catalog_cache_and_failure_isolation()
.await
.unwrap();
assert_eq!(observed["session"]["backend"]["kind"], "model");
wait_view_state(&mut view, |view, _| composer_sandbox(view) == Some(true)).await;

open_settings(&mut view).await;
toggle_runtime(&mut view, &client, "codex", "Codex", false).await;
Expand Down
Loading