Skip to content
Draft
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
2 changes: 1 addition & 1 deletion app/src/ai/execution_profiles/profiles.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2460,6 +2460,6 @@ impl Entity for AIExecutionProfilesModel {

impl SingletonEntity for AIExecutionProfilesModel {}

#[cfg(test)]
#[cfg(all(test, not(feature = "agent_mode_evals")))]
#[path = "profiles_tests.rs"]
mod tests;
1 change: 1 addition & 0 deletions app/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -398,6 +398,7 @@ pub(crate) enum LaunchMode {
/// Whether this CLI invocation is running in a sandboxed environment.
is_sandboxed: bool,
/// Override for computer use permission from CLI flags. If None, uses default behavior.
#[allow(dead_code)]
computer_use_override: Option<bool>,
},
/// Run a test - this may be an integration test or an eval.
Expand Down
3 changes: 3 additions & 0 deletions app/src/terminal/local_tty/terminal_manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ pub struct TerminalManager<S> {
pty_controller: ModelHandle<PtyController>,

/// The manager is responsible for managing the lifetime of the remote server controller.
#[allow(dead_code)]
remote_server_controller: ModelHandle<RemoteServerController>,

/// The process ID of the PTY. Purely used for integration tests. None if the PTY has not yet
Expand Down Expand Up @@ -194,6 +195,7 @@ impl<S> Drop for TerminalManager<S> {
impl<S> TerminalManager<S> {
/// Creates a local terminal manager model and terminal surface.
#[allow(clippy::too_many_arguments)]
#[allow(dead_code)]
pub(crate) fn create_model<PostWire>(
startup_directory: Option<PathBuf>,
env_vars: HashMap<OsString, OsString>,
Expand Down Expand Up @@ -500,6 +502,7 @@ impl<S> TerminalManager<S> {
}

/// Returns the remote server controller owned by this manager.
#[allow(dead_code)]
pub(super) fn remote_server_controller(&self) -> ModelHandle<RemoteServerController> {
self.remote_server_controller.clone()
}
Expand Down
7 changes: 7 additions & 0 deletions app/src/terminal/local_tty/terminal_view_adaptor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,18 +71,21 @@ use crate::terminal::writeable_pty::terminal_manager_util::wire_up_remote_server
use crate::terminal::{TerminalManager as TerminalManagerTrait, TerminalModel, TerminalView};
use crate::view_components::ToastFlavor;

#[allow(dead_code)]
const ACL_UPDATE_FAILURE_RESPONSE: &str = "Something went wrong. Please try again.";

/// Whether the given CRDT operation should be dropped when broadcasting
/// sharer input to viewers. In ambient agent sessions the sharer is a
/// headless worker — forwarding its selection ops would produce a phantom
/// cursor on the viewer side. Content ops (Edit / Undo) are kept so the
/// buffer stays in sync.
#[allow(dead_code)]
fn should_skip_sharer_op(is_ambient_session: bool, op: &CrdtOperation) -> bool {
is_ambient_session && matches!(op, CrdtOperation::UpdateSelections(_))
}

/// Configuration for constructing the GUI terminal surface.
#[allow(dead_code)]
pub(crate) struct TerminalViewSurfaceConfig {
pub(crate) resources: TerminalViewResources,
pub(crate) model_event_sender: Option<SyncSender<ModelEvent>>,
Expand All @@ -96,6 +99,7 @@ pub(crate) struct TerminalViewSurfaceConfig {
}

/// Resolves the block list used by the GUI `TerminalView` surface.
#[allow(dead_code)]
pub(crate) fn terminal_view_restored_blocks(
restored_blocks: Option<&Vec<SerializedBlockListItem>>,
conversation_restoration: &Option<ConversationRestorationInNewPaneType>,
Expand All @@ -122,6 +126,7 @@ pub(crate) fn terminal_view_restored_blocks(
}

/// Creates the GUI terminal surface and its manager-owned post-wiring closure.
#[allow(dead_code)]
pub(crate) fn create_terminal_view_surface(
config: TerminalViewSurfaceConfig,
surface_init: TerminalSurfaceInit,
Expand Down Expand Up @@ -240,6 +245,7 @@ pub(crate) fn create_terminal_view_surface(
/// remain unchanged; this helper groups the `TerminalView`-dependent wiring so it is
/// easy to identify and work on separately from the generic manager.
#[allow(clippy::too_many_arguments)]
#[allow(dead_code)]
fn wire_up_terminal_view_session_sharing(
view: &ViewHandle<TerminalView>,
current_prompt: ModelHandle<CurrentPrompt>,
Expand Down Expand Up @@ -613,6 +619,7 @@ fn wire_up_terminal_view_session_sharing(
session_sharer
}

#[allow(dead_code)]
impl TerminalManager<TerminalView> {
/// Streams all historical agent conversations from this terminal to viewers.
/// This is called when starting a shared session mid-conversation so that viewers
Expand Down
1 change: 1 addition & 0 deletions app/src/terminal/shared_session/presence_manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -777,6 +777,7 @@ impl PresenceManager {
/// Like `single_distinct_present_viewer_uid`, but reads directly from a
/// participant list before the presence manager finishes processing it.
#[cfg(not(target_arch = "wasm32"))]
#[allow(dead_code)]
pub(crate) fn single_distinct_present_viewer_uid_from_viewers<'a>(
viewers: impl Iterator<Item = &'a Viewer>,
) -> Option<&'a str> {
Expand Down
89 changes: 89 additions & 0 deletions app/src/terminal/view.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1172,6 +1172,7 @@ impl SizeUpdateBuilder {
}

#[cfg(not(target_arch = "wasm32"))]
#[allow(dead_code)]
fn for_viewer_size_report(last_size: SizeInfo, num_rows: usize, num_cols: usize) -> Self {
// Viewer size reports don't change the sharer's actual pane size.
Self {
Expand Down Expand Up @@ -1439,6 +1440,12 @@ pub enum ContextMenuAction {
exchange_id: AIAgentExchangeId,
conversation_id: AIConversationId,
},
/// Fork the AI conversation from the selected text in this AI block.
ForkAIConversationFromSelectedText {
ai_block_view_id: EntityId,
exchange_id: AIAgentExchangeId,
conversation_id: AIConversationId,
},
/// Save the AI block prompt as an agent mode workflow (saved prompt)
SavePromptAsAgentModeWorkflow {
ai_block_view_id: EntityId,
Expand Down Expand Up @@ -1524,6 +1531,9 @@ impl fmt::Debug for ContextMenuAction {
ForkAIConversationFromExactExchange { .. } => {
f.write_str("ForkAIConversationFromExactExchange")
}
ForkAIConversationFromSelectedText { .. } => {
f.write_str("ForkAIConversationFromSelectedText")
}
SavePromptAsAgentModeWorkflow { .. } => f.write_str("SavePromptAsAgentModeWorkflow"),
}
}
Expand Down Expand Up @@ -16644,6 +16654,35 @@ impl TerminalView {
.into_item(),
]);
}
if !cfg!(target_family = "wasm")
&& let BlockListMenuSource::RichContentTextRightClick {
rich_content_view_id,
..
} = menu_source
&& let Some(ai_metadata) =
self.ai_block_metadata_for_view_id(*rich_content_view_id)
&& self
.fork_selected_text_params(
*rich_content_view_id,
ai_metadata.exchange_id,
ai_metadata.conversation_id,
ctx,
)
.is_some()
{
fields.extend([
MenuItem::Separator,
MenuItemFields::new("Fork selection in new pane")
.with_on_select_action(TerminalAction::ContextMenu(
ContextMenuAction::ForkAIConversationFromSelectedText {
ai_block_view_id: *rich_content_view_id,
exchange_id: ai_metadata.exchange_id,
conversation_id: ai_metadata.conversation_id,
},
))
.into_item(),
]);
}
fields
}
(
Expand Down Expand Up @@ -20671,6 +20710,42 @@ impl TerminalView {
})
}

fn ai_block_metadata_for_view_id(&self, view_id: EntityId) -> Option<&AIBlockMetadata> {
self.rich_content_views.iter().find_map(|rich_content| {
let ai_metadata = rich_content.ai_block_metadata()?;
if ai_metadata.ai_block_handle.id() == view_id {
return Some(ai_metadata);
}
None
})
}

fn fork_selected_text_params(
&self,
ai_block_view_id: EntityId,
exchange_id: AIAgentExchangeId,
conversation_id: AIConversationId,
ctx: &AppContext,
) -> Option<ForkAIConversationParams> {
let initial_prompt = self
.ai_block_handle_by_view_id(ai_block_view_id)?
.as_ref(ctx)
.selected_text(ctx)
.filter(|text| !text.trim().is_empty())?;

Some(ForkAIConversationParams {
conversation_id,
fork_from_exchange: Some(ForkFromExchange {
exchange_id,
fork_from_exact_exchange: false,
}),
summarize_after_fork: false,
summarization_prompt: None,
initial_prompt: Some(initial_prompt),
destination: ForkedConversationDestination::SplitPane,
})
}

/// Returns the last block's `EnvVarCollectionBlock` if it is uncompleted, scoped to the
/// currently visible conversation.
fn active_env_var_collection_block(
Expand Down Expand Up @@ -24793,6 +24868,20 @@ impl TerminalView {
ctx,
);
}
ForkAIConversationFromSelectedText {
ai_block_view_id,
exchange_id,
conversation_id,
} => {
if let Some(params) = self.fork_selected_text_params(
*ai_block_view_id,
*exchange_id,
*conversation_id,
ctx,
) {
ctx.dispatch_global_action("workspace:fork_ai_conversation", params);
}
}
SavePromptAsAgentModeWorkflow { ai_block_view_id } => {
for rich_content in self.rich_content_views.iter() {
if let Some(ai_metadata) = rich_content.ai_block_metadata()
Expand Down
1 change: 1 addition & 0 deletions app/src/terminal/view/shared_session/view_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2020,6 +2020,7 @@ impl TerminalView {
/// Resizes the sharer's terminal to match the viewer's reported size,
/// going through the normal view/model/PTY resize pipeline.
#[cfg(not(target_arch = "wasm32"))]
#[allow(dead_code)]
pub(crate) fn resize_from_viewer_report(
&mut self,
viewer_size: WindowSize,
Expand Down
107 changes: 107 additions & 0 deletions app/src/terminal/view_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8009,6 +8009,113 @@ fn cmd_k_does_not_clear_buffer_when_agent_is_driving_command() {
})
}

#[test]
fn selected_ai_block_text_can_be_forked_in_new_pane() {
App::test((), |mut app| async move {
initialize_app_for_terminal_view(&mut app);
let _agent_view = FeatureFlag::AgentView.override_enabled(true);
let terminal = add_window_with_terminal(&mut app, None);

terminal.update(&mut app, |view, ctx| {
append_exchange_and_handle_event(
view,
AIAgentInput::UserQuery {
query: "explain the selected text".to_owned(),
context: Default::default(),
static_query_type: None,
referenced_attachments: Default::default(),
user_query_mode: UserQueryMode::Normal,
running_command: None,
intended_agent: None,
},
ctx,
);
});

let (ai_block, exchange_id, conversation_id) = terminal.read(&app, |view, _| {
view.rich_content_views
.iter()
.find_map(|rich_content| {
let metadata = rich_content.ai_block_metadata()?;
Some((
metadata.ai_block_handle.clone(),
metadata.exchange_id,
metadata.conversation_id,
))
})
.expect("an AI block should have been inserted")
});

ai_block.update(&mut app, |block, ctx| {
block.set_block_level_selected_text_for_test(Some("selected agent text".to_owned()));
block.handle_action(&AIBlockAction::SelectText, ctx);
});

terminal.update(&mut app, |view, ctx| {
let params = view
.fork_selected_text_params(ai_block.id(), exchange_id, conversation_id, ctx)
.expect("selected AI block text should produce fork params");

assert_eq!(params.conversation_id, conversation_id);
assert_eq!(
params.initial_prompt,
Some("selected agent text".to_owned())
);
assert!(matches!(
params.fork_from_exchange,
Some(ForkFromExchange {
exchange_id: actual_exchange_id,
fork_from_exact_exchange: false,
}) if actual_exchange_id == exchange_id
));
assert_eq!(params.destination, ForkedConversationDestination::SplitPane);

let menu_source = BlockListMenuSource::RichContentTextRightClick {
rich_content_view_id: ai_block.id(),
position_in_rich_content: Vector2F::zero(),
};
let menu_items = view.context_menu_items(&menu_source, ctx);
let labels = menu_items
.iter()
.filter_map(|item| item.fields().map(|fields| fields.label()))
.collect::<Vec<_>>();
assert!(
labels.contains(&"Fork selection in new pane"),
"Expected selected AI text menu to include fork action, got {labels:?}"
);
});
})
}

#[test]
fn regular_selected_text_context_menu_does_not_include_fork_selection() {
App::test((), |mut app| async move {
initialize_app_for_terminal_view(&mut app);

let terminal = add_window_with_terminal(&mut app, None);
terminal.update(&mut app, |view, ctx| {
{
let mut model = view.model.lock();
model.simulate_block("ls", "foo");
assert!(!model.is_block_list_empty());
}

let menu_source = BlockListMenuSource::RegularTextRightClick {
position_in_terminal_view: Vector2F::zero(),
};
let menu_items = view.context_menu_items(&menu_source, ctx);
let labels = menu_items
.iter()
.filter_map(|item| item.fields().map(|fields| fields.label()))
.collect::<Vec<_>>();
assert!(
!labels.contains(&"Fork selection in new pane"),
"Did not expect regular selected-text menu to include fork action, got {labels:?}"
);
});
})
}

#[test]
fn cmd_k_in_agent_view_clears_active_block_not_full_buffer_when_agent_driving_command() {
App::test((), |mut app| async move {
Expand Down
3 changes: 3 additions & 0 deletions app/src/terminal/writeable_pty/remote_server_controller.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ use crate::{TelemetryEvent, send_telemetry_from_ctx};
///
/// Every active state carries `setup_start` so that the total setup duration
/// can be measured when the flow reaches `SessionConnected`.
#[allow(dead_code)]
enum SshInitState {
Idle,
/// Stash held, `check_binary` in flight.
Expand Down Expand Up @@ -352,6 +353,7 @@ impl<T: EventLoopSender> RemoteServerController<T> {
}
}

#[allow(dead_code)]
pub fn handle_ssh_remote_server_install(
&mut self,
session_id: SessionId,
Expand Down Expand Up @@ -476,6 +478,7 @@ impl<T: EventLoopSender> RemoteServerController<T> {
self.flush_stashed_bootstrap(session_info, ctx);
}

#[allow(dead_code)]
pub fn handle_ssh_remote_server_skip(
&mut self,
session_id: SessionId,
Expand Down
1 change: 1 addition & 0 deletions app/src/terminal/writeable_pty/terminal_manager_util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ pub fn wire_up_pty_controller_with_surface<T: EventLoopSender, S: TerminalSurfac
/// in the subscription callbacks because that will create a reference cycle. Instead,
/// we should use weak handles and upgrade them lazily.
#[cfg(not(target_family = "wasm"))]
#[allow(dead_code)]
pub fn wire_up_remote_server_controller_with_view<T: EventLoopSender>(
remote_server_controller: &ModelHandle<
super::remote_server_controller::RemoteServerController<T>,
Expand Down
1 change: 1 addition & 0 deletions crates/onboarding/src/bin/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@ impl OnboardingMainView {
| AgentOnboardingEvent::UpgradeRequested
| AgentOnboardingEvent::UpgradeCopyUrlRequested
| AgentOnboardingEvent::UpgradePasteTokenFromClipboardRequested
| AgentOnboardingEvent::OfferSetUpLaterSelected { .. }
| AgentOnboardingEvent::LoginFromWelcomeRequested
| AgentOnboardingEvent::PrivacySettingsFromTerminalThemeSlideRequested
| AgentOnboardingEvent::AppBecameActive => {
Expand Down
Loading