11import { describe , expect , spyOn , test } from "bun:test" ;
22import { mailboxMailWakeLine } from "../subagent/mailbox-mail-drive.js" ;
33import { defined } from "../../tests/helpers/defined.js" ;
4+ import { OPERATOR_ORIGINATED_FLAG } from "../agent/message-provenance.js" ;
5+ import { buildShellBackgroundMessage } from "../session/runtime-assembly.js" ;
46import {
57 FIXTURE_BUSY_SESSION ,
68 attachSessionBridge ,
@@ -18,15 +20,26 @@ import { badgeCount } from "./session-queue";
1820import { LIVE_ACTIVITY_WORDS } from "./session-chrome" ;
1921
2022describe ( "mapReactorLike" , ( ) => {
21- test ( "message.received → user" , ( ) => {
23+ test ( "operator-originated message.received → user" , ( ) => {
2224 expect (
2325 mapReactorLike ( {
2426 type : "message.received" ,
25- data : { message : { content : "hi" } } ,
27+ data : {
28+ message : { content : "hi" , flags : [ OPERATOR_ORIGINATED_FLAG ] } ,
29+ } ,
2630 } ) ,
2731 ) . toEqual ( [ { type : "user" , text : "hi" } ] ) ;
2832 } ) ;
2933
34+ test ( "system-originated message.received → system" , ( ) => {
35+ expect (
36+ mapReactorLike ( {
37+ type : "message.received" ,
38+ data : { message : { content : "hi" } } ,
39+ } ) ,
40+ ) . toEqual ( [ { type : "system" , text : "hi" } ] ) ;
41+ } ) ;
42+
3043 test ( "mapReactorLike tool.done types" , ( ) => {
3144 const mapped = mapReactorLike ( {
3245 type : "tool.done" ,
@@ -48,6 +61,44 @@ describe("mapReactorLike", () => {
4861} ) ;
4962
5063describe ( "attachSessionBridge" , ( ) => {
64+ test ( "background shell exit paints as a system row" , async ( ) => {
65+ await withTestRenderer (
66+ async ( h ) => {
67+ const shell = createAppShell ( h . renderer , {
68+ terminal : { columns : 80 , rows : 24 } ,
69+ wireKeys : false ,
70+ run : "idle" ,
71+ } ) ;
72+ const bridge = attachSessionBridge ( shell , createRecordingPort ( ) ) ;
73+ try {
74+ const message = buildShellBackgroundMessage ( {
75+ id : "sh_1" ,
76+ command : "sleep 0.1" ,
77+ exitCode : 0 ,
78+ timedOut : false ,
79+ output : "done" ,
80+ } ) ;
81+ bridge . handle ( {
82+ type : "message.received" ,
83+ data : { message } ,
84+ } ) ;
85+ expect ( shell . streamLog . filter ( ( r ) => r . role === "user" ) ) . toHaveLength (
86+ 0 ,
87+ ) ;
88+ expect (
89+ shell . streamLog . filter (
90+ ( r ) => r . role === "system" && r . text === message . content ,
91+ ) ,
92+ ) . toHaveLength ( 1 ) ;
93+ } finally {
94+ bridge . dispose ( ) ;
95+ shell . dispose ( ) ;
96+ }
97+ } ,
98+ { width : 80 , height : 24 } ,
99+ ) ;
100+ } ) ;
101+
51102 test ( "fixture paints user / assistant / tool through shell" , async ( ) => {
52103 await withTestRenderer (
53104 async ( h ) => {
@@ -1550,7 +1601,7 @@ describe("fleet-dry open-task drive (CL-7540)", () => {
15501601 bridge . handle ( { type : "inference.done" , data : { } } ) ;
15511602 }
15521603
1553- test ( "dry+open: fleet-0 settle drives once, keeps the run busy, and swallows the prompt" , async ( ) => {
1604+ test ( "dry+open: fleet-0 settle drives once, keeps the run busy, and paints the prompt as system " , async ( ) => {
15541605 await withTestRenderer (
15551606 async ( h ) => {
15561607 const shell = createAppShell ( h . renderer , {
@@ -1588,6 +1639,11 @@ describe("fleet-dry open-task drive (CL-7540)", () => {
15881639 expect ( shell . streamLog . filter ( ( r ) => r . role === "user" ) . length ) . toBe (
15891640 userRowsBefore ,
15901641 ) ;
1642+ expect (
1643+ shell . streamLog . filter (
1644+ ( r ) => r . role === "system" && r . text === prompt ,
1645+ ) ,
1646+ ) . toHaveLength ( 1 ) ;
15911647 settleToollessTurn ( bridge ) ;
15921648 expect ( drives ) . toBe ( 1 ) ;
15931649 } finally {
@@ -1967,7 +2023,7 @@ describe("fleet-dry open-task drive (CL-7540)", () => {
19672023 ) ;
19682024 } ) ;
19692025
1970- test ( "occupancy send abort drops the continuation echo so a later matching inbound paints " , async ( ) => {
2026+ test ( "occupancy send abort does not swallow a later matching inbound" , async ( ) => {
19712027 await withTestRenderer (
19722028 async ( h ) => {
19732029 const shell = createAppShell ( h . renderer , {
@@ -2002,8 +2058,13 @@ describe("fleet-dry open-task drive (CL-7540)", () => {
20022058 data : { message : { content : occupancy } } ,
20032059 } ) ;
20042060 expect ( shell . streamLog . filter ( ( r ) => r . role === "user" ) . length ) . toBe (
2005- userRowsAfterSubmit + 1 ,
2061+ userRowsAfterSubmit ,
20062062 ) ;
2063+ expect (
2064+ shell . streamLog . filter (
2065+ ( r ) => r . role === "system" && r . text === occupancy ,
2066+ ) ,
2067+ ) . toHaveLength ( 1 ) ;
20072068 } finally {
20082069 bridge . dispose ( ) ;
20092070 shell . dispose ( ) ;
0 commit comments