Kill TextDelta, Info — UiMessage is dead. RAII ActivityGuards replace all status feedback

Streaming text now goes directly to agent entries via append_streaming().
sync_from_agent diffs the growing entry each tick. The streaming entry
is popped when the response completes; build_response_message pushes
the final version.

All status feedback uses RAII ActivityGuards:
- push_activity() for long-running work (thinking, streaming, scoring)
- notify() for instant feedback (compacted, DMN state changes, commands)
- Guards auto-remove on Drop, appending "(complete)" and lingering 5s
- expire_activities() cleans up timed-out notifications on render tick

UiMessage enum reduced to a single Info variant with zero sends.
The channel infrastructure remains for now (Mind/Agent still take
UiSender in signatures) — mechanical cleanup for a follow-up.

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
This commit is contained in:
ProofOfConcept 2026-04-05 22:18:07 -04:00
parent e7914e3d58
commit cfddb55ed9
9 changed files with 201 additions and 186 deletions

View file

@ -9,7 +9,7 @@ use reqwest::Client;
use tokio::sync::mpsc;
use super::types::*;
use crate::user::ui_channel::{UiMessage, UiSender};
use crate::user::ui_channel::UiSender;
use super::StreamEvent;
/// Stream SSE events from an OpenAI-compatible endpoint, sending
@ -66,7 +66,6 @@ pub(super) async fn stream_events(
&request,
("Authorization", &format!("Bearer {}", api_key)),
&[],
ui_tx,
&debug_label,
request_json.as_deref(),
)
@ -105,7 +104,7 @@ pub(super) async fn stream_events(
};
if let Some(ref u) = chunk.usage {
let _ = tx.send(StreamEvent::Usage(u.clone();
let _ = tx.send(StreamEvent::Usage(u.clone()));
usage = chunk.usage;
}
@ -126,7 +125,7 @@ pub(super) async fn stream_events(
reasoning_chars += r.len();
has_reasoning = true;
if !r.is_empty() {
let _ = tx.send(StreamEvent::Reasoning(r.clone();
let _ = tx.send(StreamEvent::Reasoning(r.clone()));
}
}
if let Some(ref r) = choice.delta.reasoning_details {
@ -143,7 +142,7 @@ pub(super) async fn stream_events(
first_content_at = Some(reader.stream_start.elapsed());
}
content_len += text_delta.len();
let _ = tx.send(StreamEvent::Content(text_delta.clone();
let _ = tx.send(StreamEvent::Content(text_delta.clone()));
}
if let Some(ref tc_deltas) = choice.delta.tool_calls {