Skip to content

fix(acp): journal sent prompts so an interrupted chat cannot reopen blank - #539

Open
Adam-Dalloul wants to merge 1 commit into
xintaofei:mainfrom
Adam-Dalloul:fix/persist-sent-prompt
Open

fix(acp): journal sent prompts so an interrupted chat cannot reopen blank#539
Adam-Dalloul wants to merge 1 commit into
xintaofei:mainfrom
Adam-Dalloul:fix/persist-sent-prompt

Conversation

@Adam-Dalloul

@Adam-Dalloul Adam-Dalloul commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

Problem

Codeg's database stores no message content: one metadata row per conversation, with the transcript re-read from the agent CLI's own session store on every open. That is a good design, but it has one hole: the just-sent user prompt exists only in React state and in SessionState until the agent flushes its own store.

So when the agent never gets that far, the message is gone permanently. Concrete case from a live machine: the Grok CLI accepted a prompt, finished MCP init, and then wedged before turn_started, so it never created updates.jsonl. After closing and reopening codeg, the conversation opened completely blank: GrokParser returned ConversationNotFound, and get_folder_conversation_core maps that to an empty turn list with no error. The user's own typed message was lost, in a titled empty chat. The same shape applies to any built-in whose CLI crashes on the first turn, blocks before writing, or whose store was pruned.

record_prompt already exists and already writes prompts durably before dispatch, but transcript_dir_for gates it to custom agents, so for every built-in it is a no-op.

Change

Add a prompt journal for built-in agents, reusing the transcript store end to end:

  • record_prompt_journal (acp/connection.rs) records the outgoing session/prompt content blocks for built-in agents, gated by prompt_journal_dir_for, the exact inverse of transcript_dir_for: every agent type lands in exactly one recorder, so no prompt is stored twice and none is stored nowhere. Same durable bounded-wait shape as record_prompt, written through the existing background writer via record_entry_in.
  • The journal lives under its own root, codeg_prompt_journal_root() (~/.codeg/acp-prompts, honoring CODEG_HOME / CODEG_DATA_DIR), deliberately a sibling of acp-transcripts rather than inside it: the replay gate (has_recorded_history), continuation chains, and the custom-agent conversation listing all walk the transcript root and must not start seeing built-in files.
  • Read side: get_folder_conversation_core falls back to the journal only when the agent's own store yielded nothing (parser ConversationNotFound, or a parse that produced zero turns). Because the journal holds prompts only, never agent output, it can never disagree with or duplicate a history the agent actually has. The fallback reuses AcpNativeParser::new_in pointed at the journal root, so there is no new parsing code.

The interrupted chat now reopens showing the user's sent message(s) instead of rendering blank, and the sidebar message_count recompute counts them.

Notes for review

  • This deliberately stores prompt text under codeg's data dir for built-ins, which turn_timings explicitly avoided (it stores prompt_sha for that reason). I think the trade is right here: custom agents' prompts are already stored verbatim in acp-transcripts, the journal is one line per turn, and the alternative is silently losing the user's own message. If you'd rather have this behind a setting, happy to add one.
  • Journal files follow conversation retention as transcripts do today: soft-deleted conversations keep their files, nothing new is deleted.
  • Not addressed here (separate concern, can follow up): conversations stuck at in_progress when the app closes mid-turn, since neither CloseRequested nor ExitRequested writes any conversation state.

Tests

  • prompt_journal_gate_covers_built_ins_and_skips_custom (connection.rs): the two gates are complementary for built-ins and custom agents.
  • prompt_journal_turns_reads_recorded_prompts_as_user_turns and prompt_journal_turns_is_empty_for_an_unjournaled_session (conversations.rs): journal fixture written through append_line_in into a temp root, read back through the fallback.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant