2 Commits
Author SHA1 Message Date
Hare 159704dc6f fix: clear encrypted-only thinking snapshots 2026-06-25 20:28:51 +09:00
Hare 6492f10f42 test: update pod prompt expectations 2026-06-25 18:43:57 +09:00
4 changed files with 115 additions and 12 deletions
+99 -9
View File
@@ -235,15 +235,24 @@ impl InFlightInner {
self.remove_first_text_matching(&text); self.remove_first_text_matching(&text);
} }
} }
LoggedItem::Reasoning { text, summary, .. } => { LoggedItem::Reasoning {
text,
summary,
encrypted_content,
..
} => {
let mut removed = false;
if !text.is_empty() { if !text.is_empty() {
self.remove_first_thinking_matching(text); removed |= self.remove_first_thinking_matching(text);
} }
for summary_text in summary { for summary_text in summary {
if !summary_text.is_empty() { if !summary_text.is_empty() {
self.remove_first_thinking_matching(summary_text); removed |= self.remove_first_thinking_matching(summary_text);
} }
} }
if !removed && encrypted_content.is_some() {
self.remove_first_empty_finished_thinking();
}
} }
LoggedItem::ToolCall { call_id, .. } => { LoggedItem::ToolCall { call_id, .. } => {
self.remove_tool_call(call_id); self.remove_tool_call(call_id);
@@ -262,21 +271,39 @@ impl InFlightInner {
} }
} }
fn remove_first_text_matching(&mut self, committed: &str) { fn remove_first_text_matching(&mut self, committed: &str) -> bool {
if let Some(index) = self.blocks.iter().position(|block| match block { if let Some(index) = self.blocks.iter().position(|block| match block {
TrackedBlock::Text { text, .. } => text == committed, TrackedBlock::Text { text, .. } => text == committed,
_ => false, _ => false,
}) { }) {
self.blocks.remove(index); self.blocks.remove(index);
true
} else {
false
} }
} }
fn remove_first_thinking_matching(&mut self, committed: &str) { fn remove_first_thinking_matching(&mut self, committed: &str) -> bool {
if let Some(index) = self.blocks.iter().position(|block| match block { if let Some(index) = self.blocks.iter().position(|block| match block {
TrackedBlock::Thinking { text, .. } => text == committed, TrackedBlock::Thinking { text, .. } => text == committed,
_ => false, _ => false,
}) { }) {
self.blocks.remove(index); self.blocks.remove(index);
true
} else {
false
}
}
fn remove_first_empty_finished_thinking(&mut self) -> bool {
if let Some(index) = self.blocks.iter().position(|block| match block {
TrackedBlock::Thinking { text, finished, .. } => text.is_empty() && *finished,
_ => false,
}) {
self.blocks.remove(index);
true
} else {
false
} }
} }
@@ -311,10 +338,16 @@ impl TrackedBlock {
}) })
} }
} }
TrackedBlock::Thinking { text, finished, .. } => Some(InFlightBlock::Thinking { TrackedBlock::Thinking { text, finished, .. } => {
text: text.clone(), if text.is_empty() && *finished {
finished: *finished, None
}), } else {
Some(InFlightBlock::Thinking {
text: text.clone(),
finished: *finished,
})
}
}
TrackedBlock::ToolCall { TrackedBlock::ToolCall {
id, id,
name, name,
@@ -506,4 +539,61 @@ mod tests {
let guard = in_flight.snapshot_guard(); let guard = in_flight.snapshot_guard();
assert!(snapshot_from_guard(&guard).is_empty()); assert!(snapshot_from_guard(&guard).is_empty());
} }
#[test]
fn committed_encrypted_only_reasoning_clears_empty_finished_thinking_block() {
let (event_tx, _) = broadcast::channel(16);
let in_flight = InFlightEvents::new(event_tx);
let first = in_flight.thinking_start();
in_flight.thinking_done(first, "".into());
let second = in_flight.thinking_start();
in_flight.thinking_delta(second, "still running".into());
in_flight.clear_for_committed_item_then(
&LoggedItem::Reasoning {
text: String::new(),
summary: Vec::new(),
encrypted_content: Some("opaque".into()),
signature: None,
},
|| (),
);
let guard = in_flight.snapshot_guard();
assert_eq!(
snapshot_from_guard(&guard).blocks,
vec![InFlightBlock::Thinking {
text: "still running".into(),
finished: false,
}]
);
}
#[test]
fn snapshot_omits_empty_finished_thinking_blocks() {
let (event_tx, _) = broadcast::channel(16);
let in_flight = InFlightEvents::new(event_tx);
let empty_finished = in_flight.thinking_start();
in_flight.thinking_done(empty_finished, "".into());
let empty_running = in_flight.thinking_start();
let visible_finished = in_flight.thinking_start();
in_flight.thinking_delta(visible_finished, "visible".into());
in_flight.thinking_done(visible_finished, "".into());
let guard = in_flight.snapshot_guard();
assert_eq!(
snapshot_from_guard(&guard).blocks,
vec![
InFlightBlock::Thinking {
text: String::new(),
finished: false,
},
InFlightBlock::Thinking {
text: "visible".into(),
finished: true,
}
]
);
assert_ne!(empty_running, empty_finished);
}
} }
+1 -1
View File
@@ -739,7 +739,7 @@ compact_system = "PREFIX\n{% include \"$yoi/internal/compact_system\" %}"
assert!(rendered.contains("spawned Pod notifications are background signals")); assert!(rendered.contains("spawned Pod notifications are background signals"));
assert!(rendered.contains("does not need to keep a turn open")); assert!(rendered.contains("does not need to keep a turn open"));
assert!(rendered.contains("Do not use `sleep` or polling loops")); assert!(rendered.contains("Do not use `sleep` or polling loops"));
assert!(rendered.contains("worktree status, diff, and test results")); assert!(rendered.contains("worktree state, diff, and test results"));
assert!(rendered.contains("not scheduler or auto-maintain authorization")); assert!(rendered.contains("not scheduler or auto-maintain authorization"));
assert!(rendered.contains("bypass user/workflow authorization")); assert!(rendered.contains("bypass user/workflow authorization"));
} }
+1 -1
View File
@@ -643,7 +643,7 @@ mod tests {
assert!(rendered.contains("spawned Pod notifications are background signals")); assert!(rendered.contains("spawned Pod notifications are background signals"));
assert!(rendered.contains("does not need to keep a turn open")); assert!(rendered.contains("does not need to keep a turn open"));
assert!(rendered.contains("Do not use `sleep` or polling loops")); assert!(rendered.contains("Do not use `sleep` or polling loops"));
assert!(rendered.contains("worktree status, diff, and test results")); assert!(rendered.contains("worktree state, diff, and test results"));
assert!(rendered.contains("not scheduler or auto-maintain authorization")); assert!(rendered.contains("not scheduler or auto-maintain authorization"));
assert!(rendered.contains("bypass user/workflow authorization")); assert!(rendered.contains("bypass user/workflow authorization"));
} }
+14 -1
View File
@@ -252,7 +252,20 @@ async fn feature_flags_default_to_core_tool_surface_only() {
let request = wait_for_captured_request(&client_for_assert).await; let request = wait_for_captured_request(&client_for_assert).await;
let names = request_tool_names(&request); let names = request_tool_names(&request);
assert_eq!(names, vec!["Bash", "Edit", "Glob", "Grep", "Read", "Write"]); assert_eq!(
names,
vec![
"ActiveWorkflowCancel",
"ActiveWorkflowComplete",
"ActiveWorkflowList",
"Bash",
"Edit",
"Glob",
"Grep",
"Read",
"Write"
]
);
assert!(!names.iter().any(|name| name == "TaskCreate")); assert!(!names.iter().any(|name| name == "TaskCreate"));
assert!(!names.iter().any(|name| name == "WebSearch")); assert!(!names.iter().any(|name| name == "WebSearch"));
assert!(!names.iter().any(|name| name == "SpawnPod")); assert!(!names.iter().any(|name| name == "SpawnPod"));