fix(replay): unify step indexing, dual-index entity deduplication, and prevent step-4 start bug

- Backend: maintain _step_number_cache in DataEngine and include step_number in all step_updated SSE events (summary, thinking, execution result)
- Admin console: query unassigned trace step_ids in stepless_rows to prevent orphan traces from being lost
- Showcase UI: implement dual-indexed entity deduplication (by step_id and step_number) in extractStepReplayFrames to eliminate phantom ghost steps
- Showcase UI: filter visual screenshot steps before generating sequential 1-based frame numbers, preventing step-4 start and index shift bugs
- Showcase UI: add rawStepNumber and summary properties to StepReplayFrame
- Showcase UI: update FloatingVideoPlayerComponent session reset effect to only reset playback on sessionId change rather than on every stepFrames mutation
- Showcase UI: add stepSeekRequest signal and stepIndex parameter to AgentService.openVideoPlayer
- Unit tests: add Karma and Pytest unit tests verifying step 1 start, dual-indexing, and SSE step_number payloads
This commit is contained in:
somew1nd committed 2026-08-27 11:03:47 -07:00
1 parent 5f1ee18bc1
commit 60312abbb0
8 files changed
+384 -91

No files matched your search

@@ -232,9 +232,11 @@ export class AgentService {
public recordingPlaybackMessage = signal<string>('');
public shouldAutoplayVideo = signal<boolean>(false);
public videoSeekRequest = signal<{ seconds: number; requestId: number } | null>(null);
public stepSeekRequest = signal<{ index: number; requestId: number } | null>(null);
public playerMode = signal<'video' | 'steps'>('video');
private activeVideoSessionId: string | null = null;
private videoSeekRequestId = 0;
private stepSeekRequestId = 0;
private videoRequestGeneration = 0;
private videoRetryTimer: ReturnType<typeof setTimeout> | null = null;
private videoWaitStartedAt = 0;
@@ -258,6 +260,11 @@ export class AgentService {
this.playerMode.update((mode) => (mode === 'video' ? 'steps' : 'video'));
}
public requestStepSeek(index: number): void {
this.stepSeekRequestId++;
this.stepSeekRequest.set({ index, requestId: this.stepSeekRequestId });
}
/**
* Clear user-pinned selection so subsequent runs automatically follow active runner
*/
@@ -1407,7 +1414,8 @@ export class AgentService {
sessionId?: string,
videoUrl?: string,
title?: string,
seekSeconds?: number
seekSeconds?: number,
stepIndex?: number
): void {
const targetSessionId = sessionId || this.currentSessionId();
const session = this.sessions().find(s => s.session_id === targetSessionId);
@@ -1424,6 +1432,10 @@ export class AgentService {
this.recordingPlaybackMessage.set('');
this.activeVideoSegments.set([]);
if (Number.isFinite(seekSeconds)) this.requestVideoSeek(Number(seekSeconds));
if (Number.isFinite(stepIndex)) {
this.playerMode?.set('steps');
this.requestStepSeek(Number(stepIndex));
}
if (!targetSessionId) {
this.activeVideoUrl.set(null);
this.isVideoLoading.set(false);