534 lines
22 KiB
TypeScript
534 lines
22 KiB
TypeScript
import { signal, computed } from '@angular/core';
|
|
import { of } from 'rxjs';
|
|
|
|
import { AgentService } from './agent.service';
|
|
|
|
describe('AgentService live LLM retry timeline', () => {
|
|
function createServiceWithoutPolling(): AgentService {
|
|
const service = Object.create(AgentService.prototype) as AgentService;
|
|
service.sessionLogs = signal<any[]>([]);
|
|
service.isSessionContentLoading = signal(false);
|
|
service.startupProgressBySession = signal({});
|
|
(service as any).pendingStartupProgress = signal<any[]>([]);
|
|
(service as any).sessionLoadGeneration = 0;
|
|
(service as any).sessionSnapshotRequestId = 0;
|
|
(service as any).sessionSnapshotAppliedId = 0;
|
|
(service as any).pendingSnapshotRequests = new Set<number>();
|
|
return service;
|
|
}
|
|
|
|
it('orders startup milestones and replaces duplicate stages', () => {
|
|
const service = createServiceWithoutPolling();
|
|
|
|
(service as any).appendStartupProgress({
|
|
session_id: 'session-1',
|
|
stage: 'device',
|
|
message: 'Checking device',
|
|
timestamp: 102
|
|
}, 'session-1');
|
|
(service as any).appendStartupProgress({
|
|
session_id: 'session-1',
|
|
stage: 'queued',
|
|
message: 'Task queued',
|
|
timestamp: 100
|
|
}, 'session-1');
|
|
(service as any).appendStartupProgress({
|
|
session_id: 'session-1',
|
|
stage: 'device',
|
|
message: 'Device connected',
|
|
timestamp: 103
|
|
}, 'session-1');
|
|
|
|
const events = service.startupProgressBySession()['session-1'];
|
|
expect(events.map((event) => event.stage)).toEqual(['queued', 'device']);
|
|
expect(events[1].message).toBe('Device connected');
|
|
});
|
|
|
|
it('normalizes a live retry event into the historical trace contract', () => {
|
|
const service = createServiceWithoutPolling();
|
|
const event = {
|
|
trace_id: 'retry-1',
|
|
step_id: 'step-1',
|
|
timestamp: 100,
|
|
error: '503 high demand',
|
|
delay: 1.5,
|
|
provider: 'google',
|
|
source: 'provider_sdk',
|
|
recoverable: true,
|
|
request_id: 'request-1',
|
|
scheduled_at: 100
|
|
};
|
|
|
|
(service as any).appendLiveLLMRetryTrace(event, 'session-1');
|
|
|
|
const [log] = service.sessionLogs();
|
|
expect(log.type).toBe('trace_recorded');
|
|
expect(log.data).toEqual(jasmine.objectContaining({
|
|
trace_id: 'retry-1',
|
|
session_id: 'session-1',
|
|
step_id: 'step-1',
|
|
type: 'llm_call',
|
|
name: 'llm_retry',
|
|
status: 'retrying'
|
|
}));
|
|
expect(log.data.payload).toEqual(jasmine.objectContaining({
|
|
error: '503 high demand',
|
|
delay: 1.5,
|
|
provider: 'google',
|
|
source: 'provider_sdk',
|
|
request_id: 'request-1'
|
|
}));
|
|
});
|
|
|
|
it('upserts duplicate live retry events instead of adding another row', () => {
|
|
const service = createServiceWithoutPolling();
|
|
const event = {
|
|
trace_id: 'retry-1',
|
|
timestamp: 100,
|
|
delay: 1.5,
|
|
provider: 'google',
|
|
source: 'provider_sdk',
|
|
request_id: 'request-1',
|
|
scheduled_at: 100
|
|
};
|
|
|
|
(service as any).appendLiveLLMRetryTrace(event, 'session-1');
|
|
(service as any).appendLiveLLMRetryTrace({ ...event, error: 'updated 503' }, 'session-1');
|
|
|
|
expect(service.sessionLogs().length).toBe(1);
|
|
expect(service.sessionLogs()[0].data.payload.error).toBe('updated 503');
|
|
});
|
|
|
|
it('replaces an old history snapshot while preserving live retry events', () => {
|
|
const service = createServiceWithoutPolling();
|
|
(service as any).currentSessionId = signal<string | null>('session-1');
|
|
(service as any).http = {
|
|
get: () => of([{
|
|
step_id: 'step-1',
|
|
timestamp: 100,
|
|
generic_tools: [{ trace_id: 'persisted-retry', name: 'llm_retry' }]
|
|
}])
|
|
};
|
|
service.sessionLogs.set([
|
|
{ type: 'step_updated', history_snapshot: true, data: { step_id: 'old-step' } },
|
|
{ type: 'trace_recorded', data: { trace_id: 'live-retry', name: 'llm_retry' } }
|
|
]);
|
|
service.isSessionContentLoading.set(true);
|
|
|
|
(service as any).backfillSessionSteps('session-1');
|
|
|
|
const logs = service.sessionLogs();
|
|
expect(logs.length).toBe(2);
|
|
expect(logs[0].history_snapshot).toBeTrue();
|
|
expect(logs[0].data.step_id).toBe('step-1');
|
|
expect(logs[1].data.trace_id).toBe('live-retry');
|
|
expect(service.isSessionContentLoading()).toBeFalse();
|
|
});
|
|
|
|
it('attaches the persisted checker transcript to the attempt it belongs to', () => {
|
|
const service = createServiceWithoutPolling();
|
|
(service as any).currentSessionId = signal<string | null>('session-1');
|
|
(service as any).http = {
|
|
get: () => of({
|
|
records: [
|
|
{ attempt_id: 'abc#1', checkpoint_id: 'abc', subgoal_text: 'Create the alarm', trace_id: 't-1', item_text: 'alarm exists', kind: 'verify', status: 'passed', evidence: 'seen', ts: 10 },
|
|
{ attempt_id: 'final#1', checkpoint_id: 'final', trace_id: 't-2', item_text: 'alarm exists', kind: 'verify', status: 'passed', evidence: 'seen', ts: 20 }
|
|
],
|
|
streams: [
|
|
{
|
|
attempt_id: 'abc#1',
|
|
trace_id: 't-1',
|
|
ts: 11,
|
|
segments: [
|
|
{ execution_id: 'e-1', role: 'thought', when: 8, text: 'Looking' },
|
|
{ execution_id: 'e-1', role: 'answer', when: 9, text: 'Seen it' }
|
|
]
|
|
}
|
|
],
|
|
run_outcome: null
|
|
})
|
|
};
|
|
service.sessionLogs.set([{ type: 'checker_event', checks_snapshot: true, data: { attempt_id: 'stale' } }]);
|
|
|
|
service.fetchChecks('session-1');
|
|
|
|
const logs = service.sessionLogs();
|
|
expect(logs.map((l) => l.data.attempt_id)).toEqual(['abc#1', 'final#1']);
|
|
expect(logs[0].data.stream_segments).toEqual([
|
|
{ execution_id: 'e-1', stream_type: 'thinking', text: 'Looking', timestamp: new Date(8000).toISOString(), isCompleted: true },
|
|
{ execution_id: 'e-1', stream_type: 'text', text: 'Seen it', timestamp: new Date(9000).toISOString(), isCompleted: true }
|
|
]);
|
|
expect('stream_segments' in logs[1].data).toBeFalse();
|
|
});
|
|
|
|
it('follows a just-started task even when status polling saw it first', () => {
|
|
const service = createServiceWithoutPolling();
|
|
(service as any).http = {
|
|
post: () => of({ tasks: [{ session_id: 'new-session' }] })
|
|
};
|
|
service.agentStatus = signal('running');
|
|
service.runningSessionId = signal<string | null>('new-session');
|
|
service.userPinnedSessionId = signal<string | null>(null);
|
|
(service as any).sessions = signal<any[]>([{
|
|
session_id: 'new-session',
|
|
status: 'running'
|
|
}]);
|
|
const selectSpy = spyOn(service, 'selectSession');
|
|
|
|
service.runTask('test goal').subscribe();
|
|
|
|
expect(selectSpy).toHaveBeenCalledWith('new-session', false);
|
|
});
|
|
|
|
it('keeps the paused state when the backend says there is nothing to resume', () => {
|
|
const service = createServiceWithoutPolling();
|
|
(service as any).http = { post: () => of({ status: 'not_paused' }) };
|
|
(service as any).rawSessions = signal<any[]>([{ session_id: 'session-1', status: 'paused' }]);
|
|
(service as any).pendingQueue = signal<any[]>([]);
|
|
service.agentStatus = signal('paused');
|
|
service.runningSessionId = signal<string | null>('session-1');
|
|
service.isPaused = signal(true);
|
|
service.pausedError = signal<string | null>('503 unavailable');
|
|
const statusSpy = spyOn(service, 'fetchStatus');
|
|
|
|
service.resumeTask();
|
|
|
|
expect(service.agentStatus()).toBe('paused');
|
|
expect(service.isPaused()).toBeTrue();
|
|
expect(statusSpy).toHaveBeenCalled();
|
|
});
|
|
|
|
it('moves the active session to running only after resume succeeds', () => {
|
|
const service = createServiceWithoutPolling();
|
|
(service as any).http = { post: () => of({ status: 'resumed' }) };
|
|
(service as any).rawSessions = signal<any[]>([{ session_id: 'session-1', status: 'paused' }]);
|
|
(service as any).pendingQueue = signal<any[]>([]);
|
|
service.agentStatus = signal('paused');
|
|
service.runningSessionId = signal<string | null>('session-1');
|
|
service.isPaused = signal(true);
|
|
service.pausedError = signal<string | null>('503 unavailable');
|
|
spyOn(service, 'fetchStatus');
|
|
|
|
service.resumeTask();
|
|
|
|
expect(service.agentStatus()).toBe('running');
|
|
expect(service.isPaused()).toBeFalse();
|
|
expect((service as any).rawSessions()[0].status).toBe('running');
|
|
});
|
|
});
|
|
|
|
describe('AgentService recording finalization lifecycle', () => {
|
|
function createVideoService(response: any): AgentService {
|
|
const service = Object.create(AgentService.prototype) as AgentService;
|
|
(service as any).http = { get: () => of(response) };
|
|
(service as any).rawSessions = signal<any[]>([
|
|
{ session_id: 'session-1', status: 'completed', recording_status: 'recording' }
|
|
]);
|
|
(service as any).activeVideoSessionId = 'session-1';
|
|
(service as any).videoRequestGeneration = 1;
|
|
(service as any).videoRetryTimer = null;
|
|
(service as any).videoWaitStartedAt = Date.now();
|
|
service.activeVideoUrl = signal<string | null>(null);
|
|
service.activeVideoSegments = signal<any[]>([]);
|
|
service.recordingSources = signal([]);
|
|
service.selectedRecordingSource = signal('');
|
|
service.usesSessionTimeline = signal(false);
|
|
service.recordingSessionDuration = signal(0);
|
|
service.activeVideoTitle = signal('');
|
|
service.isVideoWindowOpen = signal(false);
|
|
service.isVideoMinimized = signal(false);
|
|
service.videoSeekRequest = signal(null);
|
|
service.currentSessionId = signal<string | null>('session-1');
|
|
service.sessions = computed(() => (service as any).rawSessions());
|
|
(service as any).videoSeekRequestId = 0;
|
|
service.isVideoLoading = signal(false);
|
|
service.recordingPlaybackStatus = signal('idle');
|
|
service.recordingPlaybackMessage = signal('');
|
|
service.shouldAutoplayVideo = signal(true);
|
|
service.playerMode = signal<'video' | 'steps'>('video');
|
|
(service as any).hasCurrentSessionStepFrames = computed(() => false);
|
|
(service as any).currentSessionStepFrames = computed(() => []);
|
|
return service;
|
|
}
|
|
|
|
it('keeps unfinished media out of the video element and schedules another readiness check', () => {
|
|
const service = createVideoService({
|
|
session_id: 'session-1',
|
|
status: 'processing',
|
|
has_video: false,
|
|
video_url: null,
|
|
retry_after_ms: 750
|
|
});
|
|
const retrySpy = spyOn<any>(service, 'scheduleVideoRetry');
|
|
|
|
(service as any).requestSessionVideo('session-1', 1);
|
|
|
|
expect(service.recordingPlaybackStatus()).toBe('processing');
|
|
expect(service.isVideoLoading()).toBeTrue();
|
|
expect(service.activeVideoUrl()).toBeNull();
|
|
expect(retrySpy).toHaveBeenCalledWith('session-1', 1, 750);
|
|
});
|
|
|
|
it('publishes finalized media and preserves the automatic replay request', () => {
|
|
const service = createVideoService({
|
|
session_id: 'session-1',
|
|
status: 'ready',
|
|
has_video: true,
|
|
video_url: '/videos/recording.mp4?v=1',
|
|
video_segments: [
|
|
{ url: '/videos/recording.mp4?v=1', start: 0, duration: 5, width: 1080, height: 1920 }
|
|
]
|
|
});
|
|
|
|
(service as any).requestSessionVideo('session-1', 1);
|
|
|
|
expect(service.recordingPlaybackStatus()).toBe('ready');
|
|
expect(service.isVideoLoading()).toBeFalse();
|
|
expect(service.activeVideoUrl()).toBe('/videos/recording.mp4?v=1');
|
|
expect(service.activeVideoSegments().length).toBe(1);
|
|
expect(service.shouldAutoplayVideo()).toBeTrue();
|
|
});
|
|
|
|
it('stops polling and exposes a terminal recording failure', () => {
|
|
const service = createVideoService({
|
|
session_id: 'session-1',
|
|
status: 'failed',
|
|
has_video: false,
|
|
video_url: null,
|
|
message: 'ffmpeg failed'
|
|
});
|
|
|
|
(service as any).requestSessionVideo('session-1', 1);
|
|
|
|
expect(service.recordingPlaybackStatus()).toBe('failed');
|
|
expect(service.isVideoLoading()).toBeFalse();
|
|
expect(service.recordingPlaybackMessage()).toBe('ffmpeg failed');
|
|
});
|
|
|
|
it('publishes task-time sources and selects a computer recording by its target alias', () => {
|
|
const android = {
|
|
id: 'android', label: '安卓', kind: 'android', target_ids: ['android'], status: 'ready',
|
|
segments: [{ url: '/videos/android.mp4', start: 0, duration: 10, width: 720, height: 1280 }]
|
|
};
|
|
const computer = {
|
|
id: 'screen-1', label: '电脑', kind: 'computer', target_ids: ['window-1', 'window-2'], status: 'ready',
|
|
message: '已使用兼容编码',
|
|
segments: [{ url: '/videos/computer.mp4', start: 12, duration: 10, width: 1280, height: 720 }]
|
|
};
|
|
const service = createVideoService({
|
|
session_id: 'session-1', status: 'ready', has_video: true,
|
|
video_url: android.segments[0].url, video_segments: android.segments,
|
|
sources: [android, computer], session_timeline: true, session_duration: 25
|
|
});
|
|
|
|
(service as any).requestSessionVideo('session-1', 1);
|
|
expect(service.recordingSources()).toEqual([android, computer]);
|
|
expect(service.selectedRecordingSource()).toBe('android');
|
|
expect(service.usesSessionTimeline()).toBeTrue();
|
|
expect(service.recordingSessionDuration()).toBe(25);
|
|
|
|
expect(service.selectRecordingSource('window-2')).toBeTrue();
|
|
expect(service.selectedRecordingSource()).toBe('screen-1');
|
|
expect(service.activeVideoUrl()).toBe('/videos/computer.mp4');
|
|
expect(service.activeVideoSegments()).toBe(computer.segments);
|
|
expect(service.recordingPlaybackMessage()).toBe('已使用兼容编码');
|
|
expect(service.selectRecordingSource('screen-1')).toBeTrue();
|
|
expect(service.activeVideoSegments()).toBe(computer.segments);
|
|
expect(service.selectRecordingSource('missing-source')).toBeFalse();
|
|
expect(service.selectedRecordingSource()).toBe('screen-1');
|
|
expect(service.activeVideoUrl()).toBe('/videos/computer.mp4');
|
|
});
|
|
|
|
it('keeps a failed source explicit without retaining another source video', () => {
|
|
const service = createVideoService({});
|
|
service.activeVideoUrl.set('/videos/android.mp4');
|
|
service.recordingSources.set([{
|
|
id: 'screen-1', label: '电脑', kind: 'computer', target_ids: ['window-1'], status: 'failed',
|
|
segments: [], message: '录制进程未产生画面'
|
|
}]);
|
|
|
|
expect(service.selectRecordingSource('window-1')).toBeTrue();
|
|
expect(service.activeVideoUrl()).toBeNull();
|
|
expect(service.activeVideoSegments()).toEqual([]);
|
|
expect(service.recordingPlaybackMessage()).toBe('录制进程未产生画面');
|
|
});
|
|
|
|
it('clears the previous task source and seek command when opening another task normally', () => {
|
|
const service = createVideoService({});
|
|
const load = spyOn<any>(service, 'requestSessionVideo');
|
|
service.recordingSources.set([{
|
|
id: 'screen-old', label: '电脑', kind: 'computer', target_ids: ['window-old'], status: 'ready',
|
|
segments: [{ url: '/videos/old.mp4', start: 0, duration: 10, width: 1280, height: 720 }]
|
|
}]);
|
|
service.selectRecordingSource('screen-old');
|
|
service.usesSessionTimeline.set(true);
|
|
service.recordingSessionDuration.set(10);
|
|
service.requestVideoSeek(6, 'window-old');
|
|
|
|
service.openVideoPlayer('session-2');
|
|
|
|
expect(service.videoSeekRequest()).toBeNull();
|
|
expect(service.recordingSources()).toEqual([]);
|
|
expect(service.selectedRecordingSource()).toBe('');
|
|
expect(service.usesSessionTimeline()).toBeFalse();
|
|
expect(service.recordingSessionDuration()).toBe(0);
|
|
expect(service.activeVideoSegments()).toEqual([]);
|
|
expect(service.activeVideoUrl()).toBeNull();
|
|
expect(load).toHaveBeenCalledOnceWith('session-2', 2);
|
|
});
|
|
|
|
it('replaces the previous task seek with the new source-specific command', () => {
|
|
const service = createVideoService({});
|
|
spyOn<any>(service, 'requestSessionVideo');
|
|
service.requestVideoSeek(6, 'window-old');
|
|
const oldRequestId = service.videoSeekRequest()!.requestId;
|
|
|
|
service.openVideoPlayer('session-2', undefined, undefined, 14, undefined, 'window-new');
|
|
|
|
expect(service.videoSeekRequest()).toEqual({ seconds: 14, sourceId: 'window-new', requestId: oldRequestId + 1 });
|
|
});
|
|
});
|
|
|
|
describe('AgentService video analysis seeking', () => {
|
|
it('publishes repeatable, clamped seek requests for the floating player', () => {
|
|
const service = Object.create(AgentService.prototype) as AgentService;
|
|
service.videoSeekRequest = signal<{ seconds: number; requestId: number } | null>(null);
|
|
(service as any).videoSeekRequestId = 0;
|
|
|
|
service.requestVideoSeek(-5);
|
|
const first = service.videoSeekRequest();
|
|
service.requestVideoSeek(0);
|
|
const second = service.videoSeekRequest();
|
|
|
|
expect(first?.seconds).toBe(0);
|
|
expect(second?.seconds).toBe(0);
|
|
expect(second?.requestId).toBeGreaterThan(first?.requestId || 0);
|
|
});
|
|
});
|
|
|
|
describe('AgentService task cancellation and active session tracking', () => {
|
|
it('computes isCurrentSessionRunning true only when viewing an active running/paused session', () => {
|
|
const service = Object.create(AgentService.prototype) as any;
|
|
service.currentSessionId = signal<string | null>(null);
|
|
service.rawSessions = signal<any[]>([]);
|
|
service.activeTasks = signal<any[]>([]);
|
|
service.pendingQueue = signal<any[]>([]);
|
|
service.agentStatus = signal('idle');
|
|
service.runningSessionId = signal<string | null>(null);
|
|
service.runningGoal = signal<string | null>(null);
|
|
service.activeSessionTracking = new Map();
|
|
|
|
service.currentSession = computed(() => {
|
|
const curId = service.currentSessionId();
|
|
if (!curId) return null;
|
|
return service.sessions().find((s: any) => s.session_id === curId) || null;
|
|
});
|
|
|
|
service.sessions = computed(() => {
|
|
return service.rawSessions();
|
|
});
|
|
|
|
service.isCurrentSessionRunning = computed(() => {
|
|
const curId = service.currentSessionId();
|
|
if (!curId) return false;
|
|
const session = service.currentSession();
|
|
if (session) {
|
|
return session.status === 'running' || session.status === 'paused';
|
|
}
|
|
const isActiveStatus = service.agentStatus() === 'running' || service.agentStatus() === 'paused';
|
|
if (isActiveStatus && service.runningSessionId() === curId) {
|
|
return true;
|
|
}
|
|
if (service.activeTasks().some((at: any) => at.session_id === curId)) {
|
|
return true;
|
|
}
|
|
return false;
|
|
});
|
|
|
|
// 1. No session selected -> false
|
|
expect(service.isCurrentSessionRunning()).toBeFalse();
|
|
|
|
// 2. Completed session selected -> false
|
|
service.rawSessions.set([
|
|
{ session_id: 'sess-completed', status: 'completed', initial_goal: '完成', start_time: 100 },
|
|
{ session_id: 'sess-running', status: 'running', initial_goal: '执行中', start_time: 101 }
|
|
]);
|
|
service.currentSessionId.set('sess-completed');
|
|
expect(service.isCurrentSessionRunning()).toBeFalse();
|
|
|
|
// 3. Active running session selected -> true
|
|
service.currentSessionId.set('sess-running');
|
|
expect(service.isCurrentSessionRunning()).toBeTrue();
|
|
|
|
// 4. Paused session selected -> true
|
|
service.rawSessions.set([
|
|
{ session_id: 'sess-paused', status: 'paused', initial_goal: '已暂停', start_time: 102 }
|
|
]);
|
|
service.currentSessionId.set('sess-paused');
|
|
expect(service.isCurrentSessionRunning()).toBeTrue();
|
|
});
|
|
|
|
it('stops a specific session by passing its session_id', () => {
|
|
const service = Object.create(AgentService.prototype) as AgentService;
|
|
service.currentSessionId = signal<string | null>('sess-2');
|
|
service.runningSessionId = signal<string | null>('sess-1');
|
|
service.runningGoal = signal<string | null>('Goal 1');
|
|
service.agentStatus = signal('running');
|
|
service.isPaused = signal(false);
|
|
service.pausedError = signal<string | null>(null);
|
|
service.isRetrying = signal(false);
|
|
service.sessionLogs = signal<any[]>([]);
|
|
service.activeTasks = signal<any[]>([{ session_id: 'sess-1' }, { session_id: 'sess-2' }]);
|
|
(service as any).pendingQueue = signal<any[]>([]);
|
|
(service as any).rawSessions = signal<any[]>([
|
|
{ session_id: 'sess-1', status: 'running', initial_goal: 'Goal 1' },
|
|
{ session_id: 'sess-2', status: 'running', initial_goal: 'Goal 2' }
|
|
]);
|
|
service.sessions = computed(() => (service as any).rawSessions());
|
|
|
|
let postedUrl = '';
|
|
let postedPayload: any = null;
|
|
(service as any).http = {
|
|
post: (url: string, payload: any) => {
|
|
postedUrl = url;
|
|
postedPayload = payload;
|
|
return of({});
|
|
}
|
|
};
|
|
(service as any).setSessionStatus = (sid: string, st: string) => {};
|
|
(service as any).fetchStatus = () => {};
|
|
(service as any).fetchSessions = () => {};
|
|
|
|
// Stop sess-2 specifically
|
|
service.stopTask('sess-2', false);
|
|
|
|
expect(postedUrl).toContain('session_id=sess-2');
|
|
expect(postedPayload?.session_id).toBe('sess-2');
|
|
// Because sess-1 is still running, agentStatus should NOT be reset to idle
|
|
expect(service.agentStatus()).toBe('running');
|
|
// activeTasks should have filtered out sess-2
|
|
expect(service.activeTasks().map((at: any) => at.session_id)).toEqual(['sess-1']);
|
|
});
|
|
});
|
|
|
|
describe('Exploration queue grouping',()=>{
|
|
const service=Object.create(AgentService.prototype) as AgentService;
|
|
it('keeps validations independent and groups only exploration executions',()=>{
|
|
service.batchFilter=signal('__exploration__');
|
|
const old:any={session_id:'old',exploration_id:'e',exploration_kind:'exploration',exploration_latest_run_id:'new'};
|
|
const next:any={...old,session_id:'new',exploration_status:'paused'};
|
|
const validation:any={...old,session_id:'validation',exploration_kind:'validation'};
|
|
expect(service.isQueueRepresentative(old)).toBeFalse();expect(service.isQueueRepresentative(next)).toBeTrue();expect(service.isQueueRepresentative(validation)).toBeTrue();
|
|
expect(service.matchesBatchFilter(next)).toBeTrue();expect(service.matchesBatchFilter(validation)).toBeFalse();expect(service.explorationQueueStatus(next)).toBe('paused');
|
|
service.batchFilter.set('__single__');expect(service.matchesBatchFilter(validation)).toBeTrue();expect(service.matchesBatchFilter(next)).toBeFalse();
|
|
});
|
|
});
|
|
|
|
describe('Queued exploration identity',()=>{
|
|
it('preserves exploration mode before the session row exists',()=>{
|
|
const service=Object.create(AgentService.prototype) as AgentService;
|
|
const fields=service.explorationSessionFields({exploration_id:'conversation',exploration_kind:'exploration',exploration_model:'gpt-6.1-sol',exploration_latest_run_id:'new',exploration_status:'queued'});
|
|
expect(fields.exploration_id).toBe('conversation');expect(fields.model_info?.architecture).toBe('explore');expect(fields.exploration_latest_run_id).toBe('new');
|
|
});
|
|
});
|