11import { API } from "api/api" ;
22import { getErrorDetail , getErrorMessage } from "api/errors" ;
33import { template as templateQueryOptions } from "api/queries/templates" ;
4- import type { Workspace , WorkspaceStatus } from "api/typesGenerated" ;
4+ import type {
5+ Workspace ,
6+ WorkspaceAgent ,
7+ WorkspaceStatus ,
8+ } from "api/typesGenerated" ;
59import isChromatic from "chromatic/isChromatic" ;
610import { Button } from "components/Button/Button" ;
711import { Loader } from "components/Loader/Loader" ;
812import { Margins } from "components/Margins/Margins" ;
913import { ScrollArea } from "components/ScrollArea/ScrollArea" ;
1014import { useWorkspaceBuildLogs } from "hooks/useWorkspaceBuildLogs" ;
1115import { ArrowLeftIcon , RotateCcwIcon } from "lucide-react" ;
16+ import { AgentLogs } from "modules/resources/AgentLogs/AgentLogs" ;
17+ import { useAgentLogs } from "modules/resources/useAgentLogs" ;
1218import { AI_PROMPT_PARAMETER_NAME , type Task } from "modules/tasks/tasks" ;
1319import { WorkspaceBuildLogs } from "modules/workspaces/WorkspaceBuildLogs/WorkspaceBuildLogs" ;
14- import { type FC , type ReactNode , useEffect , useRef } from "react" ;
20+ import { type FC , type ReactNode , useLayoutEffect , useRef } from "react" ;
1521import { Helmet } from "react-helmet-async" ;
1622import { useQuery } from "react-query" ;
1723import { Panel , PanelGroup , PanelResizeHandle } from "react-resizable-panels" ;
1824import { Link as RouterLink , useParams } from "react-router" ;
25+ import type { FixedSizeList } from "react-window" ;
1926import { pageTitle } from "utils/page" ;
2027import {
2128getActiveTransitionStats ,
@@ -87,6 +94,7 @@ const TaskPage = () => {
8794}
8895
8996let content :ReactNode = null ;
97+ const agent = selectAgent ( task ) ;
9098
9199if ( waitingStatuses . includes ( task . workspace . latest_build . status ) ) {
92100content = < TaskBuildingWorkspace task = { task } /> ;
@@ -132,6 +140,8 @@ const TaskPage = () => {
132140</ div >
133141</ Margins >
134142) ;
143+ } else if ( agent && [ "created" , "starting" ] . includes ( agent . lifecycle_state ) ) {
144+ content = < TaskStartingAgent agent = { agent } /> ;
135145} else {
136146content = (
137147< PanelGroup autoSaveId = "task" direction = "horizontal" >
@@ -182,7 +192,7 @@ const TaskBuildingWorkspace: FC<TaskBuildingWorkspaceProps> = ({ task }) => {
182192
183193const scrollAreaRef = useRef < HTMLDivElement > ( null ) ;
184194// biome-ignore lint/correctness/useExhaustiveDependencies: this effect should run when build logs change
185- useEffect ( ( ) => {
195+ useLayoutEffect ( ( ) => {
186196if ( isChromatic ( ) ) {
187197return ;
188198}
@@ -196,34 +206,86 @@ const TaskBuildingWorkspace: FC<TaskBuildingWorkspaceProps> = ({ task }) => {
196206} , [ buildLogs ] ) ;
197207
198208return (
199- < section className = "w-full h-full flex justify-center items-center p-6 overflow-y-auto" >
200- < div className = "flex flex-col gap-6 items-center w-full" >
201- < header className = "flex flex-col items-center text-center" >
202- < h3 className = "m-0 font-medium text-content-primary text-xl" >
203- Starting your workspace
204- </ h3 >
205- < div className = "text-content-secondary" >
206- Your task will be running in a few moments
209+ < section className = "p-16 overflow-y-auto" >
210+ < div className = "flex justify-center items-center w-full" >
211+ < div className = "flex flex-col gap-6 items-center w-full" >
212+ < header className = "flex flex-col items-center text-center" >
213+ < h3 className = "m-0 font-medium text-content-primary text-xl" >
214+ Starting your workspace
215+ </ h3 >
216+ < p className = "text-content-secondary m-0" >
217+ Your task will be running in a few moments
218+ </ p >
219+ </ header >
220+
221+ < div className = "w-full max-w-screen-lg flex flex-col gap-4 overflow-hidden" >
222+ < WorkspaceBuildProgress
223+ workspace = { task . workspace }
224+ transitionStats = { transitionStats }
225+ variant = "task"
226+ />
227+
228+ < ScrollArea
229+ ref = { scrollAreaRef }
230+ className = "h-96 border border-solid border-border rounded-lg"
231+ >
232+ < WorkspaceBuildLogs
233+ sticky
234+ className = "border-0 rounded-none"
235+ logs = { buildLogs ?? [ ] }
236+ />
237+ </ ScrollArea >
207238</ div >
208- </ header >
239+ </ div >
240+ </ div >
241+ </ section >
242+ ) ;
243+ } ;
244+
245+ type TaskStartingAgentProps = {
246+ agent :WorkspaceAgent ;
247+ } ;
209248
210- < div className = "w-full max-w-screen-lg flex flex-col gap-4 overflow-hidden" >
211- < WorkspaceBuildProgress
212- workspace = { task . workspace }
213- transitionStats = { transitionStats }
214- variant = "task"
215- />
249+ const TaskStartingAgent :FC < TaskStartingAgentProps > = ( { agent} ) => {
250+ const logs = useAgentLogs ( agent , true ) ;
251+ const listRef = useRef < FixedSizeList > ( null ) ;
216252
217- < ScrollArea
218- ref = { scrollAreaRef }
219- className = "h-96 border border-solid border-border rounded-lg"
220- >
221- < WorkspaceBuildLogs
222- sticky
223- className = "border-0 rounded-none"
224- logs = { buildLogs ?? [ ] }
225- />
226- </ ScrollArea >
253+ useLayoutEffect ( ( ) => {
254+ if ( listRef . current ) {
255+ listRef . current . scrollToItem ( logs . length - 1 , "end" ) ;
256+ }
257+ } , [ logs ] ) ;
258+
259+ return (
260+ < section className = "p-16 overflow-y-auto" >
261+ < div className = "flex justify-center items-center w-full" >
262+ < div className = "flex flex-col gap-8 items-center w-full" >
263+ < header className = "flex flex-col items-center text-center" >
264+ < h3 className = "m-0 font-medium text-content-primary text-xl" >
265+ Running startup scripts
266+ </ h3 >
267+ < p className = "text-content-secondary m-0" >
268+ Your task will be running in a few moments
269+ </ p >
270+ </ header >
271+
272+ < div className = "w-full max-w-screen-lg flex flex-col gap-4 overflow-hidden" >
273+ < div className = "h-96 border border-solid border-border rounded-lg" >
274+ < AgentLogs
275+ logs = { logs . map ( ( l ) => ( {
276+ id :l . id ,
277+ level :l . level ,
278+ output :l . output ,
279+ sourceId :l . source_id ,
280+ time :l . created_at ,
281+ } ) ) }
282+ sources = { agent . log_sources }
283+ height = { 96 * 4 }
284+ width = "100%"
285+ ref = { listRef }
286+ />
287+ </ div >
288+ </ div >
227289</ div >
228290</ div >
229291</ section >
@@ -265,3 +327,11 @@ export const data = {
265327} satisfies Task ;
266328} ,
267329} ;
330+
331+ function selectAgent ( task :Task ) {
332+ const agents = task . workspace . latest_build . resources
333+ . flatMap ( ( r ) => r . agents )
334+ . filter ( ( a ) => ! ! a ) ;
335+
336+ return agents . at ( 0 ) ;
337+ }