File tree Expand file tree Collapse file tree
src/supervisor/agents/acp Expand file tree Collapse file tree Original file line number Diff line number Diff line change @@ -41,6 +41,25 @@ describe("mapAcpSessionUpdate", () => {
4141 expect ( ( second [ 0 ] as { delta : string } ) . delta ) . toBe ( " world" ) ;
4242 } ) ;
4343
44+ it ( "drops [MODE_UPDATE] agent text echoes — mode is chosen in the launcher, not chat" , ( ) => {
45+ // Gemini's ACP server emits `[MODE_UPDATE] <mode>` as a fresh
46+ // agent_message_chunk every time a session starts (or switches) into a
47+ // specific approval mode. The user already picked that mode in the
48+ // launcher UI; replaying it as a chat message on every turn is pure
49+ // noise, so the mapper must drop the chunk before opening an assistant
50+ // item.
51+ const state = createAcpMapperState ( "t-mode" ) ;
52+ const events = mapAcpSessionUpdate (
53+ note ( {
54+ sessionUpdate : "agent_message_chunk" ,
55+ content : { type : "text" , text : "[MODE_UPDATE] yolo" } ,
56+ } ) ,
57+ state ,
58+ ) ;
59+ expect ( events ) . toEqual ( [ ] ) ;
60+ expect ( state . openAssistantItemId ) . toBeUndefined ( ) ;
61+ } ) ;
62+
4463 it ( "drops user_message_chunk echoes — supervisor/renderer own the user_message item" , ( ) => {
4564 // Some ACP servers (Copilot) echo the user's prompt back as
4665 // `user_message_chunk` updates after we send `session/prompt`. The
Original file line number Diff line number Diff line change @@ -169,6 +169,19 @@ export function mapAcpSessionUpdate(
169169
170170 switch ( update . sessionUpdate ) {
171171 case "agent_message_chunk" : {
172+ const content = ( update as { content ?: ContentBlock } ) . content ;
173+ // Gemini echoes `[MODE_UPDATE] <mode>` as an agent text chunk whenever the
174+ // session is launched (or switched) into a specific approval mode. The
175+ // user already chose the mode in the launcher; surfacing the echo as
176+ // chat noise on every turn is just clutter. Drop it before we open an
177+ // assistant item so the chat stays clean.
178+ if (
179+ ! state . openAssistantItemId &&
180+ content ?. type === "text" &&
181+ / ^ \[ M O D E _ U P D A T E \] / . test ( content . text )
182+ ) {
183+ break ;
184+ }
172185 // Open an assistant item on first chunk; emit deltas thereafter.
173186 if ( ! state . openAssistantItemId ) {
174187 // Close any prior reasoning/user items — assistant is starting fresh.
@@ -181,7 +194,6 @@ export function mapAcpSessionUpdate(
181194 itemType : "assistant_message" ,
182195 } ) ;
183196 }
184- const content = ( update as { content ?: ContentBlock } ) . content ;
185197 if ( content ) {
186198 if ( content . type === "text" ) {
187199 events . push ( {
You can’t perform that action at this time.
0 commit comments