2026-03-14 18:10:22 -04:00
|
|
|
// cli/journal.rs — journal subcommand handlers
|
|
|
|
|
|
2026-04-13 13:26:22 -04:00
|
|
|
use crate::agent::tools::memory;
|
2026-03-14 18:10:22 -04:00
|
|
|
|
2026-03-26 19:11:17 -04:00
|
|
|
pub fn cmd_tail(n: usize, full: bool, provenance: Option<&str>, dedup: bool) -> Result<(), String> {
|
2026-03-14 18:10:22 -04:00
|
|
|
let path = crate::store::nodes_path();
|
|
|
|
|
if !path.exists() {
|
|
|
|
|
return Err("No node log found".into());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
use std::io::BufReader;
|
|
|
|
|
let file = std::fs::File::open(&path)
|
|
|
|
|
.map_err(|e| format!("open {}: {}", path.display(), e))?;
|
|
|
|
|
let mut reader = BufReader::new(file);
|
|
|
|
|
|
|
|
|
|
// Read all entries, keep last N
|
|
|
|
|
let mut entries: Vec<crate::store::Node> = Vec::new();
|
|
|
|
|
while let Ok(msg) = capnp::serialize::read_message(&mut reader, capnp::message::ReaderOptions::new()) {
|
|
|
|
|
let log = msg.get_root::<crate::memory_capnp::node_log::Reader>()
|
|
|
|
|
.map_err(|e| format!("read log: {}", e))?;
|
|
|
|
|
for node_reader in log.get_nodes()
|
|
|
|
|
.map_err(|e| format!("get nodes: {}", e))? {
|
|
|
|
|
let node = crate::store::Node::from_capnp_migrate(node_reader)?;
|
|
|
|
|
entries.push(node);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-26 19:11:17 -04:00
|
|
|
// Filter by provenance if specified (substring match)
|
2026-03-26 18:41:10 -04:00
|
|
|
if let Some(prov) = provenance {
|
|
|
|
|
entries.retain(|n| n.provenance.contains(prov));
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-26 19:11:17 -04:00
|
|
|
// Dedup: keep only the latest version of each key
|
|
|
|
|
if dedup {
|
|
|
|
|
let mut seen = std::collections::HashSet::new();
|
|
|
|
|
// Walk backwards so we keep the latest
|
|
|
|
|
entries = entries.into_iter().rev()
|
|
|
|
|
.filter(|n| seen.insert(n.key.clone()))
|
|
|
|
|
.collect();
|
|
|
|
|
entries.reverse();
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-14 18:10:22 -04:00
|
|
|
let start = entries.len().saturating_sub(n);
|
|
|
|
|
for node in &entries[start..] {
|
|
|
|
|
let ts = if node.timestamp > 0 && node.timestamp < 4_000_000_000 {
|
|
|
|
|
crate::store::format_datetime(node.timestamp)
|
|
|
|
|
} else {
|
|
|
|
|
format!("(raw:{})", node.timestamp)
|
|
|
|
|
};
|
|
|
|
|
let del = if node.deleted { " [DELETED]" } else { "" };
|
|
|
|
|
if full {
|
2026-03-26 17:48:44 -04:00
|
|
|
println!("--- {} (v{}) {} via {} w={:.3}{} ---",
|
2026-03-14 18:10:22 -04:00
|
|
|
node.key, node.version, ts, node.provenance, node.weight, del);
|
2026-03-26 17:48:44 -04:00
|
|
|
println!("{}\n", node.content);
|
2026-03-14 18:10:22 -04:00
|
|
|
} else {
|
|
|
|
|
let preview = crate::util::first_n_chars(&node.content, 100).replace('\n', "\\n");
|
2026-03-26 17:48:44 -04:00
|
|
|
println!(" {} v{} w={:.2}{}",
|
2026-03-14 18:10:22 -04:00
|
|
|
ts, node.version, node.weight, del);
|
2026-03-26 17:48:44 -04:00
|
|
|
println!(" {} via {}", node.key, node.provenance);
|
2026-03-14 18:10:22 -04:00
|
|
|
if !preview.is_empty() {
|
2026-03-26 17:48:44 -04:00
|
|
|
println!(" {}", preview);
|
2026-03-14 18:10:22 -04:00
|
|
|
}
|
2026-03-26 17:48:44 -04:00
|
|
|
println!();
|
2026-03-14 18:10:22 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-13 13:26:22 -04:00
|
|
|
pub async fn cmd_journal_tail(n: usize, full: bool, level: u8) -> Result<(), String> {
|
|
|
|
|
let format = if full { Some("full") } else { Some("compact") };
|
|
|
|
|
let result = memory::journal_tail(None, Some(n as u64), Some(level as u64), format, None).await
|
|
|
|
|
.map_err(|e| e.to_string())?;
|
2026-04-12 21:54:34 -04:00
|
|
|
print!("{}", result);
|
|
|
|
|
Ok(())
|
2026-03-14 18:14:52 -04:00
|
|
|
}
|
|
|
|
|
|
2026-04-13 13:26:22 -04:00
|
|
|
pub async fn cmd_journal_write(name: &str, text: &[String]) -> Result<(), String> {
|
2026-03-14 18:14:52 -04:00
|
|
|
if text.is_empty() {
|
2026-03-26 19:11:17 -04:00
|
|
|
return Err("journal write requires text".into());
|
2026-03-14 18:14:52 -04:00
|
|
|
}
|
poc-memory: POC_MEMORY_DRY_RUN=1 for agent testing
All mutating commands (write, delete, rename, link-add, journal write,
used, wrong, not-useful, gap) check POC_MEMORY_DRY_RUN after argument
validation but before mutation. If set, process exits silently — agent
tool calls are visible in the LLM output so we can see what it tried
to do without applying changes.
Read commands (render, search, graph link, journal tail) work normally
in dry-run mode so agents can still explore the graph.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-16 18:09:56 -04:00
|
|
|
super::check_dry_run();
|
2026-04-12 22:15:53 -04:00
|
|
|
let body = text.join(" ");
|
2026-03-14 18:14:52 -04:00
|
|
|
|
2026-04-13 13:26:22 -04:00
|
|
|
let result = memory::journal_new(None, name, name, &body, Some(0)).await
|
|
|
|
|
.map_err(|e| e.to_string())?;
|
2026-04-12 22:15:53 -04:00
|
|
|
println!("{}", result);
|
2026-03-14 18:14:52 -04:00
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|