@@ -134,6 +134,220 @@ describe('SystemMessage — background notification label', () => {
134134 } ) ;
135135} ) ;
136136
137+ describe ( 'SystemMessage — background notification i18n body' , ( ) => {
138+ it ( 'renders shell notifications with structured command via i18n' , ( ) => {
139+ const container = render (
140+ < SystemMessage
141+ content = 'Background shell "npm test" completed.'
142+ variant = "info"
143+ source = "background_notification"
144+ data = { {
145+ status : 'completed' ,
146+ kind : 'shell' ,
147+ commandLabel : 'npm test' ,
148+ } }
149+ /> ,
150+ 'zh-CN' ,
151+ ) ;
152+
153+ expect ( container . textContent ) . toContain ( '后台 Shell 已完成:npm test' ) ;
154+ expect ( container . textContent ) . not . toContain (
155+ 'Background shell "npm test" completed.' ,
156+ ) ;
157+ } ) ;
158+
159+ it ( 'renders agent notifications with structured description via i18n' , ( ) => {
160+ const container = render (
161+ < SystemMessage
162+ content = 'Background agent "worker" completed.'
163+ variant = "info"
164+ source = "background_notification"
165+ data = { {
166+ status : 'completed' ,
167+ kind : 'agent' ,
168+ description : 'worker' ,
169+ } }
170+ /> ,
171+ 'zh-CN' ,
172+ ) ;
173+
174+ expect ( container . textContent ) . toContain ( '后台智能体已完成:worker' ) ;
175+ } ) ;
176+
177+ it ( 'renders monitor notifications with the event count in English' , ( ) => {
178+ const container = render (
179+ < SystemMessage
180+ content = "raw daemon text"
181+ variant = "info"
182+ source = "background_notification"
183+ data = { {
184+ status : 'completed' ,
185+ kind : 'monitor' ,
186+ description : 'logs' ,
187+ eventCount : 5 ,
188+ } }
189+ /> ,
190+ ) ;
191+
192+ expect ( container . textContent ) . toContain (
193+ 'Monitor "logs" completed. (5 events)' ,
194+ ) ;
195+ expect ( container . textContent ) . not . toContain ( 'raw daemon text' ) ;
196+ } ) ;
197+
198+ it ( 'renders the dropped-lines clause when throttling dropped output' , ( ) => {
199+ const container = render (
200+ < SystemMessage
201+ content = "raw daemon text"
202+ variant = "info"
203+ source = "background_notification"
204+ data = { {
205+ status : 'completed' ,
206+ kind : 'monitor' ,
207+ description : 'logs' ,
208+ eventCount : 5 ,
209+ droppedLines : 2 ,
210+ } }
211+ /> ,
212+ ) ;
213+
214+ expect ( container . textContent ) . toContain (
215+ 'Monitor "logs" completed. (5 events, 2 lines dropped due to throttling)' ,
216+ ) ;
217+ } ) ;
218+
219+ it ( 'renders the dropped-lines clause in zh-CN when throttling dropped output' , ( ) => {
220+ const container = render (
221+ < SystemMessage
222+ content = "raw daemon text"
223+ variant = "info"
224+ source = "background_notification"
225+ data = { {
226+ status : 'completed' ,
227+ kind : 'monitor' ,
228+ description : 'logs' ,
229+ eventCount : 5 ,
230+ droppedLines : 2 ,
231+ } }
232+ /> ,
233+ 'zh-CN' ,
234+ ) ;
235+
236+ expect ( container . textContent ) . toContain (
237+ '监控器已完成(5 个事件,因限流丢弃 2 行):logs' ,
238+ ) ;
239+ } ) ;
240+
241+ it ( 'renders failed monitor notifications with the event count in zh-CN' , ( ) => {
242+ const container = render (
243+ < SystemMessage
244+ content = "raw daemon text"
245+ variant = "info"
246+ source = "background_notification"
247+ data = { {
248+ status : 'failed' ,
249+ kind : 'monitor' ,
250+ description : 'logs' ,
251+ eventCount : 42 ,
252+ } }
253+ /> ,
254+ 'zh-CN' ,
255+ ) ;
256+
257+ expect ( container . textContent ) . toContain (
258+ '监控器执行失败(42 个事件):logs' ,
259+ ) ;
260+ } ) ;
261+
262+ it ( 'renders cancelled shell notifications via i18n' , ( ) => {
263+ const container = render (
264+ < SystemMessage
265+ content = "raw daemon text"
266+ variant = "info"
267+ source = "background_notification"
268+ data = { {
269+ status : 'cancelled' ,
270+ kind : 'shell' ,
271+ commandLabel : 'npm test' ,
272+ } }
273+ /> ,
274+ 'zh-CN' ,
275+ ) ;
276+
277+ expect ( container . textContent ) . toContain ( '后台 Shell 已取消:npm test' ) ;
278+ } ) ;
279+
280+ it ( 'renders failed agent notifications via i18n in English' , ( ) => {
281+ const container = render (
282+ < SystemMessage
283+ content = "raw daemon text"
284+ variant = "info"
285+ source = "background_notification"
286+ data = { { status : 'failed' , kind : 'agent' , description : 'worker' } }
287+ /> ,
288+ ) ;
289+
290+ expect ( container . textContent ) . toContain (
291+ 'Background agent "worker" failed.' ,
292+ ) ;
293+ } ) ;
294+
295+ it ( 'falls back to raw content for unknown terminal statuses' , ( ) => {
296+ const container = render (
297+ < SystemMessage
298+ content = 'Monitor "logs" timed out.'
299+ variant = "info"
300+ source = "background_notification"
301+ data = { { status : 'timeout' , kind : 'monitor' , description : 'logs' } }
302+ /> ,
303+ ) ;
304+
305+ expect ( container . textContent ) . toContain ( 'Monitor "logs" timed out.' ) ;
306+ expect ( container . textContent ) . not . toContain ( 'notification.monitor.timeout' ) ;
307+ } ) ;
308+
309+ it ( 'falls back to raw content when structured fields are absent' , ( ) => {
310+ const container = render (
311+ < SystemMessage
312+ content = 'Background shell "npm test" completed.'
313+ variant = "info"
314+ source = "background_notification"
315+ data = { { status : 'completed' , kind : 'shell' } }
316+ /> ,
317+ ) ;
318+
319+ expect ( container . textContent ) . toContain (
320+ 'Background shell "npm test" completed.' ,
321+ ) ;
322+ } ) ;
323+
324+ it ( 'renders the fallback through Markdown so session links stay clickable' , ( ) => {
325+ const seen : unknown [ ] = [ ] ;
326+ const handler = ( e : Event ) => seen . push ( ( e as CustomEvent ) . detail ) ;
327+ window . addEventListener ( 'qwen:open-session' , handler ) ;
328+ const container = render (
329+ < SystemMessage
330+ content = "Sub-session [🧵 abc12345](qwen-session://abc12345-full) completed."
331+ variant = "info"
332+ source = "background_notification"
333+ data = { { status : 'completed' , kind : 'agent' } }
334+ /> ,
335+ ) ;
336+
337+ const link = container . querySelector ( 'a[role="button"]' ) ;
338+ expect ( link ) . not . toBeNull ( ) ;
339+ expect ( link ?. textContent ) . toBe ( '🧵 abc12345' ) ;
340+ expect ( container . textContent ) . not . toContain ( '](' ) ;
341+ act ( ( ) => {
342+ link ?. dispatchEvent (
343+ new MouseEvent ( 'click' , { bubbles : true , cancelable : true } ) ,
344+ ) ;
345+ } ) ;
346+ expect ( seen ) . toEqual ( [ 'abc12345-full' ] ) ;
347+ window . removeEventListener ( 'qwen:open-session' , handler ) ;
348+ } ) ;
349+ } ) ;
350+
137351describe ( 'SystemMessage — goal status activation' , ( ) => {
138352 const content = serializeGoalStatusMessage ( {
139353 kind : 'set' ,
0 commit comments