v1.4.0: Auto-compact streaming, Bedrock summarization support, subagent orchestration, and Galaxy rebrand continuation

Major features:
- Auto-compact: triggers conversation summarization when context window >= 85%,
  compacts Bedrock message history to a summary pair, and tracks live context tokens
- Bedrock summarization: plumbs `is_summarization` flag through translator/client/response
  pipeline, handles SummarizeConversation input type, and marks `summarized` in metadata
- Session restore: rebuilds bedrock_message_history from persisted task messages via
  newly-public `convert_proto_message`, preventing empty history on reconnect
- Subagent orchestration: adds SubagentQuestion/Answer/CompletionSummary event types,
  parent-child question routing with depth limits, retry counting, and drain methods
- Summarization UI: inline SummarizationView in AI blocks with progress/finished states

Refactors:
- Rename WarpTheme → GalaxyTheme across ~100 files (rebrand continuation)
- Rename warp_home_config_dir → galaxy_home_config_dir and related path functions
- Predefined rules: replace "System Defined Rule #N" with descriptive names
  (e.g. "Correctness Over Speed", "Never Guess") and add lookup helpers
- Usage view: replace cumulative input/output token display with live context tokens,
  cache hit rate calculation, and separate cache read/write stats
- Telemetry: remove verbose doc comments, simplify trait definitions
- Facts view: simplify delete permission check (always allow local deletion)
- Remove warp_managed_paths_watcher.rs (dead code)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Ryan Ward
2026-05-21 11:59:37 -05:00
co-authored by Claude Opus 4.6
parent eaa2ddc75e
commit 6f54e2cb30
229 changed files with 2506 additions and 2634 deletions
@@ -4,7 +4,7 @@ use ai::agent::{
action::{AskUserQuestionItem, AskUserQuestionOption, AskUserQuestionType},
action_result::{AskUserQuestionAnswerItem, AskUserQuestionResult},
};
use galaxy_core::ui::theme::{color::internal_colors, WarpTheme};
use galaxy_core::ui::theme::{color::internal_colors, GalaxyTheme};
use galaxyui::{
elements::{
new_scrollable::SingleAxisConfig, Border, ChildView, Clipped, ClippedScrollStateHandle,
@@ -1333,7 +1333,7 @@ impl AskUserQuestionView {
fn render_question_text(
question_text: &str,
appearance: &Appearance,
theme: &WarpTheme,
theme: &GalaxyTheme,
) -> Box<dyn Element> {
let text_color = theme.foreground().into();
Container::new(render_text_with_markdown_support(
@@ -1357,7 +1357,7 @@ impl AskUserQuestionView {
&self,
question_text: &str,
appearance: &Appearance,
theme: &WarpTheme,
theme: &GalaxyTheme,
) -> Box<dyn Element> {
let body = Flex::column()
.with_cross_axis_alignment(CrossAxisAlignment::Stretch)
@@ -1385,7 +1385,7 @@ impl AskUserQuestionView {
fn render_nav_footer(
&self,
appearance: &Appearance,
theme: &WarpTheme,
theme: &GalaxyTheme,
app: &AppContext,
) -> Box<dyn Element> {
let counter = format!(
@@ -11,6 +11,7 @@ pub(crate) mod requested_command_attribution;
pub(crate) mod requested_script;
pub(super) mod search_codebase;
pub(crate) mod search_results_common;
pub(super) mod summarization;
pub(crate) mod suggested_unit_tests;
pub(super) mod web_fetch;
pub(super) mod web_search;
@@ -0,0 +1,197 @@
use galaxy_core::ui::appearance::Appearance;
use galaxyui::elements::shimmering_text::{ShimmerConfig, ShimmeringTextElement, ShimmeringTextStateHandle};
use galaxyui::elements::{
ConstrainedBox, Container, CornerRadius, CrossAxisAlignment, Element, Flex,
MainAxisAlignment, ParentElement, Radius, Shrinkable, Text,
};
use galaxyui::r#async::{SpawnedFutureHandle, Timer};
use galaxyui::{AppContext, Entity, SingletonEntity, View, ViewContext};
use instant::Instant;
use std::time::Duration;
use super::inline_action_header::{
INLINE_ACTION_HEADER_VERTICAL_PADDING, INLINE_ACTION_HORIZONTAL_PADDING,
};
use super::inline_action_icons::icon_size;
use crate::ai::blocklist::block::view_impl::WithContentItemSpacing;
use crate::ui_components::icons::Icon;
pub enum SummarizationViewEvent {}
pub struct SummarizationView {
pub is_finished: bool,
shimmering_text_handle: ShimmeringTextStateHandle,
start_time: Instant,
timer_handle: Option<SpawnedFutureHandle>,
}
impl SummarizationView {
pub fn new(ctx: &mut ViewContext<Self>) -> Self {
let mut view = Self {
is_finished: false,
shimmering_text_handle: ShimmeringTextStateHandle::default(),
start_time: Instant::now(),
timer_handle: None,
};
view.start_timer(ctx);
view
}
pub fn mark_finished(&mut self) {
self.is_finished = true;
if let Some(handle) = self.timer_handle.take() {
handle.abort();
}
}
fn start_timer(&mut self, ctx: &mut ViewContext<Self>) {
if self.timer_handle.is_some() {
return;
}
let handle = ctx.spawn(
async move {
Timer::after(Duration::from_secs(1)).await;
},
|me, _unit, ctx| {
me.timer_handle = None;
if !me.is_finished {
ctx.notify();
me.start_timer(ctx);
}
},
);
self.timer_handle = Some(handle);
}
fn render_in_progress(&self, app: &AppContext) -> Box<dyn Element> {
let appearance = Appearance::as_ref(app);
let theme = appearance.theme();
let header_background = theme.surface_2();
let mut header_row = Flex::row()
.with_main_axis_alignment(MainAxisAlignment::Start)
.with_cross_axis_alignment(CrossAxisAlignment::Center);
// Clock loader icon (magenta, matches InProgress convention)
let icon_element = galaxyui::elements::Icon::new(
Icon::ClockLoader.into(),
theme.ansi_fg_magenta(),
)
.finish();
let icon_box = ConstrainedBox::new(icon_element)
.with_width(icon_size(app))
.with_height(icon_size(app))
.finish();
header_row.add_child(
Container::new(icon_box)
.with_margin_right(8.)
.finish(),
);
// Shimmering "Summarizing conversation..." text
let base_color = theme.disabled_text_color(header_background).into_solid();
let shimmer_color = theme.main_text_color(header_background).into_solid();
let shimmer_element = ShimmeringTextElement::new(
"Summarizing conversation...".to_string(),
appearance.ui_font_family(),
appearance.monospace_font_size(),
base_color,
shimmer_color,
ShimmerConfig::default(),
self.shimmering_text_handle.clone(),
)
.finish();
header_row.add_child(Shrinkable::new(1.0, shimmer_element).finish());
// Elapsed time suffix
let elapsed = self.start_time.elapsed();
let elapsed_text = format_elapsed(elapsed);
let suffix = Text::new_inline(
format!(" \u{2022} {elapsed_text}"),
appearance.ui_font_family(),
appearance.monospace_font_size(),
)
.with_color(theme.disabled_text_color(header_background).into())
.finish();
header_row.add_child(suffix);
Container::new(header_row.finish())
.with_horizontal_padding(INLINE_ACTION_HORIZONTAL_PADDING)
.with_vertical_padding(INLINE_ACTION_HEADER_VERTICAL_PADDING)
.with_background(header_background)
.with_corner_radius(CornerRadius::with_all(Radius::Pixels(8.)))
.finish()
}
fn render_finished(&self, app: &AppContext) -> Box<dyn Element> {
let appearance = Appearance::as_ref(app);
let theme = appearance.theme();
let header_background = theme.surface_2();
let mut header_row = Flex::row()
.with_main_axis_alignment(MainAxisAlignment::Start)
.with_cross_axis_alignment(CrossAxisAlignment::Center);
// Checkmark-style icon for completed
let icon_element = galaxyui::elements::Icon::new(
Icon::Check.into(),
theme.ansi_fg_green(),
)
.finish();
let icon_box = ConstrainedBox::new(icon_element)
.with_width(icon_size(app))
.with_height(icon_size(app))
.finish();
header_row.add_child(
Container::new(icon_box)
.with_margin_right(8.)
.finish(),
);
let elapsed = self.start_time.elapsed();
let elapsed_text = format_elapsed(elapsed);
let title = Text::new_inline(
format!("Conversation summarized \u{2022} {elapsed_text}"),
appearance.ui_font_family(),
appearance.monospace_font_size(),
)
.with_color(theme.main_text_color(header_background).into())
.finish();
header_row.add_child(Shrinkable::new(1.0, title).finish());
Container::new(header_row.finish())
.with_horizontal_padding(INLINE_ACTION_HORIZONTAL_PADDING)
.with_vertical_padding(INLINE_ACTION_HEADER_VERTICAL_PADDING)
.with_background(header_background)
.with_corner_radius(CornerRadius::with_all(Radius::Pixels(8.)))
.finish()
}
}
impl Entity for SummarizationView {
type Event = SummarizationViewEvent;
}
impl View for SummarizationView {
fn ui_name() -> &'static str {
"SummarizationView"
}
fn render(&self, app: &AppContext) -> Box<dyn Element> {
let element = if self.is_finished {
self.render_finished(app)
} else {
self.render_in_progress(app)
};
element.with_agent_output_item_spacing(app).finish()
}
}
fn format_elapsed(duration: Duration) -> String {
let secs = duration.as_secs();
if secs < 60 {
format!("{secs}s")
} else {
format!("{}m {}s", secs / 60, secs % 60)
}
}