v1.3.0: Bedrock translator refactor, usage metrics, session restore fixes, and predefined rules
Major changes: - **Bedrock translator architecture**: Extract orchestration logic from `impl.rs` into a dedicated `translator.rs` module. Rename `convert_request.rs` → `request_translator.rs` and `stream.rs` → `response_translator.rs` for clarity. Remove `tool_docs.rs` (inlined). Remove `fallback_to_warp` setting and server fallback path — Bedrock is now the sole backend. - **Unknown tool handling**: The response translator now detects hallucinated/unknown tool calls from the model and synthesizes error tool_results so the conversation doesn't deadlock waiting for a result that will never come. - **Usage display overhaul**: Replace credit-based usage display with detailed token metrics showing context window %, cache hit rate (read/write/miss), and estimated cost in dollars. Add `total_input_tokens`, `total_cache_read_tokens`, `total_cache_write_tokens`, and `cache_miss_tokens` accessors to `AIConversation`. - **Predefined rules system**: Add `predefined_rules.rs` with 11 system-defined behavioral rules that are auto-seeded on first launch. Add "Add Predefined Rules" button to the Rules UI for re-adding them later. Track seeding state via `has_seeded_predefined_rules` setting. - **Session restore improvements**: Rename database file from `warp.sqlite` to `galaxy.sqlite` with automatic migration from both same-directory and state_dir legacy paths. Improve CWD persistence by falling back to `session_startup_path` for agent-mode and fresh tabs. Add extensive session-save/restore logging. - **Shell bootstrap rebrand**: Rename `WARP_INITIAL_WORKING_DIR` environment variable to `GALAXY_INITIAL_WORKING_DIR` across bash, zsh, and fish bootstrap scripts. - **Model defaults**: Change default Bedrock model from Opus 4.7 to Opus 4.6. Add `context_window_for_model()` helper with model-aware context sizes. Remove `is_bedrock_model()` (no longer needed without server fallback). - **User query persistence**: The response translator now emits a `UserQuery` proto message at stream start so the user's prompt persists across sessions for conversation titles. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
ec99146ccc
commit
eaa2ddc75e
@@ -16,6 +16,7 @@ use galaxy_core::ui::appearance::Appearance;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub mod manager;
|
||||
pub mod predefined_rules;
|
||||
pub mod view;
|
||||
pub use manager::AIFactManager;
|
||||
pub use view::{AIFactView, AIFactViewEvent};
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
pub struct PredefinedRule {
|
||||
pub name: &'static str,
|
||||
pub content: &'static str,
|
||||
}
|
||||
|
||||
pub const SYSTEM_DEFINED_RULE_PREFIX: &str = "System Defined Rule";
|
||||
|
||||
pub const PREDEFINED_RULES: &[PredefinedRule] = &[
|
||||
PredefinedRule {
|
||||
name: "System Defined Rule #1",
|
||||
content: "Prioritize correctness, completeness, and reliability over speed.",
|
||||
},
|
||||
PredefinedRule {
|
||||
name: "System Defined Rule #2",
|
||||
content: "Never guess. If uncertain, explicitly say so and verify before finalizing.",
|
||||
},
|
||||
PredefinedRule {
|
||||
name: "System Defined Rule #3",
|
||||
content: "Ground non-trivial claims in evidence (repo files, command output, tests, official documentation).",
|
||||
},
|
||||
PredefinedRule {
|
||||
name: "System Defined Rule #4",
|
||||
content: "If confidence is not high, or if a claim depends on external/current behavior, perform web verification before answering; prioritize official docs and cross-check with at least one additional reliable source.",
|
||||
},
|
||||
PredefinedRule {
|
||||
name: "System Defined Rule #5",
|
||||
content: "Clearly separate facts, assumptions, and hypotheses.",
|
||||
},
|
||||
PredefinedRule {
|
||||
name: "System Defined Rule #6",
|
||||
content: "Ask clarifying questions when ambiguity could change the solution or implementation.",
|
||||
},
|
||||
PredefinedRule {
|
||||
name: "System Defined Rule #7",
|
||||
content: "For code changes, run relevant validations when available (tests, lint, typecheck, build) and report what was run, what passed/failed, and what was not run.",
|
||||
},
|
||||
PredefinedRule {
|
||||
name: "System Defined Rule #8",
|
||||
content: "If validation cannot be run, state that explicitly and describe residual risk and recommended manual checks.",
|
||||
},
|
||||
PredefinedRule {
|
||||
name: "System Defined Rule #9",
|
||||
content: "Prefer \"I don't know yet\" over plausible speculation.",
|
||||
},
|
||||
PredefinedRule {
|
||||
name: "System Defined Rule #10",
|
||||
content: "If the user's idea is wrong, incomplete, risky, or non-optimal, say so directly and respectfully; explain why it may fail and provide a better alternative that still achieves the user's goal.",
|
||||
},
|
||||
PredefinedRule {
|
||||
name: "System Defined Rule #11",
|
||||
content: "Do not hide uncertainty, and do not avoid technical disagreement when correctness is at stake.",
|
||||
},
|
||||
];
|
||||
@@ -47,6 +47,7 @@ use std::fmt::Debug;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use super::{is_edit_allowed, is_syncing, style, AIFact, CloudAIFact, CloudAIFactModel};
|
||||
use crate::ai::facts::predefined_rules::{PREDEFINED_RULES, SYSTEM_DEFINED_RULE_PREFIX};
|
||||
use crate::ai::facts::AIMemory;
|
||||
|
||||
pub const HEADER_TEXT: &str = "Rules";
|
||||
@@ -80,6 +81,7 @@ pub enum RuleViewEvent {
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum RuleViewAction {
|
||||
AddRule,
|
||||
AddPredefinedRules,
|
||||
InitializeProject,
|
||||
Edit(SyncId),
|
||||
OpenSettings,
|
||||
@@ -151,6 +153,7 @@ pub struct RuleView {
|
||||
search_editor: ViewHandle<EditorView>,
|
||||
search_bar: ViewHandle<SearchBar>,
|
||||
add_button: ViewHandle<ActionButton>,
|
||||
add_predefined_rules_button: ViewHandle<ActionButton>,
|
||||
initialize_button: ViewHandle<ActionButton>,
|
||||
disabled_banner_highlight_index: HighlightedHyperlink,
|
||||
current_scope: RuleScope,
|
||||
@@ -265,12 +268,44 @@ impl RuleView {
|
||||
.on_click(|ctx| ctx.dispatch_typed_action(RuleViewAction::AddRule))
|
||||
});
|
||||
|
||||
let add_predefined_rules_button = ctx.add_typed_action_view(|_| {
|
||||
ActionButton::new("Add Predefined Rules", NakedTheme)
|
||||
.with_icon(Icon::Plus)
|
||||
.on_click(|ctx| ctx.dispatch_typed_action(RuleViewAction::AddPredefinedRules))
|
||||
});
|
||||
|
||||
let initialize_button = ctx.add_typed_action_view(|_| {
|
||||
ActionButton::new("Initialize Project", NakedTheme)
|
||||
.with_icon(Icon::Plus)
|
||||
.on_click(|ctx| ctx.dispatch_typed_action(RuleViewAction::InitializeProject))
|
||||
});
|
||||
|
||||
// Seed predefined rules on first launch if no global rules exist
|
||||
if ai_rules.is_empty() && !AISettings::as_ref(ctx).has_seeded_predefined_rules() {
|
||||
if let Some(owner) = owner {
|
||||
let update_manager = UpdateManager::handle(ctx);
|
||||
update_manager.update(ctx, |update_manager, ctx| {
|
||||
for rule in PREDEFINED_RULES {
|
||||
let ai_fact = AIFact::Memory(AIMemory {
|
||||
is_autogenerated: false,
|
||||
name: Some(rule.name.to_string()),
|
||||
content: rule.content.to_string(),
|
||||
suggested_logging_id: None,
|
||||
});
|
||||
update_manager.create_ai_fact(
|
||||
ai_fact,
|
||||
ClientId::default(),
|
||||
owner,
|
||||
ctx,
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
AISettings::handle(ctx).update(ctx, |settings, ctx| {
|
||||
settings.mark_predefined_rules_seeded(ctx);
|
||||
});
|
||||
}
|
||||
|
||||
Self {
|
||||
owner,
|
||||
global_rules: ai_rules,
|
||||
@@ -278,6 +313,7 @@ impl RuleView {
|
||||
search_editor,
|
||||
search_bar,
|
||||
add_button,
|
||||
add_predefined_rules_button,
|
||||
initialize_button,
|
||||
disabled_banner_highlight_index: Default::default(),
|
||||
current_scope: RuleScope::Global,
|
||||
@@ -418,6 +454,60 @@ impl RuleView {
|
||||
});
|
||||
}
|
||||
|
||||
pub fn add_predefined_rules(&mut self, ctx: &mut ViewContext<Self>) {
|
||||
let Some(owner) = self.owner else {
|
||||
return;
|
||||
};
|
||||
|
||||
// Build a map of existing system-defined rules by name for update detection
|
||||
let existing_system_rules: std::collections::HashMap<String, (SyncId, Option<Revision>)> =
|
||||
self.global_rules
|
||||
.iter()
|
||||
.filter_map(|row| {
|
||||
let AIFact::Memory(AIMemory { ref name, .. }) = row.fact.model().string_model;
|
||||
let name = name.as_deref().unwrap_or_default();
|
||||
if name.starts_with(SYSTEM_DEFINED_RULE_PREFIX) {
|
||||
Some((
|
||||
name.to_string(),
|
||||
(row.fact.sync_id(), row.fact.metadata().revision.clone()),
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
let update_manager = UpdateManager::handle(ctx);
|
||||
update_manager.update(ctx, |update_manager, ctx| {
|
||||
for rule in PREDEFINED_RULES {
|
||||
let ai_fact = AIFact::Memory(AIMemory {
|
||||
is_autogenerated: false,
|
||||
name: Some(rule.name.to_string()),
|
||||
content: rule.content.to_string(),
|
||||
suggested_logging_id: None,
|
||||
});
|
||||
|
||||
if let Some((sync_id, revision)) =
|
||||
existing_system_rules.get(rule.name)
|
||||
{
|
||||
update_manager.update_ai_fact(
|
||||
ai_fact,
|
||||
*sync_id,
|
||||
revision.clone(),
|
||||
ctx,
|
||||
);
|
||||
} else {
|
||||
update_manager.create_ai_fact(
|
||||
ai_fact,
|
||||
ClientId::default(),
|
||||
owner,
|
||||
ctx,
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn render_header(&self, appearance: &Appearance) -> Box<dyn Element> {
|
||||
Flex::row()
|
||||
.with_cross_axis_alignment(CrossAxisAlignment::Center)
|
||||
@@ -617,11 +707,20 @@ impl RuleView {
|
||||
.finish()
|
||||
}
|
||||
|
||||
fn render_add_predefined_rules_button(&self) -> Box<dyn Element> {
|
||||
Container::new(ChildView::new(&self.add_predefined_rules_button).finish())
|
||||
.with_margin_left(style::SECTION_MARGIN)
|
||||
.finish()
|
||||
}
|
||||
|
||||
fn render_search_bar_row(&self, filtered_rules: &[RuleRow]) -> Box<dyn Element> {
|
||||
let mut row = Flex::row()
|
||||
.with_cross_axis_alignment(CrossAxisAlignment::Center)
|
||||
.with_child(Expanded::new(1., ChildView::new(&self.search_bar).finish()).finish());
|
||||
|
||||
if self.current_scope == RuleScope::Global {
|
||||
row.add_child(self.render_add_predefined_rules_button());
|
||||
}
|
||||
if !filtered_rules.is_empty() {
|
||||
row.add_child(self.render_add_button());
|
||||
}
|
||||
@@ -945,6 +1044,9 @@ impl TypedActionView for RuleView {
|
||||
RuleViewAction::AddRule => {
|
||||
ctx.emit(RuleViewEvent::AddRule);
|
||||
}
|
||||
RuleViewAction::AddPredefinedRules => {
|
||||
self.add_predefined_rules(ctx);
|
||||
}
|
||||
RuleViewAction::Edit(sync_id) => {
|
||||
ctx.emit(RuleViewEvent::Edit(*sync_id));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user