fix(chat): preserve Gemini CLI history sessions on reopen

When reopening a Gemini CLI history session, session/load fails with
"Authentication required" and the fallback session/new overwrites the
DB external_id with a new session ID that has no corresponding file,
causing all historical messages to disappear.

- Skip session/new when session/load returns "Authentication required"
- Add Gemini to the parser fallback so stale external_ids recover via
  folder_path + started_at matching
- Guard externalIdSavedRef for existing conversations to prevent
  session/new from overwriting the persisted external_id
- Only update conversation status on disconnect when user has sent a
  message, avoiding spurious "completed" flips on pure history views

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
xintaofei
2026-04-10 22:32:56 +08:00
parent 3dfa913584
commit e4eb7f67eb
3 changed files with 21 additions and 7 deletions

View File

@@ -844,6 +844,12 @@ async fn run_connection(
// Only emit a visible error for unexpected failures;
// "Method not found" is expected for agents that don't
// support session resume (e.g. Cline).
// "Authentication required" is expected for agents whose
// credentials have expired (e.g. Gemini CLI) — skip
// session/new too since it will also fail.
if err_str.contains("Authentication required") {
return Ok(());
}
if !err_str.contains("Method not found") {
crate::web::event_bridge::emit_event(
&emitter_clone,

View File

@@ -289,11 +289,12 @@ pub async fn get_folder_conversation_core(
match parser.get_conversation(&eid) {
Ok(d) => Ok((d.turns, d.session_stats, None)),
Err(crate::parsers::ParseError::ConversationNotFound(_)) => {
// For agents like OpenClaw and Cline, the external_id is an
// ACP session UUID that doesn't correspond to any local file.
// Fall back to matching by folder_path and started_at from
// the parsed conversation list.
if at == AgentType::OpenClaw || at == AgentType::Cline {
// The external_id may no longer match any local file —
// e.g. an ACP session UUID (OpenClaw, Cline) or a stale
// ID after session/new fallback overwrote the original
// (Gemini CLI). Fall back to matching by folder_path
// and started_at from the parsed conversation list.
if matches!(at, AgentType::OpenClaw | AgentType::Cline | AgentType::Gemini) {
if let Ok(all) = parser.list_conversations() {
// Filter by folder_path first, then find the closest
// started_at match within 300 seconds of db_created_at.