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
+53 -50
View File
@@ -13,7 +13,7 @@ cfg_if::cfg_if! {
use ignore::gitignore::Gitignore;
use async_channel::Sender;
const RULES_FILE_PATTERN: [&str; 2] = ["WARP.md", "AGENTS.md"];
const RULES_FILE_PATTERN: [&str; 4] = ["GALAXY.md", "WARP.md", "CLAUDE.md", "AGENTS.md"];
const MAX_SCAN_DEPTH: usize = 3;
const MAX_FILES_TO_SCAN: usize = 5000;
}
@@ -28,13 +28,23 @@ pub struct ProjectRule {
#[derive(Debug, Default)]
struct RuleAtPath {
parent_path: PathBuf,
galaxy_md: Option<ProjectRule>,
warp_md: Option<ProjectRule>,
claude_md: Option<ProjectRule>,
agents_md: Option<ProjectRule>,
}
impl RuleAtPath {
fn respected_rule(&self) -> Option<&ProjectRule> {
self.warp_md.as_ref().or(self.agents_md.as_ref())
fn all_rules(&self) -> Vec<&ProjectRule> {
[
self.galaxy_md.as_ref(),
self.warp_md.as_ref(),
self.claude_md.as_ref(),
self.agents_md.as_ref(),
]
.into_iter()
.flatten()
.collect()
}
}
@@ -82,12 +92,14 @@ impl ProjectRules {
// Collect all applicable rules (rules in directories that are ancestors of the target path)
for rule in &self.rules {
if let Some(respected_rule) = rule.respected_rule() {
// Check if the rule's directory is an ancestor of or equal to the target path
if path.starts_with(&rule.parent_path) {
active_rules.push(respected_rule.clone());
} else {
available_rule_paths.push(respected_rule.path.to_string_lossy().to_string());
if path.starts_with(&rule.parent_path) {
for project_rule in rule.all_rules() {
active_rules.push(project_rule.clone());
}
} else {
for project_rule in rule.all_rules() {
available_rule_paths
.push(project_rule.path.to_string_lossy().to_string());
}
}
}
@@ -109,16 +121,16 @@ impl ProjectRules {
.iter_mut()
.find(|rule| rule.parent_path == parent)?;
if file_name.to_lowercase() == "warp.md" {
rule.warp_md.take()
} else if file_name.to_lowercase() == "agents.md" {
rule.agents_md.take()
} else {
None
match file_name.to_lowercase().as_str() {
"galaxy.md" => rule.galaxy_md.take(),
"warp.md" => rule.warp_md.take(),
"claude.md" => rule.claude_md.take(),
"agents.md" => rule.agents_md.take(),
_ => None,
}
}
/// Upsert a rule to the set of project rules. This will create a new RuleAtPath entry if none exists and update the existin one
/// Upsert a rule to the set of project rules. This will create a new RuleAtPath entry if none exists and update the existing one
/// otherwise.
#[cfg_attr(not(feature = "local_fs"), allow(dead_code))]
fn upsert_rule(&mut self, path: &Path, content: String) {
@@ -139,32 +151,29 @@ impl ProjectRules {
content,
});
match existing_rule {
Some(rule) => {
if file_name.to_lowercase() == "warp.md" {
rule.warp_md = rule_file;
} else if file_name.to_lowercase() == "agents.md" {
rule.agents_md = rule_file;
}
}
let rule_ref = match existing_rule {
Some(rule) => rule,
None => {
let mut rule = RuleAtPath {
self.rules.push(RuleAtPath {
parent_path: parent.to_path_buf(),
..Default::default()
};
if file_name.to_lowercase() == "warp.md" {
rule.warp_md = rule_file;
} else if file_name.to_lowercase() == "agents.md" {
rule.agents_md = rule_file;
}
self.rules.push(rule);
});
self.rules.last_mut().unwrap()
}
};
match file_name.to_lowercase().as_str() {
"galaxy.md" => rule_ref.galaxy_md = rule_file,
"warp.md" => rule_ref.warp_md = rule_file,
"claude.md" => rule_ref.claude_md = rule_file,
"agents.md" => rule_ref.agents_md = rule_file,
_ => {}
}
}
}
/// Singleton model that keeps track of mapping between paths and rule files
/// Currently supports WARP.md files, but designed to be extensible
/// Supports GALAXY.md, WARP.md, CLAUDE.md, and AGENTS.md project rule files
#[cfg_attr(not(feature = "local_fs"), allow(dead_code))]
#[derive(Debug, Default)]
pub struct ProjectContextModel {
@@ -237,18 +246,12 @@ impl ProjectContextModel {
discovered_rules: rule_files
.rules
.iter()
.filter_map(|rule| {
rule.warp_md.as_ref().map(|rule| ProjectRulePath {
.flat_map(|rule| {
rule.all_rules().into_iter().map(|r| ProjectRulePath {
project_root: root_clone.clone(),
path: rule.path.clone(),
path: r.path.clone(),
})
})
.chain(rule_files.rules.iter().filter_map(|rule| {
rule.agents_md.as_ref().map(|rule| ProjectRulePath {
project_root: root_clone.clone(),
path: rule.path.clone(),
})
}))
.collect(),
deleted_rules: Default::default(),
};
@@ -489,7 +492,7 @@ impl ProjectContextModel {
(existing_rules, rules_delta)
}
/// Scan a directory for rule files (currently WARP.md, extensible for future file types)
/// Scan a directory for rule files (GALAXY.md, WARP.md, CLAUDE.md, AGENTS.md)
/// Uses repo_metadata::entry::build_tree for efficient directory traversal
#[cfg(feature = "local_fs")]
async fn scan_directory_for_rules(dir_path: &Path) -> Result<ProjectRules> {
@@ -576,11 +579,10 @@ impl ProjectContextModel {
pub fn indexed_rules(&self) -> impl Iterator<Item = PathBuf> + '_ {
self.path_to_rules.values().flat_map(|rules| {
rules.rules.iter().filter_map(|rules| {
rules
.respected_rule()
.map(|project_rule| project_rule.path.clone())
})
rules
.rules
.iter()
.flat_map(|rule| rule.all_rules().into_iter().map(|r| r.path.clone()))
})
}
@@ -590,8 +592,9 @@ impl ProjectContextModel {
.get(workspace_path)
.into_iter()
.flat_map(|rules| {
rules.rules.iter().filter_map(|rule| {
rule.respected_rule()
rules.rules.iter().flat_map(|rule| {
rule.all_rules()
.into_iter()
.map(|project_rule| project_rule.path.clone())
})
})
+7 -7
View File
@@ -121,12 +121,12 @@ fn test_find_applicable_rules_handles_root_path() {
#[test]
fn test_find_applicable_rules_complex_scenario() {
// This test covers the example from the original request:
// For path /a/b/c/file.rs with rules:
// - /a/WARP.md
// - /a/AGENTS.md
// - /a/b/WARP.md
// - /a/b/AGENTS.md
// All ancestor rule files should be included.
let mut rules = ProjectRules::default();
rules.upsert_rule(Path::new("/a/WARP.md"), "a_warp".to_string());
@@ -138,13 +138,13 @@ fn test_find_applicable_rules_complex_scenario() {
let path = PathBuf::from("/a/b/c/file.rs");
let result = rules.find_active_or_applicable_rules(&path).active_rules;
assert_eq!(result.len(), 2);
assert_eq!(result.len(), 4);
// Expect only WARP.md files to be included as they have higher priority.
assert_eq!(result[0].path, PathBuf::from("/a/WARP.md"));
assert_eq!(result[0].content, "a_warp");
assert_eq!(result[1].path, PathBuf::from("/a/b/WARP.md"));
assert_eq!(result[1].content, "ab_warp");
let paths: Vec<PathBuf> = result.iter().map(|r| r.path.clone()).collect();
assert!(paths.contains(&PathBuf::from("/a/WARP.md")));
assert!(paths.contains(&PathBuf::from("/a/AGENTS.md")));
assert!(paths.contains(&PathBuf::from("/a/b/WARP.md")));
assert!(paths.contains(&PathBuf::from("/a/b/AGENTS.md")));
}
#[test]
+4 -4
View File
@@ -159,7 +159,7 @@ pub fn provider_rank(provider: SkillProvider) -> usize {
pub fn home_skills_path(provider: SkillProvider) -> Option<PathBuf> {
if provider == SkillProvider::Warp {
return galaxy_core::paths::warp_home_skills_dir();
return galaxy_core::paths::galaxy_home_skills_dir();
}
let definition = SKILL_PROVIDER_DEFINITIONS
.iter()
@@ -220,17 +220,17 @@ mod tests {
fn warp_home_skills_path_uses_warp_home_path() {
assert_eq!(
home_skills_path(SkillProvider::Warp),
galaxy_core::paths::warp_home_skills_dir()
galaxy_core::paths::galaxy_home_skills_dir()
);
}
#[test]
fn warp_home_skill_path_is_home_warp_skill() {
let Some(warp_home_skills_dir) = galaxy_core::paths::warp_home_skills_dir() else {
let Some(galaxy_home_skills_dir) = galaxy_core::paths::galaxy_home_skills_dir() else {
eprintln!("Skipping test: home directory not available");
return;
};
let path = warp_home_skills_dir.join("my-skill").join("SKILL.md");
let path = galaxy_home_skills_dir.join("my-skill").join("SKILL.md");
assert_eq!(get_provider_for_path(&path), Some(SkillProvider::Warp));
assert_eq!(get_scope_for_path(&path), SkillScope::Home);