Skip to content

Commit ed03625

Browse files
authored
Merge pull request #1 from lawsmd/claude/cortex-orchestrate-claude-api-kjqkm
feat(cortex-settings): AI section with /orchestrate Claude/Codex toggle
2 parents 066dfc4 + 551b60e commit ed03625

6 files changed

Lines changed: 179 additions & 3 deletions

File tree

app/src/cortex_settings/action.rs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,18 +13,20 @@ use crate::settings::{
1313
pub enum CortexSettingsSection {
1414
WorkingPanes,
1515
Tabs,
16+
Ai,
1617
}
1718

1819
impl CortexSettingsSection {
1920
pub fn label(self) -> &'static str {
2021
match self {
2122
Self::WorkingPanes => "Working Panes",
2223
Self::Tabs => "Tabs",
24+
Self::Ai => "AI",
2325
}
2426
}
2527

2628
pub fn all() -> &'static [Self] {
27-
&[Self::WorkingPanes, Self::Tabs]
29+
&[Self::WorkingPanes, Self::Tabs, Self::Ai]
2830
}
2931
}
3032

@@ -72,4 +74,11 @@ pub enum CortexSettingsAction {
7274
SetTabTitleFontWeight(Weight),
7375
/// Flip the italic toggle for tab titles.
7476
ToggleTabTitleItalic,
77+
/// Flip the "Allow Claude Code / Codex as orchestrate child agents" toggle
78+
/// on the AI page. Mirrors the persisted [`crate::settings::CortexSettings`]
79+
/// bool *and* pushes the same value into
80+
/// `FeatureFlag::LocalClaudeCodexChildHarnesses` via `set_user_preference`,
81+
/// so the orchestration controls (`local_child_harnesses.rs`,
82+
/// `orchestration_controls.rs`) react without a restart.
83+
ToggleAllowLocalClaudeCodexChildHarnesses,
7584
}

app/src/cortex_settings/ai_page.rs

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
//! Content rendered on the right side of the Cortex Settings pane when the
2+
//! "AI" section is selected.
3+
//!
4+
//! Currently houses a single toggle: `Allow Claude Code / Codex as
5+
//! orchestrate child agents`. The toggle is the user-facing surface for
6+
//! [`warp_core::features::FeatureFlag::LocalClaudeCodexChildHarnesses`]; the
7+
//! flag stays the single source of truth read by
8+
//! `app/src/ai/local_child_harnesses.rs` and
9+
//! `app/src/ai/blocklist/inline_action/orchestration_controls.rs`, and the
10+
//! setting just hydrates that flag at startup and on each user flip.
11+
use warpui::{
12+
elements::{
13+
Align, Container, CrossAxisAlignment, Element, Flex, Padding, ParentElement, Shrinkable,
14+
},
15+
ui_components::{components::UiComponent, switch::SwitchStateHandle},
16+
AppContext, SingletonEntity,
17+
};
18+
19+
use crate::appearance::Appearance;
20+
use crate::cortex_settings::action::CortexSettingsAction;
21+
use crate::settings::CortexSettings;
22+
23+
const ROW_VERTICAL_PADDING: f32 = 6.0;
24+
const CONTROL_RIGHT_PADDING: f32 = 5.0;
25+
26+
/// Per-toggle UI state that has to outlive a single render frame (switch
27+
/// animation state). Owned by `CortexSettingsView`.
28+
#[derive(Default)]
29+
pub struct AiPageState {
30+
allow_local_claude_codex_child_harnesses_switch: SwitchStateHandle,
31+
}
32+
33+
pub fn ai_page_search_terms() -> &'static [&'static str] {
34+
&[
35+
"ai",
36+
"agent",
37+
"orchestrate",
38+
"orchestration",
39+
"claude",
40+
"claude code",
41+
"codex",
42+
"child",
43+
"harness",
44+
"subagent",
45+
"sub-agent",
46+
]
47+
}
48+
49+
pub fn render_ai_page(
50+
state: &AiPageState,
51+
appearance: &Appearance,
52+
app: &AppContext,
53+
) -> Box<dyn Element> {
54+
Flex::column()
55+
.with_cross_axis_alignment(CrossAxisAlignment::Stretch)
56+
.with_child(render_allow_local_claude_codex_child_harnesses_row(
57+
state, appearance, app,
58+
))
59+
.finish()
60+
}
61+
62+
fn render_allow_local_claude_codex_child_harnesses_row(
63+
state: &AiPageState,
64+
appearance: &Appearance,
65+
app: &AppContext,
66+
) -> Box<dyn Element> {
67+
let ui_builder = appearance.ui_builder();
68+
let current_value = *CortexSettings::as_ref(app).allow_local_claude_codex_child_harnesses;
69+
70+
let label = ui_builder
71+
.span("Allow Claude Code / Codex as orchestrate child agents".to_string())
72+
.build()
73+
.finish();
74+
75+
let switch = ui_builder
76+
.switch(
77+
state
78+
.allow_local_claude_codex_child_harnesses_switch
79+
.clone(),
80+
)
81+
.check(current_value)
82+
.build()
83+
.on_click(move |ctx, _, _| {
84+
ctx.dispatch_typed_action(
85+
CortexSettingsAction::ToggleAllowLocalClaudeCodexChildHarnesses,
86+
);
87+
})
88+
.finish();
89+
90+
let header = Shrinkable::new(
91+
1.0,
92+
Container::new(Align::new(label).left().finish()).finish(),
93+
)
94+
.finish();
95+
96+
let control = Container::new(switch)
97+
.with_padding_right(CONTROL_RIGHT_PADDING)
98+
.finish();
99+
100+
let row = Flex::row()
101+
.with_cross_axis_alignment(CrossAxisAlignment::Center)
102+
.with_child(header)
103+
.with_child(control)
104+
.finish();
105+
106+
Container::new(row)
107+
.with_padding(Padding::uniform(ROW_VERTICAL_PADDING))
108+
.finish()
109+
}

