diff --git a/crates/core/src/palace.rs b/crates/core/src/palace.rs index 0f31326..b3196bd 100644 --- a/crates/core/src/palace.rs +++ b/crates/core/src/palace.rs @@ -579,6 +579,63 @@ pub struct Palace { pub llm: Option>, /// Session store for lifecycle observations (Phase 0 / bead 0.3). pub sessions: Option>, + /// mp-migration 5/8: optional activity sink. When set, the + /// `Palace` impl fires `ActivityEvent`s at meaningful pipeline + /// points (search start/done, found relevant, tool action, etc). + /// Used by the jcode adapter to drive its `MemoryEventSink`. + pub activity_sink: Option>, +} + +// --------------------------------------------------------------------------- +// ActivityEvent (mp-migration 5/8) +// --------------------------------------------------------------------------- + +/// A single lifecycle event from the palace's per-call pipeline. +/// +/// Mirrors jcode's `MemoryEventKind` + `MemoryState` enums in a +/// single struct. jcode's adapter maps these to its own +/// `ServerEvent::MemoryActivity { activity }` shape. +/// +/// The pipeline progresses as: +/// Idle → Embedding → FoundRelevant → Idle +/// Idle → Extracting → Idle +/// Idle → Maintaining → Idle +/// Idle → ToolAction → Idle (for tool-driven calls) +/// +/// `detail` carries an optional human-readable string (e.g. the +/// query that was searched, the drawer id that was filed, the +/// tag that was applied). +#[derive(Debug, Clone)] +pub struct ActivityEvent { + /// Pipeline state this event represents. + pub state: ActivityState, + /// Optional human-readable detail. + pub detail: Option, + /// Wall-clock timestamp when the event was emitted. + pub timestamp: chrono::DateTime, +} + +/// Pipeline state for an [`ActivityEvent`]. Mirrors jcode's +/// `MemoryState` enum. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum ActivityState { + /// No activity. Default / after-pipeline. + Idle, + /// Running embedding search. + Embedding, + /// Sidecar (LLM) checking relevance of candidates. + SidecarChecking, + /// Embedding search found relevant results. + FoundRelevant, + /// Extracting memories from a transcript. + Extracting, + /// Background maintenance (consolidation, lesson decay, etc). + Maintaining, + /// Agent is using a memory tool (remember/recall/etc). + ToolAction { + /// "remember" | "recall" | "search" | "list" | "forget" | "tag" | "link" | "related" + action: &'static str, + }, } impl std::fmt::Debug for Palace { @@ -592,6 +649,19 @@ impl std::fmt::Debug for Palace { // --------------------------------------------------------------------------- impl Palace { + /// Fire an activity event if a sink is registered. mp-migration + /// 5/8. Called from the `impl MemoryProvider for Palace` methods + /// to publish state transitions; no-op when no sink is set. + fn emit_activity(&self, state: ActivityState, detail: Option) { + if let Some(sink) = &self.activity_sink { + sink(ActivityEvent { + state, + detail, + timestamp: chrono::Utc::now(), + }); + } + } + fn derive_drawer_id(content: &str) -> DrawerId { use std::collections::hash_map::DefaultHasher; use std::hash::{Hash, Hasher}; diff --git a/crates/core/src/palace/builder.rs b/crates/core/src/palace/builder.rs index 20c59a9..97569b3 100644 --- a/crates/core/src/palace/builder.rs +++ b/crates/core/src/palace/builder.rs @@ -84,6 +84,12 @@ pub struct PalaceBuilder { store: Option>, llm: Option>, session_store: Option>, + /// mp-migration 5/8: optional callback fired from + /// `Palace::add_drawer` / `forget` / `search` etc. with an + /// `ActivityEvent`. Used by the jcode adapter to mirror jcode's + /// `MemoryEventSink = Arc` and feed the + /// `MemoryActivity` snapshot the TUI info widget reads. + activity_sink: Option>, } impl std::fmt::Debug for PalaceBuilder { @@ -105,6 +111,7 @@ impl PalaceBuilder { store: None, llm: None, session_store: None, + activity_sink: None, } } @@ -154,6 +161,21 @@ impl PalaceBuilder { self } + /// Set the activity sink (mp-migration 5/8). + /// + /// When set, the [`Palace`] fires an [`super::ActivityEvent`] at + /// every meaningful point in the per-call pipeline (search start, + /// search done, found relevant, sidecar checking, extracting, + /// maintaining, tool action). jcode's adapter plugs this into + /// its `MemoryEventSink = Arc` to drive the + /// TUI info widget. + /// + /// Default: no sink. Calls complete silently. + pub fn activity_sink(mut self, sink: Arc) -> Self { + self.activity_sink = Some(sink); + self + } + /// Open the palace. Validates all required fields and initializes /// storage. Returns an error if config or embedder is missing, or /// if the embedder fails to load. @@ -227,6 +249,7 @@ impl PalaceBuilder { store, llm: self.llm, sessions: self.session_store, + activity_sink: self.activity_sink, }) } }