From 09f5630aed09b373517e20493d0012b8f39b8fbb Mon Sep 17 00:00:00 2001 From: Joey Chilson Date: Mon, 19 Oct 2026 15:38:46 -0500 Subject: [PATCH 1/2] Name an OpenCode or notebook read by its file in a handoff --- CHANGELOG.md | 5 +++ src/sessions/handoff.rs | 75 ++++++++++++++++++++++++++++------------- 2 files changed, 56 insertions(+), 24 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1bc44a4..ce2707f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,11 @@ read in the same words. `check_limits`' verdict on the first account says how to fix it, as the others do, not only that its limits can't be read. `status` writes $1,000 or more with a comma, as `statusline` does. +- **Handoff:** a read by OpenCode's `read` or Claude Code's `NotebookRead` + is named by its file, not by its input as JSON. So is a failed OpenCode + `edit` or `write`, or Claude Code `NotebookEdit`, under "What failed", + and it no longer shows there once a later one of the same file passed, + as a failed Claude Code `Edit` already didn't. ## 0.1.4 diff --git a/src/sessions/handoff.rs b/src/sessions/handoff.rs index 84067e0..7de7aff 100644 --- a/src/sessions/handoff.rs +++ b/src/sessions/handoff.rs @@ -312,6 +312,9 @@ fn tasks(entries: &[Entry], share: usize) -> String { ) } +/// The keys a tool's input names a file under, as each agent's tools do. +const PATHS: [&str; 4] = ["file_path", "filePath", "notebook_path", "path"]; + /// The files the work was in from `since` on, as a handoff's section: those /// its tools read or changed, and those its commands named that are in its /// folder `cwd`, the latest first, with how often. Up to 15, within @@ -355,7 +358,7 @@ fn files(entries: &[Entry], since: usize, cwd: Option<&str>, share: usize) -> St let Ok(input) = serde_json::from_str::(&tool.input) else { continue; }; - for key in ["file_path", "filePath", "notebook_path", "path"] { + for key in PATHS { if let Some(file) = input[key].as_str() { let changed = tool.files.iter().any(|known| known == file); if !changed { @@ -643,29 +646,21 @@ fn gist(tool: &Tool) -> String { let said = serde_json::from_str::(&tool.input) .ok() .and_then(|input| { - [ - "description", - "command", - "cmd", - "file_path", - "path", - "pattern", - "query", - "url", - "prompt", - ] - .iter() - .find_map(|key| match input.get(key)? { - serde_json::Value::String(text) => Some(text.clone()), - serde_json::Value::Array(words) => Some( - words - .iter() - .filter_map(|word| word.as_str()) - .collect::>() - .join(" "), - ), - _ => None, - }) + ["description", "command", "cmd"] + .iter() + .chain(&PATHS) + .chain(&["pattern", "query", "url", "prompt"]) + .find_map(|key| match input.get(key)? { + serde_json::Value::String(text) => Some(text.clone()), + serde_json::Value::Array(words) => Some( + words + .iter() + .filter_map(|word| word.as_str()) + .collect::>() + .join(" "), + ), + _ => None, + }) }) .unwrap_or_else(|| match commands(tool).as_slice() { [] => tool.input.clone(), @@ -1114,6 +1109,38 @@ mod tests { } } + /// A read is named by its path under whichever key its agent's tool + /// takes it: OpenCode's `read` takes `filePath`, Claude Code's + /// `NotebookRead` `notebook_path`. + #[test] + fn a_read_is_named_by_its_path() { + for (name, key) in [("read", "filePath"), ("NotebookRead", "notebook_path")] { + let entries = [ + said(Kind::User, 0, "Fix the CSV parser."), + said(Kind::Assistant, 1, "Reading the parser."), + call( + name, + serde_json::json!({ key: "src/csv.rs" }), + "", + false, + Role::Read, + ), + ]; + let handoff = brief( + &session("CSV", 2 * MINUTE), + &entries, + BUDGET, + Cite::Tool, + 3 * MINUTE, + ); + let line = format!("#2 tool `{name}`: \"src/csv.rs\""); + assert!( + handoff.lines().any(|shown| shown == line), + "{line} is missing:\n{handoff}" + ); + } + } + #[test] fn a_session_of_one_entry_says_entry() { let entries = [said(Kind::User, 0, "Fix the CSV parser.")]; From 06c64fbbbcc6b8856f418fb506084cefd9d791e8 Mon Sep 17 00:00:00 2001 From: Joey Chilson Date: Mon, 19 Oct 2026 15:39:02 -0500 Subject: [PATCH 2/2] Say Claude Code's NotebookEdit as the line before says NotebookRead --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ce2707f..dc73d63 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,7 +18,7 @@ read. `status` writes $1,000 or more with a comma, as `statusline` does. - **Handoff:** a read by OpenCode's `read` or Claude Code's `NotebookRead` is named by its file, not by its input as JSON. So is a failed OpenCode - `edit` or `write`, or Claude Code `NotebookEdit`, under "What failed", + `edit` or `write`, or Claude Code's `NotebookEdit`, under "What failed", and it no longer shows there once a later one of the same file passed, as a failed Claude Code `Edit` already didn't.