@@ -44,6 +44,7 @@ import { Page } from 'playwright';
4444import { Snapshot } from './types' ;
4545import { AssertContext , Predicate } from './verification' ;
4646import { Tracer } from './tracing/tracer' ;
47+ import { TraceEventBuilder } from './utils/trace-event-builder' ;
4748import { LLMProvider } from './llm-provider' ;
4849import { FailureArtifactBuffer , FailureArtifactsOptions } from './failure-artifacts' ;
4950import {
@@ -338,6 +339,8 @@ export class AgentRuntime {
338339 stepIndex : number = 0 ;
339340 /** Most recent snapshot (for assertion context) */
340341 lastSnapshot : Snapshot | null = null ;
342+ private stepPreSnapshot : Snapshot | null = null ;
343+ private stepPreUrl : string | null = null ;
341344 /** Best-effort download records (Playwright downloads) */
342345 private downloads : Array < Record < string , any > > = [ ] ;
343346
@@ -347,6 +350,8 @@ export class AgentRuntime {
347350
348351 /** Assertions accumulated during current step */
349352 private assertionsThisStep : AssertionRecord [ ] = [ ] ;
353+ private stepGoal : string | null = null ;
354+ private lastAction : string | null = null ;
350355 /** Task completion tracking */
351356 private taskDone : boolean = false ;
352357 private taskDoneLabel : string | null = null ;
@@ -532,6 +537,10 @@ export class AgentRuntime {
532537 async snapshot ( options ?: Record < string , any > ) : Promise < Snapshot > {
533538 const { _skipCaptchaHandling, ...snapshotOptions } = options || { } ;
534539 this . lastSnapshot = await this . browser . snapshot ( this . page , snapshotOptions ) ;
540+ if ( this . lastSnapshot && ! this . stepPreSnapshot ) {
541+ this . stepPreSnapshot = this . lastSnapshot ;
542+ this . stepPreUrl = this . lastSnapshot . url ;
543+ }
535544 if ( ! _skipCaptchaHandling ) {
536545 await this . handleCaptchaIfNeeded ( this . lastSnapshot , 'gateway' ) ;
537546 }
@@ -713,6 +722,7 @@ export class AgentRuntime {
713722 * Record an action in the artifact timeline and capture a frame if enabled.
714723 */
715724 async recordAction ( action : string , url ?: string ) : Promise < void > {
725+ this . lastAction = action ;
716726 if ( ! this . artifactBuffer ) {
717727 return ;
718728 }
@@ -722,6 +732,84 @@ export class AgentRuntime {
722732 }
723733 }
724734
735+ /**
736+ * Emit a step_end event using TraceEventBuilder.
737+ */
738+ emitStepEnd ( opts : {
739+ action ?: string ;
740+ success ?: boolean ;
741+ error ?: string ;
742+ outcome ?: string ;
743+ durationMs ?: number ;
744+ attempt ?: number ;
745+ verifyPassed ?: boolean ;
746+ verifySignals ?: Record < string , any > ;
747+ postUrl ?: string ;
748+ postSnapshotDigest ?: string ;
749+ } ) : any {
750+ const goal = this . stepGoal || '' ;
751+ const preSnap = this . stepPreSnapshot || this . lastSnapshot ;
752+ const preUrl = this . stepPreUrl || preSnap ?. url || this . page ?. url ?.( ) || '' ;
753+ const postUrl = opts . postUrl || this . page ?. url ?.( ) || this . lastSnapshot ?. url || preUrl ;
754+
755+ const preDigest = preSnap ? TraceEventBuilder . buildSnapshotDigest ( preSnap ) : undefined ;
756+ const postDigest =
757+ opts . postSnapshotDigest ||
758+ ( this . lastSnapshot ? TraceEventBuilder . buildSnapshotDigest ( this . lastSnapshot ) : undefined ) ;
759+
760+ const urlChanged = Boolean ( preUrl && postUrl && String ( preUrl ) !== String ( postUrl ) ) ;
761+ const assertionsData = this . getAssertionsForStepEnd ( ) ;
762+
763+ const signals = { ...( opts . verifySignals || { } ) } as Record < string , any > ;
764+ if ( signals . url_changed === undefined ) {
765+ signals . url_changed = urlChanged ;
766+ }
767+ if ( opts . error && signals . error === undefined ) {
768+ signals . error = opts . error ;
769+ }
770+ if ( assertionsData . task_done !== undefined ) {
771+ signals . task_done = assertionsData . task_done ;
772+ }
773+ if ( assertionsData . task_done_label ) {
774+ signals . task_done_label = assertionsData . task_done_label ;
775+ }
776+
777+ const verifyPassed =
778+ opts . verifyPassed !== undefined ? opts . verifyPassed : this . requiredAssertionsPassed ( ) ;
779+
780+ const execData = {
781+ success : opts . success !== undefined ? opts . success : verifyPassed ,
782+ action : opts . action || this . lastAction || 'unknown' ,
783+ outcome : opts . outcome || '' ,
784+ duration_ms : opts . durationMs ,
785+ error : opts . error ,
786+ } ;
787+
788+ const verifyData = {
789+ passed : Boolean ( verifyPassed ) ,
790+ signals,
791+ } ;
792+
793+ const stepEndData = TraceEventBuilder . buildRuntimeStepEndData ( {
794+ stepId : this . stepId || '' ,
795+ stepIndex : this . stepIndex ,
796+ goal,
797+ attempt : opts . attempt ?? 0 ,
798+ preUrl,
799+ postUrl,
800+ preSnapshotDigest : preDigest ,
801+ postSnapshotDigest : postDigest ,
802+ execData,
803+ verifyData,
804+ assertions : assertionsData . assertions ,
805+ taskDone : assertionsData . task_done ,
806+ taskDoneLabel : assertionsData . task_done_label ,
807+ } ) ;
808+
809+ this . tracer . emit ( 'step_end' , stepEndData , this . stepId || undefined ) ;
810+ return stepEndData ;
811+ }
812+
725813 private async captureArtifactFrame ( ) : Promise < void > {
726814 if ( ! this . artifactBuffer ) {
727815 return ;
@@ -797,6 +885,10 @@ export class AgentRuntime {
797885 beginStep ( goal : string , stepIndex ?: number ) : string {
798886 // Clear previous step state
799887 this . assertionsThisStep = [ ] ;
888+ this . stepPreSnapshot = null ;
889+ this . stepPreUrl = null ;
890+ this . stepGoal = goal ;
891+ this . lastAction = null ;
800892
801893 // Update step index
802894 if ( stepIndex !== undefined ) {
0 commit comments