app/src/cortex_settings/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
//! `app/src/pane_group/pane/cortex_settings_pane.rs` because it needs access
2020
//! to `pub(super)` items in the pane-group module hierarchy.
2121
pub mod action;
22+
pub mod ai_page;
2223
pub mod brand;
2324
pub mod pane_manager;
2425
pub mod tabs_page;

app/src/cortex_settings/view.rs

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ use warpui::{
2424

2525
use crate::appearance::Appearance;
2626
use crate::cortex_settings::action::{CortexSettingsAction, CortexSettingsSection};
27+
use crate::cortex_settings::ai_page::{ai_page_search_terms, render_ai_page, AiPageState};
2728
use crate::cortex_settings::brand::{
2829
BRAND_HEADER_ICON_TO_TITLE_FONT_RATIO, BRAND_HEADER_TITLE_TO_FONT_RATIO,
2930
BRAND_MENU_ICON_LABEL_GAP_RATIO,
@@ -81,6 +82,7 @@ pub struct CortexSettingsView {
8182
sidebar_states: Vec<(CortexSettingsSection, MouseStateHandle)>,
8283
working_panes_state: WorkingPanesPageState,
8384
tabs_state: TabsPageState,
85+
ai_state: AiPageState,
8486
search_editor: ViewHandle<EditorView>,
8587
}
8688

@@ -119,6 +121,7 @@ impl CortexSettingsView {
119121
sidebar_states,
120122
working_panes_state: WorkingPanesPageState::default(),
121123
tabs_state: TabsPageState::new(ctx),
124+
ai_state: AiPageState::default(),
122125
search_editor,
123126
}
124127
}
@@ -321,6 +324,32 @@ impl CortexSettingsView {
321324
ctx.notify();
322325
}
323326

327+
fn toggle_allow_local_claude_codex_child_harnesses(&mut self, ctx: &mut ViewContext<Self>) {
328+
use crate::settings::CortexSettings;
329+
use settings::ToggleableSetting;
330+
use warp_core::features::FeatureFlag;
331+
use warpui::SingletonEntity;
332+
333+
// Read pre-toggle value so we can compute the post-toggle value and push
334+
// it into the feature flag without re-reading after the closure (which
335+
// would race with rendering threads that have already snapshotted).
336+
let previous_value = *CortexSettings::as_ref(ctx).allow_local_claude_codex_child_harnesses;
337+
338+
CortexSettings::handle(ctx).update(ctx, |settings, ctx| {
339+
let _ = settings
340+
.allow_local_claude_codex_child_harnesses
341+
.toggle_and_save_value(ctx);
342+
});
343+
344+
// Mirror the setting into the runtime feature flag so the existing
345+
// gate sites (`local_child_harnesses.rs`, `orchestration_controls.rs`)
346+
// pick up the change on the next `is_enabled()` call without needing
347+
// to be ported to read the setting directly.
348+
FeatureFlag::LocalClaudeCodexChildHarnesses.set_user_preference(!previous_value);
349+
350+
ctx.notify();
351+
}
352+
324353
fn handle_search_editor_event(
325354
&mut self,
326355
_editor: ViewHandle<EditorView>,
@@ -442,6 +471,7 @@ impl CortexSettingsView {
442471
render_working_panes_page(&self.working_panes_state, appearance, app)
443472
}
444473
CortexSettingsSection::Tabs => render_tabs_page(&self.tabs_state, appearance, app),
474+
CortexSettingsSection::Ai => render_ai_page(&self.ai_state, appearance, app),
445475
}
446476
}
447477
}
@@ -523,6 +553,9 @@ impl warpui::TypedActionView for CortexSettingsView {
523553
self.set_tab_title_font_weight(*value, ctx)
524554
}
525555
CortexSettingsAction::ToggleTabTitleItalic => self.toggle_tab_title_italic(ctx),
556+
CortexSettingsAction::ToggleAllowLocalClaudeCodexChildHarnesses => {
557+
self.toggle_allow_local_claude_codex_child_harnesses(ctx)
558+
}
526559
}
527560
}
528561
}
@@ -628,8 +661,9 @@ impl BackingView for CortexSettingsView {
628661
#[allow(dead_code)]
629662
pub fn cortex_settings_search_terms() -> String {
630663
format!(
631-
"cortex settings {} {}",
664+
"cortex settings {} {} {}",
632665
working_panes_page_search_terms().join(" "),
633-
tabs_page_search_terms().join(" ")
666+
tabs_page_search_terms().join(" "),
667+
ai_page_search_terms().join(" ")
634668
)
635669
}

app/src/settings/cortex.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,5 +262,14 @@ define_settings_group!(CortexSettings, settings: [
262262
private: false,
263263
toml_path: "cortex.terminal.cli_agent_clear_scrolls_to_top",
264264
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.",
265+
},
266+
allow_local_claude_codex_child_harnesses: AllowLocalClaudeCodexChildHarnesses {
267+
type: bool,
268+
default: true,
269+
supported_platforms: SupportedPlatforms::ALL,
270+
sync_to_cloud: SyncToCloud::Globally(RespectUserSyncSetting::Yes),
271+
private: false,
272+
toml_path: "cortex.ai.allow_local_claude_codex_child_harnesses",
273+
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.",
265274
}
266275
]);

