use std::time::Duration;
use tokio_util::sync::CancellationToken;
use crate::app::events::BackgroundKind;
use crate::entities::profile::ToolId;
use crate::entities::sampling::SamplingConfig;
use crate::features::tools::notes;
use crate::shared::api::{ApiMessage, ChatRequest};
use super::Orchestrator;
use super::request::last_user_message_at;
use super::tool_loop;
const CONSOLIDATE_MAX_TOKENS: usize = 2048;
const CONSOLIDATE_MAX_ROUNDS: u32 = 8;
const CONSOLIDATE_TIMEOUT: Duration = Duration::from_secs(180);
const CONSOLIDATE_TOOL_IDS: &[&str] = &[
"note_recall",
notes::NOTE_REVISE_ID,
notes::NOTE_SUPERSEDE_ID,
notes::NOTE_MERGE_ID,
notes::NOTE_LINK_ID,
notes::NOTE_NEIGHBORS_ID,
];
impl Orchestrator {
pub(super) fn maybe_auto_consolidate(&mut self, chat_id: uuid::Uuid) {
let every = self.config.notes.auto_consolidate_every;
if every == 0 {
return;
}
let profile_id;
let lang; let system_message;
let last_user;
let allowed: Vec<ToolId>;
{
let Some(chat) = self.chats.iter().find(|c| c.id == chat_id) else {
return;
};
profile_id = chat.profile_id;
let Some(profile) = self.profiles.iter().find(|p| p.id == profile_id) else {
return;
};
lang = profile.language;
if !profile
.enabled_tools
.iter()
.any(|t| t == notes::NOTE_MERGE_ID)
{
return;
}
allowed = CONSOLIDATE_TOOL_IDS
.iter()
.filter(|id| profile.enabled_tools.iter().any(|t| t == **id))
.map(ToString::to_string)
.collect();
system_message = chat.system_message.clone();
last_user = last_user_message_at(chat);
}
{
let count = self.consolidate_counts.entry(chat_id).or_insert(0);
*count += 1;
if !tool_loop::due(*count, every) {
return;
}
}
if self.bg_running(BackgroundKind::Consolidation) {
return; }
let active_user = self
.storage
.db()
.note_list(profile_id, None, &[], None)
.unwrap_or_default()
.iter()
.filter(|n| !notes::is_self_note(n))
.count();
if active_user < 2 {
return;
}
let Ok(backend) = self.engines.backend_if_ready(self.ui_locale()) else {
return;
};
let count = self.consolidate_counts.insert(chat_id, 0).unwrap_or(0);
let window = Some(super::background::Window::Counter {
chat: chat_id,
count,
});
let overview = notes::build_consolidation_overview(
&self.storage,
profile_id,
crate::shared::i18n::locale(lang),
);
let cancel = CancellationToken::new();
let sessions = self.session_budget();
let ctx = self.background_tool_ctx(
backend.clone(),
sessions,
profile_id,
chat_id,
system_message,
last_user,
lang,
cancel.clone(),
);
let sampling = SamplingConfig {
max_tokens: Some(CONSOLIDATE_MAX_TOKENS),
temperature: Some(0.3),
..Default::default()
};
let request = ChatRequest {
continue_final: false,
system: Some(
crate::shared::i18n::locale(lang)
.t("prompt.consolidate.system")
.to_string(),
),
messages: vec![ApiMessage::user(overview)],
sampling,
tools: self
.registry
.schemas_for(&allowed, crate::shared::i18n::locale(lang)),
};
let acted = std::sync::Arc::new(super::background::Acted::default());
tool_loop::spawn_silent_loop(tool_loop::SilentLoop {
backend,
registry: self.registry.clone(),
ctx,
request,
allowed,
cancel: cancel.clone(),
max_rounds: CONSOLIDATE_MAX_ROUNDS,
timeout: CONSOLIDATE_TIMEOUT,
label: "auto-consolidation",
profile_id,
kind: BackgroundKind::Consolidation,
done_tx: self.bg_done_tx.clone(),
acted: acted.clone(),
summary_semantics: None,
});
self.begin_bg(
BackgroundKind::Consolidation,
cancel,
window.map(|window| super::background::Refund { window, acted }),
);
}
}