@@ -18,6 +18,7 @@ import { ChatPoller } from "./chat-poller.js";
1818import { MessagePoller } from "./message-poller.js" ;
1919import { WS_MSG } from "./ws-types.js" ;
2020import { resolveTaskWorkingDir } from "./git-ops.js" ;
21+ import { createTaskLogger } from "./task-logger.js" ;
2122import { logError , CLI_ERR } from "./error-logger.js" ;
2223import { ensureGitUser } from "./spawner.js" ;
2324import { setup , type CliArgs , type SetupResult } from "./setup.js" ;
@@ -268,8 +269,11 @@ async function runLoop(cliArgs: CliArgs, runner: AgentRunner): Promise<void> {
268269 const isReadOnly = agentTemplate . tools !== "all" ;
269270 ui . info ( `[task] Template: "${ agentTemplate . id } "${ isReadOnly ? ` (read-only: ${ ( agentTemplate . tools as string [ ] ) . join ( ", " ) } )` : "" } ` ) ;
270271
272+ const taskLog = createTaskLogger ( task . id ) ;
273+ taskLog . event ( "pickup" , { agent : agentName , template : agentTemplate . id , title : task . title , taskType, hasReviewComment : ! ! ( task as Record < string , unknown > ) . review_comment } ) ;
274+
271275 const actionCtx : ActionContext = {
272- api, task, agentName,
276+ api, task, agentName, template : agentTemplate , taskLog ,
273277 config : { apiUrl : cliArgs . apiUrl , apiKey : cliArgs . apiKey , workingDir : taskWorkingDir , baseBranch : cliArgs . baseBranch , sprintNumber : sprintData . sprint . number , language : ctx . language , engine : cliArgs . engine } ,
274278 onDataUpdate : ( entity , id , changes ) => {
275279 ctx . wsServer ?. broadcast ( {
@@ -308,6 +312,31 @@ async function runLoop(cliArgs: CliArgs, runner: AgentRunner): Promise<void> {
308312 let pastFailures : Array < { summary : string ; failure_type: string ; agent_name: string | null } > = [ ] ;
309313 try { pastFailures = await api . fetchRelevantFailures ( ) ; } catch { /* non-fatal */ }
310314
315+ // Extract previous review feedback for retry injection
316+ let previousReview : string | undefined ;
317+ const taskReviewComment = ( task as Record < string , unknown > ) . review_comment as string | undefined ;
318+ if ( taskReviewComment ) {
319+ try {
320+ const r = JSON . parse ( taskReviewComment ) ;
321+ if ( r . verdict === "NEEDS_CHANGES" ) {
322+ const parts = [ `Verdict: ${ r . verdict } ` ] ;
323+ if ( r . requirement_match ) parts . push ( `Requirements: ${ r . requirement_match } ` ) ;
324+ if ( r . code_quality ) parts . push ( `Code quality: ${ r . code_quality } ` ) ;
325+ if ( r . risks ) parts . push ( `Risks: ${ r . risks } ` ) ;
326+ previousReview = parts . join ( "\n" ) ;
327+ }
328+ } catch {
329+ if ( taskReviewComment . startsWith ( "Blocked:" ) || taskReviewComment . includes ( "NEEDS_CHANGES" ) ) {
330+ previousReview = taskReviewComment ;
331+ }
332+ }
333+ }
334+
335+ if ( previousReview ) {
336+ ui . warn ( `Injecting previous review feedback into agent prompt` ) ;
337+ taskLog . event ( "previous_review_injected" , { preview : previousReview . slice ( 0 , 200 ) } ) ;
338+ }
339+
311340 const prompt = buildAgentPrompt ( {
312341 role : agentName , projectName : ctx . workspaceName , projectSpec : ctx . workspaceSpec ,
313342 taskId : task . id , taskTitle : task . title ,
@@ -319,6 +348,7 @@ async function runLoop(cliArgs: CliArgs, runner: AgentRunner): Promise<void> {
319348 targetRepo : task . target_repo ?? undefined ,
320349 apiDocs : apiDocs || undefined , engineHint : getEngine ( cliArgs . engine ) . promptHint ,
321350 pastFailures : pastFailures . length > 0 ? pastFailures : undefined ,
351+ previousReview,
322352 } ) ;
323353
324354 try {
@@ -386,6 +416,30 @@ async function runLoop(cliArgs: CliArgs, runner: AgentRunner): Promise<void> {
386416 } ) ;
387417 } ;
388418 // Extract COMPLETION_JSON from agent stdout → enrich post_action update_task
419+ // Extract COMPLETION_JSON or stream result from agent stdout
420+ // Fallback: if no COMPLETION_JSON found, check stream-json result event
421+ if ( ! actionCtx . completionJson ) {
422+ for ( const l of runningAgent . stdout ) {
423+ try {
424+ const ev = JSON . parse ( l ) ;
425+ if ( ev . type === "result" && ev . subtype === "success" && typeof ev . result === "string" ) {
426+ // Agent completed successfully but didn't output COMPLETION_JSON
427+ // Use the result text as review_comment
428+ const resultText = ev . result . slice ( 0 , 2000 ) ;
429+ actionCtx . completionJson = { review_comment : resultText , commits : "" } ;
430+ for ( const action of agentTemplate . post_actions ) {
431+ if ( action . type === "update_task" && action . when === "success" && action . params ?. status === "review" ) {
432+ action . params = { ...action . params , review_comment : resultText , commits : "" } ;
433+ break ;
434+ }
435+ }
436+ ui . info ( `[completion] Extracted completion from stream result (no COMPLETION_JSON)` ) ;
437+ taskLog . event ( "completion_parse" , { source : "stream_result" , review_comment : resultText . slice ( 0 , 200 ) } ) ;
438+ break ;
439+ }
440+ } catch { /* skip */ }
441+ }
442+ }
389443 for ( const line of runningAgent . stdout ) {
390444 const completionLine = line . startsWith ( "COMPLETION_JSON:" ) ? line : null ;
391445 if ( completionLine ) {
@@ -398,7 +452,9 @@ async function runLoop(cliArgs: CliArgs, runner: AgentRunner): Promise<void> {
398452 break ;
399453 }
400454 }
455+ actionCtx . completionJson = { review_comment : json . review_comment , commits : json . commits } ;
401456 ui . info ( `[completion] Parsed COMPLETION_JSON: ${ json . review_comment ?. slice ( 0 , 80 ) } ...` ) ;
457+ taskLog . event ( "completion_parse" , { source : "completion_json" , review_comment : json . review_comment ?. slice ( 0 , 200 ) , commits : json . commits } ) ;
402458 // Broadcast review comment immediately for real-time dashboard update
403459 if ( json . review_comment ) {
404460 actionCtx . onReviewUpdate ?. ( task . id , "agent_submitted" , json . review_comment ) ;
@@ -419,6 +475,7 @@ async function runLoop(cliArgs: CliArgs, runner: AgentRunner): Promise<void> {
419475 break ;
420476 }
421477 }
478+ actionCtx . completionJson = { review_comment : json . review_comment , commits : json . commits } ;
422479 ui . info ( `[completion] Parsed COMPLETION_JSON from stream: ${ json . review_comment ?. slice ( 0 , 80 ) } ...` ) ;
423480 // Broadcast review comment immediately for real-time dashboard update
424481 if ( json . review_comment ) {
@@ -493,13 +550,18 @@ async function runLoop(cliArgs: CliArgs, runner: AgentRunner): Promise<void> {
493550 }
494551 }
495552 } ;
553+ taskLog . stdout ( runningAgent . stdout ) ;
554+ taskLog . event ( "post_actions_start" , { exitCode, mergeSkipped : actionCtx . mergeSkipped , hasCompletion : ! ! actionCtx . completionJson , reviewVerdict : actionCtx . reviewVerdict } ) ;
496555 await executeActions ( agentTemplate . post_actions , actionCtx , "post" ) ;
556+ taskLog . event ( "post_actions_done" , { reviewVerdict : actionCtx . reviewVerdict } ) ;
557+ taskLog . close ( ) ;
497558 } catch ( err ) {
498559 logError ( CLI_ERR . AGENT_SPAWN_FAILED , `Error spawning agent for task ${ task . id } : ${ err } ` , { taskId : task . id , agentName } , err ) ;
499560 ui . error ( `Error spawning agent for task ${ task . id } : ${ err } ` ) ;
500- // Use failure post_actions to reset task and notify user
561+ taskLog . event ( "error" , { message : err instanceof Error ? err . message : String ( err ) } ) ;
501562 actionCtx . exitCode = 1 ;
502563 await executeActions ( agentTemplate . post_actions , actionCtx , "post" ) ;
564+ taskLog . close ( ) ;
503565 } finally {
504566 scheduler . releaseSlot ( slotName ) ;
505567 }
0 commit comments