merge local main into code-branch-20251007-104544 before merge

This commit is contained in:
James Peter
2025-10-07 21:51:56 +10:00
5 changed files with 133 additions and 33 deletions
+5 -5
View File
@@ -25,7 +25,7 @@ use code_exec::Cli as ExecCli;
use code_responses_api_proxy::Args as ResponsesApiProxyArgs;
use code_tui::Cli as TuiCli;
use code_tui::ExitSummary;
use code_tui::RESUME_COMMAND_NAME;
use code_tui::resume_command_name;
use std::path::Path;
use std::path::PathBuf;
use std::process;
@@ -299,8 +299,8 @@ async fn cli_main(code_linux_sandbox_exe: Option<PathBuf>) -> anyhow::Result<()>
}
if let Some(session_id) = session_id {
println!(
"To continue this session, run {} resume {}.",
RESUME_COMMAND_NAME,
"To continue this session, run {} resume {}",
resume_command_name(),
session_id
);
}
@@ -348,8 +348,8 @@ async fn cli_main(code_linux_sandbox_exe: Option<PathBuf>) -> anyhow::Result<()>
}
if let Some(session_id) = session_id {
println!(
"To continue this session, run {} resume {}.",
RESUME_COMMAND_NAME,
"To continue this session, run {} resume {}",
resume_command_name(),
session_id
);
}
+21 -24
View File
@@ -15798,40 +15798,37 @@ fi\n\
.is_some()
}) {
// Replace the tail finalized assistant cell if the new content is identical OR
// a superset revision of the previous content (common provider behavior where
// a later final slightly extends the earlier one). Otherwise append a new
// assistant message so distinct messages remain separate.
let (should_replace, _prev_len, _new_len) = self.history_cells[idx]
// a small revision that merely adds leading/trailing context. Otherwise append a
// new assistant message so distinct replies remain separate.
let should_replace = self.history_cells[idx]
.as_any()
.downcast_ref::<history_cell::AssistantMarkdownCell>()
.map(|amc| {
let prev = Self::normalize_text(amc.markdown());
let newn = Self::normalize_text(&source);
let identical = prev == newn;
let is_superset = !identical && newn.contains(&prev);
// Heuristic: treat as revision when previous is reasonably long to
// avoid collapsing very short replies unintentionally.
let long_enough = prev.len() >= 80;
(
identical || (is_superset && long_enough),
prev.len(),
newn.len(),
)
if identical || prev.is_empty() {
return identical;
}
let is_prefix_expansion = newn.starts_with(&prev);
let is_suffix_expansion = newn.ends_with(&prev);
let is_large_superset = prev.len() >= 80 && newn.contains(&prev);
identical || is_prefix_expansion || is_suffix_expansion || is_large_superset
})
.unwrap_or((false, 0, 0));
.unwrap_or(false);
if should_replace {
tracing::debug!(
"final-answer: replacing tail AssistantMarkdownCell via heuristic identical/superset"
"final-answer: replacing tail AssistantMarkdownCell via heuristic identical/expansion"
);
let state =
self.finalize_answer_stream_state(id.as_deref(), &final_source);
let cell = history_cell::AssistantMarkdownCell::from_state(state, &self.config);
self.history_replace_at(idx, Box::new(cell));
self.autoscroll_if_near_bottom();
// Final assistant content revised; advance Auto Drive now.
self.auto_on_assistant_final();
return;
}
let state =
self.finalize_answer_stream_state(id.as_deref(), &final_source);
let cell = history_cell::AssistantMarkdownCell::from_state(state, &self.config);
self.history_replace_at(idx, Box::new(cell));
self.autoscroll_if_near_bottom();
// Final assistant content revised; advance Auto Drive now.
self.auto_on_assistant_final();
return;
}
}
// Fallback: no prior assistant cell found; insert at stable sequence position.
+18 -1
View File
@@ -25,6 +25,7 @@ use code_ollama::DEFAULT_OSS_MODEL;
use code_protocol::config_types::SandboxMode;
use std::fs::OpenOptions;
use std::path::PathBuf;
use std::sync::OnceLock;
use tracing_appender::non_blocking;
use tracing_subscriber::EnvFilter;
use uuid::Uuid;
@@ -287,7 +288,23 @@ pub struct ExitSummary {
pub session_id: Option<Uuid>,
}
pub const RESUME_COMMAND_NAME: &str = "code";
pub fn resume_command_name() -> &'static str {
static COMMAND: OnceLock<&'static str> = OnceLock::new();
COMMAND.get_or_init(|| {
let arg0 = std::env::args().next();
let invoked = arg0
.as_ref()
.and_then(|value| std::path::Path::new(value).file_name())
.and_then(|name| name.to_str())
.unwrap_or("");
if invoked.eq_ignore_ascii_case("coder") {
"coder"
} else {
"code"
}
})
}
pub async fn run_main(
mut cli: Cli,
+3 -3
View File
@@ -4,7 +4,7 @@ use code_common::CliConfigOverrides;
use code_tui::Cli;
use code_tui::ExitSummary;
use code_tui::run_main;
use code_tui::RESUME_COMMAND_NAME;
use code_tui::resume_command_name;
#[derive(Parser, Debug)]
struct TopCli {
@@ -36,8 +36,8 @@ fn main() -> anyhow::Result<()> {
}
if let Some(session_id) = session_id {
println!(
"To continue this session, run {} resume {}.",
RESUME_COMMAND_NAME,
"To continue this session, run {} resume {}",
resume_command_name(),
session_id
);
}
+86
View File
@@ -0,0 +1,86 @@
#![cfg(test)]
#![allow(clippy::unwrap_used, clippy::expect_used)]
use code_core::protocol::{Event, EventMsg, ReplayHistoryEvent};
use code_protocol::models::{ContentItem, ResponseItem};
use code_tui::test_helpers::{render_chat_widget_to_vt100, ChatWidgetHarness};
fn assistant_cell_count(screen: &str) -> usize {
screen
.lines()
.filter(|line| line.trim_start().starts_with("• "))
.count()
}
fn message(role: &str, text: &str) -> ResponseItem {
let content = match role {
"assistant" => ContentItem::OutputText { text: text.to_string() },
_ => ContentItem::InputText { text: text.to_string() },
};
ResponseItem::Message {
id: None,
role: role.to_string(),
content: vec![content],
}
}
#[test]
fn replay_history_duplicates_short_assistant_messages() {
let mut harness = ChatWidgetHarness::new();
let items = vec![
message("user", "Please summarize the plan."),
message("assistant", "Working."),
message("assistant", "Working. Done."),
];
harness.handle_event(Event {
id: "resume-replay".to_string(),
event_seq: 0,
msg: EventMsg::ReplayHistory(ReplayHistoryEvent {
items,
history_snapshot: None,
}),
order: None,
});
let screen = render_chat_widget_to_vt100(&mut harness, 80, 24);
assert_eq!(
1,
assistant_cell_count(&screen),
"expected a single restored assistant message but saw: {screen}"
);
assert!(screen.contains("Working. Done."));
}
#[test]
fn replay_history_handles_prefixed_revisions() {
let mut harness = ChatWidgetHarness::new();
let items = vec![
message("user", "Please summarize the plan."),
message("assistant", "Working."),
message("assistant", "Update:\nWorking."),
];
harness.handle_event(Event {
id: "resume-replay".to_string(),
event_seq: 0,
msg: EventMsg::ReplayHistory(ReplayHistoryEvent {
items,
history_snapshot: None,
}),
order: None,
});
let screen = render_chat_widget_to_vt100(&mut harness, 80, 24);
assert_eq!(
1,
assistant_cell_count(&screen),
"expected a single restored assistant message but saw: {screen}"
);
assert!(screen.contains("Update:"));
}