Pure GUI adapter encoder for the resident agent-chat transcript stream
(gui_agent_transcript, 0x86).
Owns the gui_agent_transcript (0x86) resident framing so a frontend can
scroll the session from local data without a BEAM round-trip (#2654),
decoupled from the small gui_agent_chat (0x78) chrome model owned by
Minga.Frontend.Adapter.GUI.AgentChatEncoder. The render model owns resident
selection. This adapter encodes its resident_messages exactly and never
selects a suffix, marks content truncated, or drops entries. It uses
Minga.Frontend.Adapter.GUI.AgentChatMessageCodec for the shared per-message
bodies.
Adopts the #2652 resident-store lifecycle: a full store replace only on genuine
structural change (transcript_epoch flip or a non-prefix divergence such as
compaction), and id-keyed append/upsert deltas within an epoch for streaming
growth, the in-place last-message patch, and upstream resident eviction. The
append delta carries trim_front, the number of leading messages removed by
the model. State for the delta base lives in a
Minga.Frontend.Adapter.GUI.AgentTranscriptSentState inside
Minga.Frontend.Adapter.GUI.Caches, never on the BEAM editor state.
Wire payload (after the len32 opcode + u32 length framing)
version:u8 = 1
mode:u8 # 0 = full_replace, 1 = append
epoch:u32
truncated:u8 # model-owned resident suffix omitted older messages
# full_replace (mode 0):
count:u32
# append (mode 1):
trim_front:u32 # leading messages evicted from the store front this delta
base_count:u32 # unchanged leading messages of the remainder to keep
count:u32
# both modes:
count * [ id:u32, body_len:u32, body:bytes ]body is the shared AgentChatMessageCodec message body.
Decoder contract
full_replace: clear the store, then set it to thecountentries in order.append: droptrim_frontmessages from the front of the store; of the remainder, keep the firstbase_countmessages unchanged; then upsert each of thecountentries byid(a new message appends, a matchingidpatches the streaming last message in place).base_countis the count of unchanged leading messages of the remainder — keep[0, base_count)and upsert entries from there. It is not the client's resident count; it is strictly<= the remainder lengthand is normally less than the resident count on every streaming patch. Desync (drop and await the nextfull_replace) is only warranted when the store's resident count is less thantrim_front + base_count, i.e. the delta cannot be applied against what the store holds.
Summary
Functions
@spec encode( Minga.RenderModel.UI.AgentChat.t(), Minga.Frontend.Adapter.GUI.Caches.t() ) :: {binary() | nil, Minga.Frontend.Adapter.GUI.Caches.t()}