Skip to content
Merged
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
11 changes: 10 additions & 1 deletion app/src/cortex_settings/action.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,20 @@ use crate::settings::{
pub enum CortexSettingsSection {
WorkingPanes,
Tabs,
Ai,
}

impl CortexSettingsSection {
pub fn label(self) -> &'static str {
match self {
Self::WorkingPanes => "Working Panes",
Self::Tabs => "Tabs",
Self::Ai => "AI",
}
}

pub fn all() -> &'static [Self] {
&[Self::WorkingPanes, Self::Tabs]
&[Self::WorkingPanes, Self::Tabs, Self::Ai]
}
}

Expand Down Expand Up @@ -72,4 +74,11 @@ pub enum CortexSettingsAction {
SetTabTitleFontWeight(Weight),
/// Flip the italic toggle for tab titles.
ToggleTabTitleItalic,
/// Flip the "Allow Claude Code / Codex as orchestrate child agents" toggle
/// on the AI page. Mirrors the persisted [`crate::settings::CortexSettings`]
/// bool *and* pushes the same value into
/// `FeatureFlag::LocalClaudeCodexChildHarnesses` via `set_user_preference`,
/// so the orchestration controls (`local_child_harnesses.rs`,
/// `orchestration_controls.rs`) react without a restart.
ToggleAllowLocalClaudeCodexChildHarnesses,
}
109 changes: 109 additions & 0 deletions app/src/cortex_settings/ai_page.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
//! Content rendered on the right side of the Cortex Settings pane when the
//! "AI" section is selected.
//!
//! Currently houses a single toggle: `Allow Claude Code / Codex as
//! orchestrate child agents`. The toggle is the user-facing surface for
//! [`warp_core::features::FeatureFlag::LocalClaudeCodexChildHarnesses`]; the
//! flag stays the single source of truth read by
//! `app/src/ai/local_child_harnesses.rs` and
//! `app/src/ai/blocklist/inline_action/orchestration_controls.rs`, and the
//! setting just hydrates that flag at startup and on each user flip.
use warpui::{
elements::{
Align, Container, CrossAxisAlignment, Element, Flex, Padding, ParentElement, Shrinkable,
},
ui_components::{components::UiComponent, switch::SwitchStateHandle},
AppContext, SingletonEntity,
};

use crate::appearance::Appearance;
use crate::cortex_settings::action::CortexSettingsAction;
use crate::settings::CortexSettings;

const ROW_VERTICAL_PADDING: f32 = 6.0;
const CONTROL_RIGHT_PADDING: f32 = 5.0;

/// Per-toggle UI state that has to outlive a single render frame (switch
/// animation state). Owned by `CortexSettingsView`.
#[derive(Default)]
pub struct AiPageState {
allow_local_claude_codex_child_harnesses_switch: SwitchStateHandle,
}

pub fn ai_page_search_terms() -> &'static [&'static str] {
&[
"ai",
"agent",
"orchestrate",
"orchestration",
"claude",
"claude code",
"codex",
"child",
"harness",
"subagent",
"sub-agent",
]
}

pub fn render_ai_page(
state: &AiPageState,
appearance: &Appearance,
app: &AppContext,
) -> Box<dyn Element> {
Flex::column()
.with_cross_axis_alignment(CrossAxisAlignment::Stretch)
.with_child(render_allow_local_claude_codex_child_harnesses_row(
state, appearance, app,
))
.finish()
}

fn render_allow_local_claude_codex_child_harnesses_row(
state: &AiPageState,
appearance: &Appearance,
app: &AppContext,
) -> Box<dyn Element> {
let ui_builder = appearance.ui_builder();
let current_value = *CortexSettings::as_ref(app).allow_local_claude_codex_child_harnesses;

let label = ui_builder
.span("Allow Claude Code / Codex as orchestrate child agents".to_string())
.build()
.finish();

let switch = ui_builder
.switch(
state
.allow_local_claude_codex_child_harnesses_switch
.clone(),
)
.check(current_value)
.build()
.on_click(move |ctx, _, _| {
ctx.dispatch_typed_action(
CortexSettingsAction::ToggleAllowLocalClaudeCodexChildHarnesses,
);
})
.finish();

let header = Shrinkable::new(
1.0,
Container::new(Align::new(label).left().finish()).finish(),
)
.finish();

let control = Container::new(switch)
.with_padding_right(CONTROL_RIGHT_PADDING)
.finish();

let row = Flex::row()
.with_cross_axis_alignment(CrossAxisAlignment::Center)
.with_child(header)
.with_child(control)
.finish();

Container::new(row)
.with_padding(Padding::uniform(ROW_VERTICAL_PADDING))
.finish()
}
1 change: 1 addition & 0 deletions app/src/cortex_settings/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
//! `app/src/pane_group/pane/cortex_settings_pane.rs` because it needs access
//! to `pub(super)` items in the pane-group module hierarchy.
pub mod action;
pub mod ai_page;
pub mod brand;
pub mod pane_manager;
pub mod tabs_page;
Expand Down
38 changes: 36 additions & 2 deletions app/src/cortex_settings/view.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ use warpui::{

use crate::appearance::Appearance;
use crate::cortex_settings::action::{CortexSettingsAction, CortexSettingsSection};
use crate::cortex_settings::ai_page::{ai_page_search_terms, render_ai_page, AiPageState};
use crate::cortex_settings::brand::{
BRAND_HEADER_ICON_TO_TITLE_FONT_RATIO, BRAND_HEADER_TITLE_TO_FONT_RATIO,
BRAND_MENU_ICON_LABEL_GAP_RATIO,
Expand Down Expand Up @@ -81,6 +82,7 @@ pub struct CortexSettingsView {
sidebar_states: Vec<(CortexSettingsSection, MouseStateHandle)>,
working_panes_state: WorkingPanesPageState,
tabs_state: TabsPageState,
ai_state: AiPageState,
search_editor: ViewHandle<EditorView>,
}

Expand Down Expand Up @@ -119,6 +121,7 @@ impl CortexSettingsView {
sidebar_states,
working_panes_state: WorkingPanesPageState::default(),
tabs_state: TabsPageState::new(ctx),
ai_state: AiPageState::default(),
search_editor,
}
}
Expand Down Expand Up @@ -321,6 +324,32 @@ impl CortexSettingsView {
ctx.notify();
}

fn toggle_allow_local_claude_codex_child_harnesses(&mut self, ctx: &mut ViewContext<Self>) {
use crate::settings::CortexSettings;
use settings::ToggleableSetting;
use warp_core::features::FeatureFlag;
use warpui::SingletonEntity;

// Read pre-toggle value so we can compute the post-toggle value and push
// it into the feature flag without re-reading after the closure (which
// would race with rendering threads that have already snapshotted).
let previous_value = *CortexSettings::as_ref(ctx).allow_local_claude_codex_child_harnesses;

CortexSettings::handle(ctx).update(ctx, |settings, ctx| {
let _ = settings
.allow_local_claude_codex_child_harnesses
.toggle_and_save_value(ctx);
});

// Mirror the setting into the runtime feature flag so the existing
// gate sites (`local_child_harnesses.rs`, `orchestration_controls.rs`)
// pick up the change on the next `is_enabled()` call without needing
// to be ported to read the setting directly.
FeatureFlag::LocalClaudeCodexChildHarnesses.set_user_preference(!previous_value);

ctx.notify();
}

fn handle_search_editor_event(
&mut self,
_editor: ViewHandle<EditorView>,
Expand Down Expand Up @@ -442,6 +471,7 @@ impl CortexSettingsView {
render_working_panes_page(&self.working_panes_state, appearance, app)
}
CortexSettingsSection::Tabs => render_tabs_page(&self.tabs_state, appearance, app),
CortexSettingsSection::Ai => render_ai_page(&self.ai_state, appearance, app),
}
}
}
Expand Down Expand Up @@ -523,6 +553,9 @@ impl warpui::TypedActionView for CortexSettingsView {
self.set_tab_title_font_weight(*value, ctx)
}
CortexSettingsAction::ToggleTabTitleItalic => self.toggle_tab_title_italic(ctx),
CortexSettingsAction::ToggleAllowLocalClaudeCodexChildHarnesses => {
self.toggle_allow_local_claude_codex_child_harnesses(ctx)
}
}
}
}
Expand Down Expand Up @@ -628,8 +661,9 @@ impl BackingView for CortexSettingsView {
#[allow(dead_code)]
pub fn cortex_settings_search_terms() -> String {
format!(
"cortex settings {} {}",
"cortex settings {} {} {}",
working_panes_page_search_terms().join(" "),
tabs_page_search_terms().join(" ")
tabs_page_search_terms().join(" "),
ai_page_search_terms().join(" ")
)
}
9 changes: 9 additions & 0 deletions app/src/settings/cortex.rs
Original file line number Diff line number Diff line change
Expand Up @@ -262,5 +262,14 @@ define_settings_group!(CortexSettings, settings: [
private: false,
toml_path: "cortex.terminal.cli_agent_clear_scrolls_to_top",
description: "When a running CLI agent (Claude Code, Codex, Cursor, Gemini) runs /clear, scroll the viewport so the agent's freshly-cleared UI sits at the top of the visible pane and the prior conversation remains in scrollback. Claude is wired via Cortex's OSC-777 → SessionEnd hook; other agents are detected when they emit ESC[2J. Off restores upstream Warp behavior.",
},
allow_local_claude_codex_child_harnesses: AllowLocalClaudeCodexChildHarnesses {
type: bool,
default: true,
supported_platforms: SupportedPlatforms::ALL,
sync_to_cloud: SyncToCloud::Globally(RespectUserSyncSetting::Yes),
private: false,
toml_path: "cortex.ai.allow_local_claude_codex_child_harnesses",
description: "Whether /orchestrate's Local execution mode may spawn child agents using the Claude Code or Codex CLI harnesses instead of being limited to Oz. Upstream Warp keeps this gated behind FeatureFlag::LocalClaudeCodexChildHarnesses; Cortex hydrates that flag from this setting at startup and on each toggle, so checks at the existing call sites (local_child_harnesses.rs, orchestration_controls.rs) react without a restart. Default on — the whole point of the Cortex fork on this branch is to route /orchestrate children through your local Claude Code login.",
}
]);
14 changes: 14 additions & 0 deletions app/src/settings/init.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,20 @@ pub fn register_all_settings(ctx: &mut AppContext) {
TerminalSettings::register(ctx);
PaneSettings::register(ctx);
CortexSettings::register(ctx);
// Cortex divergence — hydrate `FeatureFlag::LocalClaudeCodexChildHarnesses`
// from the persisted `CortexSettings.allow_local_claude_codex_child_harnesses`
// toggle so the gate sites in `local_child_harnesses.rs` and
// `orchestration_controls.rs` reflect the user's last choice from frame
// zero (without needing a runtime-feature-flags debug menu, and without
// forcing every gate site to be ported to read the setting directly).
// The Cortex Settings → AI page mirrors any subsequent toggle back into
// the flag, so this read is only needed for the cold-start path.
{
let allow_local_claude_codex_child_harnesses =
*CortexSettings::as_ref(ctx).allow_local_claude_codex_child_harnesses;
FeatureFlag::LocalClaudeCodexChildHarnesses
.set_user_preference(allow_local_claude_codex_child_harnesses);
}
// Cortex divergence — surface a dev-build warning when the CLI-agent
// `/clear` viewport pin is disabled in dev's settings.toml. This is
// typically a debug-iteration leftover that silently disables the pin
Expand Down