@@ -10,9 +10,9 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
1010import { getApiUrl } from "@/lib/config" ;
1111import { formatDistanceToNow } from "date-fns" ;
1212import { Table , TableBody , TableCell , TableHead , TableHeader , TableRow } from "@/components/ui/table" ;
13- import { RFEWorkflow , WorkflowPhase } from "@/types/agentic-session" ;
13+ import { AgenticSession , CreateAgenticSessionRequest , RFEWorkflow , WorkflowPhase } from "@/types/agentic-session" ;
1414import { WORKFLOW_PHASE_LABELS } from "@/lib/agents" ;
15- import { ArrowLeft , Edit , Play , Loader2 , RefreshCw , FolderTree } from "lucide-react" ;
15+ import { ArrowLeft , Play , Loader2 , FolderTree , Plus } from "lucide-react" ;
1616import { FileTree , type FileTreeNode } from "@/components/file-tree" ;
1717
1818function phaseProgress ( w : RFEWorkflow , phase : WorkflowPhase ) {
@@ -32,7 +32,7 @@ export default function ProjectRFEDetailPage() {
3232 const [ error , setError ] = useState < string | null > ( null ) ;
3333 const [ advancing , setAdvancing ] = useState ( false ) ;
3434 const [ startingPhase , setStartingPhase ] = useState < WorkflowPhase | null > ( null ) ;
35- const [ rfeSessions , setRfeSessions ] = useState < Array < { name : string ; phase ?: string ; labels ?: Record < string , unknown > } > > ( [ ] ) ;
35+ const [ rfeSessions , setRfeSessions ] = useState < AgenticSession [ ] > ( [ ] ) ;
3636 const [ sessionsLoading , setSessionsLoading ] = useState ( false ) ;
3737 const [ hasWorkspace , setHasWorkspace ] = useState < boolean | null > ( null ) ;
3838 const [ wsTree , setWsTree ] = useState < FileTreeNode [ ] > ( [ ] ) ;
@@ -119,6 +119,18 @@ export default function ProjectRFEDetailPage() {
119119 } ) ( ) ;
120120 } , [ project , id , listWsPath , probeWorkspaceAndPhase ] ) ;
121121
122+ const updateChildrenByPath = useCallback ( ( nodes : FileTreeNode [ ] , targetPath : string , children : FileTreeNode [ ] ) : FileTreeNode [ ] => {
123+ return nodes . map ( ( n ) => {
124+ if ( n . path === targetPath ) {
125+ return { ...n , children } ;
126+ }
127+ if ( n . type === "folder" && n . children && n . children . length > 0 ) {
128+ return { ...n , children : updateChildrenByPath ( n . children , targetPath , children ) } ;
129+ }
130+ return n ;
131+ } ) ;
132+ } , [ ] ) ;
133+
122134 const onWsToggle = useCallback ( async ( node : FileTreeNode ) => {
123135 if ( node . type !== "folder" ) return ;
124136 const items = await listWsPath ( node . path ) ;
@@ -129,8 +141,8 @@ export default function ProjectRFEDetailPage() {
129141 expanded : false ,
130142 sizeKb : typeof it . size === "number" ? it . size / 1024 : undefined ,
131143 } ) ) ;
132- setWsTree ( prev => prev . map ( n => n . path === node . path ? { ... n , children } : n ) ) ;
133- } , [ listWsPath ] ) ;
144+ setWsTree ( prev => updateChildrenByPath ( prev , node . path , children ) ) ;
145+ } , [ listWsPath , updateChildrenByPath ] ) ;
134146
135147 const onWsSelect = useCallback ( async ( node : FileTreeNode ) => {
136148 if ( node . type !== "file" ) return ;
@@ -204,11 +216,6 @@ export default function ProjectRFEDetailPage() {
204216 < p className = "text-muted-foreground mt-1" > { workflow . description } </ p >
205217 </ div >
206218 </ div >
207- < div className = "flex gap-2" >
208- < Link href = { `/projects/${ encodeURIComponent ( project ) } /rfe/${ encodeURIComponent ( id ) } /edit` } >
209- < Button variant = "outline" size = "sm" > < Edit className = "mr-2 h-4 w-4" /> Edit</ Button >
210- </ Link >
211- </ div >
212219 </ div >
213220
214221 < div className = "grid gap-6 md:grid-cols-3" >
@@ -286,29 +293,37 @@ export default function ProjectRFEDetailPage() {
286293 return "specs/tasks.md" ;
287294 } ) ( ) ;
288295 const exists = phase === "specify" ? specExists : phase === "plan" ? planExists : tasksExists ;
289- const sessionForPhase = rfeSessions . find ( s => ( s . labels as any ) ?. [ "rfe-phase" ] === phase ) ;
290- const running = ( sessionForPhase ?. phase || "" ) . toLowerCase ( ) === "running" ;
291- const completed = ( sessionForPhase ?. phase || "" ) . toLowerCase ( ) === "completed" ;
296+ const sessionForPhase = rfeSessions . find ( s => ( s . metadata . labels ) ?. [ "rfe-phase" ] === phase ) ;
297+
292298 const prerequisitesMet = phase === "specify" ? true : phase === "plan" ? specExists : ( specExists && planExists ) ;
299+ const sessionDisplay = ( ( sessionForPhase as any ) ?. spec ?. displayName ) || sessionForPhase ?. metadata . name ;
293300 return (
294301 < div key = { phase } className = "p-4 rounded-lg border flex items-center justify-between" >
295- < div className = "flex items-center gap-3" >
296- < Badge variant = "outline" > { WORKFLOW_PHASE_LABELS [ phase ] } </ Badge >
297- < span className = "text-sm text-muted-foreground" > { expected } </ span >
302+ < div className = "flex flex-col gap-1" >
303+ < div className = "flex items-center gap-3" >
304+ < Badge variant = "outline" > { WORKFLOW_PHASE_LABELS [ phase ] } </ Badge >
305+ < span className = "text-sm text-muted-foreground" > { expected } </ span >
306+ </ div >
307+ { sessionForPhase && (
308+ < div className = "flex items-center gap-2" >
309+ < Link href = { `/projects/${ encodeURIComponent ( project ) } /sessions/${ encodeURIComponent ( sessionForPhase . metadata . name ) } ` } >
310+ < Button variant = "link" size = "sm" className = "px-0 h-auto" > { sessionDisplay } </ Button >
311+ </ Link >
312+ { sessionForPhase ?. status ?. phase && < Badge variant = "outline" > { sessionForPhase . status . phase } </ Badge > }
313+ </ div >
314+ ) }
298315 </ div >
299316 < div className = "flex items-center gap-3" >
300317 < Badge variant = { exists ? "outline" : "secondary" } > { exists ? "Exists" : ( prerequisitesMet ? "Missing" : "Blocked" ) } </ Badge >
301- { running && < Badge variant = "outline" > Running</ Badge > }
302- { completed && < Badge variant = "outline" > Completed</ Badge > }
303- { ! exists && ! running && (
318+ { ! exists && sessionForPhase ?. status ?. phase !== "Running" && (
304319 < Button size = "sm" onClick = { async ( ) => {
305320 try {
306321 setStartingPhase ( phase ) ;
307- const payload = {
322+ const payload : CreateAgenticSessionRequest = {
308323 prompt : `/${ phase } ${ workflow . description } ` ,
309324 displayName : `${ workflow . title } - ${ phase } ` ,
310- interactive : false ,
311- sharedWorkspace : workflowWorkspace ,
325+ interactive : false ,
326+ workspacePath : workflowWorkspace ,
312327 environmentVariables : {
313328 WORKFLOW_PHASE : phase ,
314329 PARENT_RFE : workflow . id ,
@@ -357,10 +372,12 @@ export default function ProjectRFEDetailPage() {
357372 < CardTitle > Agentic Sessions ({ rfeSessions . length } )</ CardTitle >
358373 < CardDescription > Sessions scoped to this RFE</ CardDescription >
359374 </ div >
360- < Button variant = "outline" size = "sm" onClick = { loadSessions } disabled = { sessionsLoading } >
361- < RefreshCw className = { `w-4 h-4 mr-2 ${ sessionsLoading ? "animate-spin" : "" } ` } />
362- Refresh
363- </ Button >
375+ < Link href = { `/projects/${ encodeURIComponent ( project ) } /sessions/new?workspacePath=${ encodeURIComponent ( workflowWorkspace ) } &rfeWorkflow=${ encodeURIComponent ( workflow . id ) } ` } >
376+ < Button variant = "default" size = "sm" >
377+ < Plus className = "w-4 h-4 mr-2" />
378+ Create Session
379+ </ Button >
380+ </ Link >
364381 </ div >
365382 </ CardHeader >
366383 < CardContent >
@@ -380,14 +397,14 @@ export default function ProjectRFEDetailPage() {
380397 { rfeSessions . length === 0 ? (
381398 < TableRow > < TableCell colSpan = { 6 } className = "py-6 text-center text-muted-foreground" > No agent sessions yet</ TableCell > </ TableRow >
382399 ) : (
383- rfeSessions . map ( ( s : any ) => {
384- const labels = ( s . labels || { } ) as Record < string , unknown > ;
385- const name = s . name ;
400+ rfeSessions . map ( ( s ) => {
401+ const labels = ( s . metadata . labels || { } ) as Record < string , unknown > ;
402+ const name = s . metadata . name ;
386403 const display = s . spec ?. displayName || name ;
387404 const rfePhase = typeof labels [ "rfe-phase" ] === "string" ? String ( labels [ "rfe-phase" ] ) : '' ;
388405 const model = s . spec ?. llmSettings ?. model ;
389406 const created = s . metadata ?. creationTimestamp ? formatDistanceToNow ( new Date ( s . metadata . creationTimestamp ) , { addSuffix : true } ) : '' ;
390- const cost = s . status ?. cost ;
407+ const cost = s . status ?. total_cost_usd ;
391408 return (
392409 < TableRow key = { name } >
393410 < TableCell className = "font-medium min-w-[180px]" >
@@ -397,7 +414,7 @@ export default function ProjectRFEDetailPage() {
397414 </ Link >
398415 </ TableCell >
399416 < TableCell > { WORKFLOW_PHASE_LABELS [ rfePhase as WorkflowPhase ] || rfePhase || '—' } </ TableCell >
400- < TableCell > < span className = "text-sm" > { s . phase || 'Pending' } </ span > </ TableCell >
417+ < TableCell > < span className = "text-sm" > { s . status ?. phase || 'Pending' } </ span > </ TableCell >
401418 < TableCell className = "hidden md:table-cell" > < span className = "text-sm text-gray-600 truncate max-w-[160px] block" > { model || '—' } </ span > </ TableCell >
402419 < TableCell className = "hidden lg:table-cell" > { created || < span className = "text-gray-400" > —</ span > } </ TableCell >
403420 < TableCell className = "hidden xl:table-cell" > { cost ? < span className = "text-sm font-mono" > ${ cost . toFixed ?.( 4 ) ?? cost } </ span > : < span className = "text-gray-400" > —</ span > } </ TableCell >
@@ -442,68 +459,6 @@ export default function ProjectRFEDetailPage() {
442459 </ TabsContent >
443460 </ Tabs >
444461
445- < Card >
446- < CardHeader >
447- < div className = "flex items-center justify-between" >
448- < div >
449- < CardTitle > Agentic Sessions ({ rfeSessions . length } )</ CardTitle >
450- < CardDescription > Sessions scoped to this RFE</ CardDescription >
451- </ div >
452- < Button variant = "outline" size = "sm" onClick = { loadSessions } disabled = { sessionsLoading } >
453- < RefreshCw className = { `w-4 h-4 mr-2 ${ sessionsLoading ? "animate-spin" : "" } ` } />
454- Refresh
455- </ Button >
456- </ div >
457- </ CardHeader >
458- < CardContent >
459- < div className = "overflow-x-auto" >
460- < Table >
461- < TableHeader >
462- < TableRow >
463- < TableHead className = "min-w-[220px]" > Name</ TableHead >
464- < TableHead > Stage</ TableHead >
465- < TableHead > Status</ TableHead >
466- < TableHead className = "hidden md:table-cell" > Model</ TableHead >
467- < TableHead className = "hidden lg:table-cell" > Created</ TableHead >
468- < TableHead className = "hidden xl:table-cell" > Cost</ TableHead >
469- </ TableRow >
470- </ TableHeader >
471- < TableBody >
472- { rfeSessions . length === 0 ? (
473- < TableRow > < TableCell colSpan = { 6 } className = "py-6 text-center text-muted-foreground" > No agent sessions yet</ TableCell > </ TableRow >
474- ) : (
475- rfeSessions . map ( ( s : any ) => {
476- const labels = ( s . labels || { } ) as Record < string , unknown > ;
477- const name = s . name ;
478- const display = s . spec ?. displayName || name ;
479- const rfePhase = typeof labels [ "rfe-phase" ] === "string" ? String ( labels [ "rfe-phase" ] ) : '' ;
480- const model = s . spec ?. llmSettings ?. model ;
481- const created = s . metadata ?. creationTimestamp ? formatDistanceToNow ( new Date ( s . metadata . creationTimestamp ) , { addSuffix : true } ) : '' ;
482- const cost = s . status ?. cost ;
483- return (
484- < TableRow key = { name } >
485- < TableCell className = "font-medium min-w-[180px]" >
486- < Link href = { `/projects/${ encodeURIComponent ( project ) } /sessions/${ encodeURIComponent ( name ) } ` } className = "text-blue-600 hover:underline hover:text-blue-800 transition-colors block" >
487- < div className = "font-medium" > { display } </ div >
488- { display !== name && ( < div className = "text-xs text-gray-500" > { name } </ div > ) }
489- </ Link >
490- </ TableCell >
491- < TableCell > { WORKFLOW_PHASE_LABELS [ rfePhase as WorkflowPhase ] || rfePhase || '—' } </ TableCell >
492- < TableCell > < span className = "text-sm" > { s . phase || 'Pending' } </ span > </ TableCell >
493- < TableCell className = "hidden md:table-cell" > < span className = "text-sm text-gray-600 truncate max-w-[160px] block" > { model || '—' } </ span > </ TableCell >
494- < TableCell className = "hidden lg:table-cell" > { created || < span className = "text-gray-400" > —</ span > } </ TableCell >
495- < TableCell className = "hidden xl:table-cell" > { cost ? < span className = "text-sm font-mono" > ${ cost . toFixed ?.( 4 ) ?? cost } </ span > : < span className = "text-gray-400" > —</ span > } </ TableCell >
496- </ TableRow >
497- ) ;
498- } )
499- ) }
500- </ TableBody >
501- </ Table >
502- </ div >
503- </ CardContent >
504- </ Card >
505-
506- { /* Artifacts grid omitted; use Workspace tab */ }
507462 </ div >
508463 </ div >
509464 ) ;
0 commit comments