diff --git a/src/agent/tools/memory.rs b/src/agent/tools/memory.rs index b525925..55c6447 100644 --- a/src/agent/tools/memory.rs +++ b/src/agent/tools/memory.rs @@ -194,7 +194,7 @@ memory_tool!(memory_links, ref -> Vec, key: [str]); pub use crate::hippocampus::local::JournalEntry; memory_tool!(journal_tail, ref -> Vec, count: [Option], level: [Option], after: [Option<&str>]); -memory_tool!(journal_new, mut, name: [str], title: [str], body: [str], level: [Option]); +memory_tool!(journal_new, mut, name: [str], title: [str], body: [str], level: [Option], date: [Option<&str>]); memory_tool!(journal_update, mut, body: [str], level: [Option]); // ── Graph tools ─────────────────────────────────────────────── @@ -363,7 +363,8 @@ pub fn journal_tools() -> [super::Tool; 3] { "name": {"type": "string"}, "title": {"type": "string"}, "body": {"type": "string"}, - "level": {"type": "integer"} + "level": {"type": "integer"}, + "date": {"type": "string", "description": "Override timestamp (YYYY-MM-DD or YYYY-MM-DDTHH:MM)"} }, "required": ["name", "title", "body"] }"#), diff --git a/src/cli/journal.rs b/src/cli/journal.rs index 52436fc..f5f6417 100644 --- a/src/cli/journal.rs +++ b/src/cli/journal.rs @@ -82,14 +82,14 @@ pub async fn cmd_journal_tail(n: usize, full: bool, level: u8) -> Result<()> { Ok(()) } -pub async fn cmd_journal_write(name: &str, text: &[String]) -> Result<()> { +pub async fn cmd_journal_write(name: &str, date: Option<&str>, text: &[String]) -> Result<()> { if text.is_empty() { bail!("journal write requires text"); } super::check_dry_run(); let body = text.join(" "); - let result = memory::journal_new(None, name, name, &body, Some(0)).await?; + let result = memory::journal_new(None, name, name, &body, Some(0), date).await?; println!("{}", result); Ok(()) } diff --git a/src/hippocampus/local.rs b/src/hippocampus/local.rs index 66b4803..0577811 100644 --- a/src/hippocampus/local.rs +++ b/src/hippocampus/local.rs @@ -315,9 +315,13 @@ fn level_to_node_type(level: i64) -> crate::store::NodeType { } } -pub fn journal_new(store: &Store, provenance: &str, name: &str, title: &str, body: &str, level: Option) -> Result { +pub fn journal_new(store: &Store, provenance: &str, name: &str, title: &str, body: &str, level: Option, date: Option<&str>) -> Result { let level = level.unwrap_or(0); - let ts = chrono::Local::now().format("%Y-%m-%dT%H:%M"); + let ts = if let Some(d) = date { + d.to_string() + } else { + chrono::Local::now().format("%Y-%m-%dT%H:%M").to_string() + }; let content = format!("## {} — {}\n\n{}", ts, title, body); let base_key: String = name.split_whitespace() @@ -340,6 +344,12 @@ pub fn journal_new(store: &Store, provenance: &str, name: &str, title: &str, bod base_key.to_string() }; let mut node = crate::store::new_node(&key, &content); + if let Some(d) = date { + if let Some(epoch) = parse_date_to_epoch(d) { + node.timestamp = epoch; + node.created_at = epoch; + } + } node.node_type = level_to_node_type(level); node.provenance = provenance.to_string(); store.upsert_node(node).map_err(|e| anyhow::anyhow!("{}", e))?; @@ -348,6 +358,18 @@ pub fn journal_new(store: &Store, provenance: &str, name: &str, title: &str, bod Ok(format!("New entry '{}' ({} words)", title, word_count)) } +fn parse_date_to_epoch(date: &str) -> Option { + use chrono::NaiveDate; + use chrono::NaiveDateTime; + if let Ok(dt) = NaiveDateTime::parse_from_str(date, "%Y-%m-%dT%H:%M") { + Some(dt.and_local_timezone(chrono::Local).single()?.timestamp()) + } else if let Ok(d) = NaiveDate::parse_from_str(date, "%Y-%m-%d") { + Some(d.and_hms_opt(12, 0, 0)?.and_local_timezone(chrono::Local).single()?.timestamp()) + } else { + None + } +} + pub fn journal_update(store: &Store, provenance: &str, body: &str, level: Option) -> Result { let level = level.unwrap_or(0); let node_type = level_to_node_type(level); diff --git a/src/hippocampus/mod.rs b/src/hippocampus/mod.rs index d79640d..32a243a 100644 --- a/src/hippocampus/mod.rs +++ b/src/hippocampus/mod.rs @@ -309,7 +309,7 @@ memory_tool!(memory_links, ref -> Vec, key: [str]); // ── Journal tools ────────────────────────────────────────────── memory_tool!(journal_tail, ref -> Vec, count: [Option], level: [Option], after: [Option<&str>]); -memory_tool!(journal_new, mut, name: [str], title: [str], body: [str], level: [Option]); +memory_tool!(journal_new, mut, name: [str], title: [str], body: [str], level: [Option], date: [Option<&str>]); memory_tool!(journal_update, mut, body: [str], level: [Option]); // ── Graph tools ─────────────────────────────────────────────── diff --git a/src/main.rs b/src/main.rs index f13448c..d12ce9e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -195,6 +195,9 @@ enum JournalCmd { Write { /// Entry name (becomes the node key) name: String, + /// Override timestamp (YYYY-MM-DD or YYYY-MM-DDTHH:MM) + #[arg(long)] + date: Option, /// Entry text text: Vec, }, @@ -415,7 +418,7 @@ impl Run for NodeCmd { impl Run for JournalCmd { async fn run(self) -> anyhow::Result<()> { match self { - Self::Write { name, text } => cli::journal::cmd_journal_write(&name, &text).await, + Self::Write { name, date, text } => cli::journal::cmd_journal_write(&name, date.as_deref(), &text).await, Self::Tail { n, full, level } => cli::journal::cmd_journal_tail(n, full, level).await, } }