use crate::{ActivityLog, ActivitySource};
use std::path::Path;
use std::sync::Arc;
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum HookType {
UserPromptSubmit,
SessionStart,
}
impl HookType {
pub fn as_str(&self) -> &'static str {
match self {
Self::UserPromptSubmit => "UserPromptSubmit",
Self::SessionStart => "SessionStart",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum InjectionKind {
PromptContext,
InboxCheck,
}
impl InjectionKind {
pub fn as_str(&self) -> &'static str {
match self {
Self::PromptContext => "prompt-context",
Self::InboxCheck => "inbox-check",
}
}
}
#[derive(Clone, Debug, serde::Serialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum DaemonEvent {
PalaceCreated {
id: String,
name: String,
source: ActivitySource,
},
DrawerAdded {
palace_id: String,
#[serde(default)]
palace_name: String,
drawer_count: usize,
timestamp: chrono::DateTime<chrono::Utc>,
#[serde(default)]
content_preview: String,
source: ActivitySource,
},
DrawerDeleted {
palace_id: String,
drawer_count: usize,
source: ActivitySource,
},
DreamCompleted {
palace_id: Option<String>,
merged: usize,
pruned: usize,
compacted: usize,
closets_updated: usize,
duration_ms: u64,
source: ActivitySource,
},
StatusChanged {
total_drawers: usize,
total_vectors: usize,
total_kg_triples: usize,
},
HookFired {
#[serde(default)]
palace_id: Option<String>,
#[serde(default)]
palace_name: Option<String>,
hook_type: HookType,
injection_kind: InjectionKind,
injection_length: u64,
#[serde(default)]
trigger_prompt_excerpt: String,
timestamp: chrono::DateTime<chrono::Utc>,
duration_ms: u64,
source: ActivitySource,
},
}
impl DaemonEvent {
pub fn type_str(&self) -> &'static str {
match self {
Self::PalaceCreated { .. } => "palace_created",
Self::DrawerAdded { .. } => "drawer_added",
Self::DrawerDeleted { .. } => "drawer_deleted",
Self::DreamCompleted { .. } => "dream_completed",
Self::StatusChanged { .. } => "status_changed",
Self::HookFired { .. } => "hook_fired",
}
}
pub fn palace_id(&self) -> Option<&str> {
match self {
Self::PalaceCreated { id, .. } => Some(id),
Self::DrawerAdded { palace_id, .. } | Self::DrawerDeleted { palace_id, .. } => {
Some(palace_id)
}
Self::DreamCompleted { palace_id, .. } => palace_id.as_deref(),
Self::HookFired { palace_id, .. } => palace_id.as_deref(),
Self::StatusChanged { .. } => None,
}
}
pub fn source(&self) -> Option<ActivitySource> {
match self {
Self::PalaceCreated { source, .. }
| Self::DrawerAdded { source, .. }
| Self::DrawerDeleted { source, .. }
| Self::DreamCompleted { source, .. }
| Self::HookFired { source, .. } => Some(*source),
Self::StatusChanged { .. } => None,
}
}
}
pub(crate) fn open_activity_log_with_fallback(data_root: &Path) -> Arc<ActivityLog> {
open_activity_log_with_fallback_in(data_root, &std::env::temp_dir())
}
pub(crate) fn open_activity_log_with_fallback_in(
data_root: &Path,
fallback_root: &Path,
) -> Arc<ActivityLog> {
match ActivityLog::open(data_root) {
Ok(log) => Arc::new(log),
Err(primary_err) => {
tracing::warn!(
"could not open activity log at {}: {primary_err:#}; falling back to per-process tempdir",
data_root.display()
);
let fallback =
fallback_root.join(format!("trusty-memory-activity-{}", std::process::id()));
match ActivityLog::open(&fallback) {
Ok(log) => Arc::new(log),
Err(fallback_err) => {
tracing::warn!(
"activity log tempdir fallback at {} also failed: {fallback_err:#}; \
activity feed disabled for this process (no-op log)",
fallback.display()
);
Arc::new(ActivityLog::discard())
}
}
}
}
}