app/src/settings/init.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,20 @@ pub fn register_all_settings(ctx: &mut AppContext) {
6767
TerminalSettings::register(ctx);
6868
PaneSettings::register(ctx);
6969
CortexSettings::register(ctx);
70+
// Cortex divergence — hydrate `FeatureFlag::LocalClaudeCodexChildHarnesses`
71+
// from the persisted `CortexSettings.allow_local_claude_codex_child_harnesses`
72+
// toggle so the gate sites in `local_child_harnesses.rs` and
73+
// `orchestration_controls.rs` reflect the user's last choice from frame
74+
// zero (without needing a runtime-feature-flags debug menu, and without
75+
// forcing every gate site to be ported to read the setting directly).
76+
// The Cortex Settings → AI page mirrors any subsequent toggle back into
77+
// the flag, so this read is only needed for the cold-start path.
78+
{
79+
let allow_local_claude_codex_child_harnesses =
80+
*CortexSettings::as_ref(ctx).allow_local_claude_codex_child_harnesses;
81+
FeatureFlag::LocalClaudeCodexChildHarnesses
82+
.set_user_preference(allow_local_claude_codex_child_harnesses);
83+
}
7084
// Cortex divergence — surface a dev-build warning when the CLI-agent
7185
// `/clear` viewport pin is disabled in dev's settings.toml. This is
7286
// typically a debug-iteration leftover that silently disables the pin

0 commit comments

Comments
 (0)