use crate::{AppState, DaemonReadiness};
use anyhow::{anyhow, Context, Result};
use chrono::Utc;
use serde_json::{json, Value};
use trusty_common::memory_core::palace::DrawerType;
use trusty_common::memory_core::retrieval::RememberOptions;
use trusty_common::memory_core::timeouts;
use uuid::Uuid;
use super::helpers::{
open_palace_handle, parse_room, parse_tags, resolve_palace, room_label, write_drawer,
WriteDrawerParams,
};
pub(crate) async fn handle_task_add(state: &AppState, args: Value) -> Result<Value> {
let defer_embedding = state.readiness() == DaemonReadiness::Warming;
let palace = resolve_palace(state, &args, "task_add")?;
let palace = palace.as_str();
let content = args
.get("content")
.and_then(|v| v.as_str())
.ok_or_else(|| anyhow!("task_add: missing 'content'"))?
.to_string();
let room = parse_room(args.get("room").and_then(|v| v.as_str()));
let tags = parse_tags(&args);
let room_label_for_kg = room_label(&room);
let write_lock = state.palace_write_lock(palace);
let _write_guard =
timeouts::lock_with_timeout(&write_lock, timeouts::write_lock_timeout(), palace)
.await
.map_err(|e| anyhow!("task_add: {e:#}"))?;
let drawer_id = write_drawer(
state,
WriteDrawerParams {
palace_id: palace,
content,
tags,
room,
importance: 1.0, opts: RememberOptions {
force: true, enforce_min_tokens: false,
classify_as: Some(DrawerType::Task),
defer_embedding,
..RememberOptions::default()
},
room_label_for_kg,
},
)
.await?;
Ok(json!({
"drawer_id": drawer_id.to_string(),
"palace": palace,
"status": "stored",
"drawer_type": "Task",
}))
}
pub(crate) async fn handle_task_list(state: &AppState, args: Value) -> Result<Value> {
let palace = resolve_palace(state, &args, "task_list")?;
let include_completed = args
.get("include_completed")
.and_then(|v| v.as_bool())
.unwrap_or(false);
let handle = open_palace_handle(state, &palace)?;
let tasks: Vec<Value> = {
let drawers = handle.drawers.read();
drawers
.iter()
.filter(|d| d.drawer_type == DrawerType::Task)
.filter(|d| include_completed || d.completed_at.is_none())
.map(|d| {
json!({
"drawer_id": d.id.to_string(),
"content": d.content,
"importance": d.importance,
"tags": d.tags,
"created_at": d.created_at.to_rfc3339(),
"completed_at": d.completed_at.map(|t| t.to_rfc3339()),
"drawer_type": "Task",
})
})
.collect()
};
Ok(json!({ "palace": palace, "tasks": tasks }))
}
pub(crate) async fn handle_task_complete(state: &AppState, args: Value) -> Result<Value> {
let palace = resolve_palace(state, &args, "task_complete")?;
let drawer_id_str = args
.get("drawer_id")
.and_then(|v| v.as_str())
.ok_or_else(|| anyhow!("task_complete: missing 'drawer_id'"))?;
let drawer_id = Uuid::parse_str(drawer_id_str)
.map_err(|e| anyhow!("task_complete: invalid drawer_id UUID: {e}"))?;
let handle = open_palace_handle(state, &palace)?;
let drawer = {
let drawers = handle.drawers.read();
drawers
.iter()
.find(|d| d.id == drawer_id)
.cloned()
.ok_or_else(|| anyhow!("task_complete: drawer not found: {drawer_id}"))?
};
if drawer.drawer_type != DrawerType::Task {
return Err(anyhow!(
"task_complete: drawer {drawer_id} is not a Task (type: {})",
drawer.drawer_type.as_str()
));
}
let now = Utc::now();
let mut updated = drawer;
updated.completed_at = Some(now);
handle
.kg
.upsert_drawer(&updated)
.await
.context("task_complete: persist updated drawer")?;
{
let mut drawers = handle.drawers.write();
if let Some(d) = drawers.iter_mut().find(|d| d.id == drawer_id) {
d.completed_at = Some(now);
}
}
Ok(json!({
"palace": palace,
"drawer_id": drawer_id_str,
"completed_at": now.to_rfc3339(),
"status": "completed",
}))
}