Seed predefined rules at startup without requiring Warp auth
Previously, predefined rules (11 system-defined behavioral rules) were
only seeded when the user navigated to the Rules settings page, and
required a cloud-authenticated owner to create cloud objects.
Since Galaxy operates without Warp cloud auth:
1. personal_drive() now returns a synthetic local owner
('local-galaxy-user') when not authenticated, allowing cloud objects
(rules, etc.) to be created and stored locally in SQLite.
2. seed_predefined_rules_if_needed() runs at app startup (after
CloudModel and UpdateManager are initialized) to seed rules on
first launch without requiring the user to visit settings.
Rules are now visible in Galaxy Drive > Rules on first launch.
This commit is contained in:
@@ -2205,6 +2205,10 @@ pub(crate) fn initialize_app(
|
||||
ctx.add_singleton_model(HarnessAvailabilityModel::new);
|
||||
ctx.add_singleton_model(ConnectedSelfHostedWorkersModel::new);
|
||||
|
||||
// Seed predefined rules on first launch if no global rules exist.
|
||||
// This runs after CloudModel and UpdateManager are initialized.
|
||||
seed_predefined_rules_if_needed(ctx);
|
||||
|
||||
let tip_model_handle = ctx.add_singleton_model(|ctx| {
|
||||
ai::agent_tips::AITipModel::<ai::AgentTip>::new_for_agent_tips(ctx)
|
||||
});
|
||||
@@ -2913,6 +2917,65 @@ fn launch(ctx: &mut galaxyui::AppContext, app_state: Option<AppState>, launch_mo
|
||||
///
|
||||
/// Additionally, we must not write anything to stdout in this function, as it
|
||||
/// can interfere with test harnesses collecting the set of tests to run. (This
|
||||
/// Seeds predefined system rules into the local CloudModel on first launch
|
||||
/// (when no global rules exist and they haven't been seeded before).
|
||||
/// This ensures rules are available for AI requests without requiring the user
|
||||
/// to manually visit the Rules settings page.
|
||||
fn seed_predefined_rules_if_needed(ctx: &mut AppContext) {
|
||||
use ai::facts::predefined_rules::PREDEFINED_RULES;
|
||||
use ai::facts::{AIFact, AIMemory};
|
||||
use cloud_object::model::generic_string_model::GenericStringObjectId;
|
||||
use cloud_object::model::persistence::CloudModel;
|
||||
use server::cloud_objects::update_manager::UpdateManager;
|
||||
use server::ids::ClientId;
|
||||
use workspaces::user_workspaces::UserWorkspaces;
|
||||
|
||||
let settings = AISettings::as_ref(ctx);
|
||||
if settings.has_seeded_predefined_rules() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if any rules already exist
|
||||
let has_existing_rules = CloudModel::handle(ctx)
|
||||
.as_ref(ctx)
|
||||
.get_all_objects_of_type::<GenericStringObjectId, ai::facts::CloudAIFactModel>()
|
||||
.next()
|
||||
.is_some();
|
||||
|
||||
if has_existing_rules {
|
||||
// Rules exist (e.g. from a previous session) — mark as seeded and skip
|
||||
AISettings::handle(ctx).update(ctx, |settings, ctx| {
|
||||
settings.mark_predefined_rules_seeded(ctx);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
let Some(owner) = UserWorkspaces::as_ref(ctx).personal_drive(ctx) else {
|
||||
return;
|
||||
};
|
||||
|
||||
log::info!(
|
||||
"[rules] Seeding {} predefined rules on first launch",
|
||||
PREDEFINED_RULES.len()
|
||||
);
|
||||
|
||||
UpdateManager::handle(ctx).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);
|
||||
});
|
||||
}
|
||||
|
||||
/// is why we're not simply calling the init() function above.)
|
||||
#[ctor::ctor]
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -717,10 +717,18 @@ impl UserWorkspaces {
|
||||
// Returns the [`Owner`] for the user's personal drive. If the user is not authenticated, this
|
||||
// returns `None`.
|
||||
pub fn personal_drive(&self, ctx: &AppContext) -> Option<Owner> {
|
||||
AuthStateProvider::as_ref(ctx)
|
||||
.get()
|
||||
.user_id()
|
||||
.map(|user_uid| Owner::User { user_uid })
|
||||
// Return the authenticated user's ID if available, otherwise provide a
|
||||
// synthetic local owner so cloud objects (rules, etc.) can be created and
|
||||
// stored locally without requiring Warp authentication.
|
||||
Some(
|
||||
AuthStateProvider::as_ref(ctx)
|
||||
.get()
|
||||
.user_id()
|
||||
.map(|user_uid| Owner::User { user_uid })
|
||||
.unwrap_or_else(|| Owner::User {
|
||||
user_uid: UserUid::new("local-galaxy-user"),
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
// Maps a [`Space`] into an [`Owner`], based on the user's team memberships. If the space
|
||||
|
||||
Reference in New Issue
Block a user