import type { Page, Request, Route } from '@playwright/test' export const PROPOSAL_ID = '11111111-1111-4111-8111-111111111111' export const PROJECT_ID = '22222222-2222-4222-8222-222222222222' export const DOC_PATH = 'nexus/mission-control.md' export const MEMORY_NAME = '2026-07-31.md' export const INCIDENT_NAME = '2026-07-30-gateway-timeout.md' type NexusRole = 'owner' | 'member' type ProposalStatus = | 'awaiting_approval' | 'provisioning' | 'ready' | 'partial' | 'failed' | 'in_doubt' | 'rejected' interface MockOptions { authenticated?: boolean role?: NexusRole proposalStatus?: ProposalStatus domainResyncSequence?: number boardRefreshDelayMs?: number boardRefreshTitle?: string apiFailure?: { path: string status: number attempts?: number } } interface CapturedRequest { path: string method: string headers: Record body: unknown } export interface NexusApiHarness { readonly proposalRequests: CapturedRequest[] readonly boardRequestUrls: string[] readonly domainEventRequests: Array<{ lastEventId: string | null }> readonly taskMutationRequests: CapturedRequest[] readonly chatRequests: CapturedRequest[] readonly resyncResponseCount: number readonly openClawOverviewRequestCount: number readonly browserTelemetryRequestCount: number readonly proposal: Record } const now = '2026-07-31T10:00:00.000Z' function collection(items: unknown[] = []) { return { state: 'ready', items, nextCursor: null, message: null, recovery: null, checkedAt: now, } } function task( id: string, title: string, state: string, overrides: Record = {}, ) { return { id, title, detail: `${title} detail`, source: 'bao', state, priority: 'Medium', assignedTo: 'bao', parentTaskId: null, projectId: null, dueDate: null, createdAt: now, updatedAt: now, isAgentTask: false, expectedFrom: null, lastActivityMessage: 'Visible E2E activity', lastActivityAt: now, childTaskCount: 0, openChildTaskCount: 0, hasVisibleDelegation: false, childTasks: [], ...overrides, } } const openTask = task('task-open-1', 'E2E Backlog task', 'Backlog') const progressTask = task('task-progress-1', 'E2E Active task', 'In progress') const firstDoneTask = task('task-done-1', 'E2E Done page one', 'Done') const secondDoneTask = task('task-done-2', 'E2E Done page two', 'Done') const projectTask = task('task-project-1', 'E2E Project task', 'In progress', { projectId: PROJECT_ID, assignedTo: 'iris', expectedFrom: 'iris', isAgentTask: true, }) const unrelatedTask = task('task-unrelated-1', 'E2E Unrelated task', 'Backlog', { projectId: null, }) const projectFixture = { id: PROJECT_ID, name: 'Release Readiness', description: 'Coordinates the release across tasks, agents, and runs.', status: 'Online', progress: 64, createdAt: now, updatedAt: now, } const projectRunFixture = { id: '33333333-3333-4333-8333-333333333333', title: 'Release readiness verification', prompt: 'Verify the linked project.', agentId: 'iris', sessionKey: 'agent:iris:project-release', status: 'completed', taskId: projectTask.id, projectId: PROJECT_ID, openClawRunId: 'openclaw-project-run-1', retriedFromRunId: null, correlationId: 'e2e-project-correlation', actor: 'bao', lastError: null, lastGatewaySequence: 42, sequenceGapDetected: false, canStop: false, canRetry: true, canResume: false, resumeCapabilityMessage: 'Completed runs cannot be resumed.', createdAt: now, updatedAt: now, startedAt: now, finishedAt: now, } const agentDetailFixture = { id: 'iris', name: 'Iris', role: 'Chief of Staff', model: 'openai/gpt-5.4', status: 'Online', lastSeen: now, workspace: '/managed/agents/iris', agentDir: '/managed/agents/iris/agent', description: 'Coordinates E2E work.', subAgents: ['release-sentinel'], identityName: 'Iris', } const agentActivityFixture = [ { id: 81, type: 'task', message: 'Verified the OpenClaw content inventory.', at: now, source: 'activity', relativeTime: 'just now', }, ] const agentSummaryFixture = { now: { text: 'Verified the OpenClaw content inventory.', source: 'nexus-activity', timestamp: now, }, today: { text: 'Last 24h: Verified the OpenClaw content inventory.', source: 'nexus-activity', timestamp: now, }, generatedAt: now, } const standardAgentFiles = [ 'AGENTS.md', 'SOUL.md', 'TOOLS.md', 'IDENTITY.md', 'USER.md', 'HEARTBEAT.md', 'BOOTSTRAP.md', 'MEMORY.md', ] as const const agentFileCollectionFixture = { agentId: 'iris', files: standardAgentFiles.map((name, index) => ({ name, missing: false, size: 64 + index, updatedAt: now, contentHash: `sha256-e2e-${name.toLowerCase().replace('.', '-')}`, })), checkedAt: now, } const docFixture = { name: 'mission-control.md', path: DOC_PATH, category: 'nexus', type: 'md', size: 112, modifiedAt: now, sourceAgentId: 'iris', workspacePath: DOC_PATH, } const docDetailFixture = { name: docFixture.name, path: docFixture.path, content: '# Mission Control Runbook\n\nOpenClaw-backed documentation is visible in Nexus.', size: docFixture.size, modifiedAt: docFixture.modifiedAt, sourceAgentId: docFixture.sourceAgentId, workspacePath: docFixture.workspacePath, } const memoryFixture = { name: MEMORY_NAME, path: MEMORY_NAME, size: 96, modifiedAt: now, sourceAgentId: 'iris', workspacePath: `memory/${MEMORY_NAME}`, } const memoryDetailFixture = { ...memoryFixture, content: '# Daily Memory\n\nAgent-first context is loaded from Iris.', } const memorySearchFixture = { name: memoryFixture.name, path: memoryFixture.path, excerpt: 'Agent-first context is loaded from Iris.', size: memoryFixture.size, sourceAgentId: memoryFixture.sourceAgentId, workspacePath: memoryFixture.workspacePath, } const incidentFixture = { name: INCIDENT_NAME, title: 'Gateway Timeout Recovered', date: '2026-07-30', severity: 'major', excerpt: 'The gateway recovered without duplicate provisioning.', size: 144, sourceAgentId: 'iris', workspacePath: `memory/incidents/${INCIDENT_NAME}`, } const incidentDetailFixture = { name: incidentFixture.name, title: incidentFixture.title, date: incidentFixture.date, content: [ '# Gateway Timeout Recovered', '', '**Severity:** major', '', 'The gateway recovered without duplicate provisioning.', ].join('\n'), size: incidentFixture.size, sourceAgentId: incidentFixture.sourceAgentId, workspacePath: incidentFixture.workspacePath, } const securityStatusFixture = { authMethod: 'JWT + PBKDF2', tokenConfig: { issuer: 'nexus', audience: 'nexus-web', refreshTokenDays: 7, accessTokenMinutes: 30, }, rateLimit: '5 login attempts per minute per IP', passwordPolicy: 'Minimum 10 characters', cookieConfig: { httpOnly: true, secure: true, sameSite: 'Strict', }, twoFactorEnabled: false, passkeyEnabled: false, checkedAt: now, } function buildProposal(status: ProposalStatus = 'awaiting_approval'): Record { return { id: PROPOSAL_ID, source: 'manual', requestedName: 'Release Sentinel', requestedAgentId: 'release-sentinel', role: 'Release Operations', description: 'Verifies release readiness and reports blockers.', model: 'openai/gpt-5.4', emoji: null, avatar: null, workspace: '/managed/agents/release-sentinel', files: [ { name: 'SOUL.md', contentHash: 'abc123def456abc123def456', size: 42, content: '# Release Sentinel\nVerify before reporting.', }, ], status, requestedBy: 'Bao Owner', approvedBy: status === 'ready' ? 'Bao Owner' : null, rejectedBy: null, rejectionReason: null, openClawAgentId: status === 'ready' ? 'release-sentinel' : null, openClawWorkspace: status === 'ready' ? '/managed/agents/release-sentinel' : null, error: null, revision: status === 'ready' ? 2 : 1, createdAt: now, updatedAt: now, approvedAt: status === 'ready' ? now : null, rejectedAt: null, completedAt: status === 'ready' ? now : null, } } function openClawOverview() { const agents = [ { id: 'iris', name: 'Iris', description: 'Coordinates E2E work.', model: 'openai/gpt-5.4', provider: 'openai', workspace: '/managed/agents/iris', status: 'ready', }, ] return { connection: { state: 'connected', configured: true, credentialConfigured: true, connected: true, endpoint: 'ws://openclaw-gateway:18789', gatewayVersion: '2026.7.1', requiredVersion: '2026.7.1', versionPinned: true, versionMatches: true, protocolVersion: 4, grantedScopes: ['operator.read', 'operator.admin'], advertisedEvents: [], lastConnectedAt: now, lastEventAt: now, reconnectAttempts: 0, message: 'Connected', recovery: null, checkedAt: now, deviceId: 'nexus-e2e', pairingRequired: false, pairingRequestId: null, }, capabilities: [], tasks: collection([]), sessions: collection([]), cronJobs: collection([]), approvals: collection([]), activity: collection([]), models: collection([]), agents: collection(agents), generatedAt: now, } } function operationsSnapshot() { return { generatedAt: now, runtime: { runtime: 'OpenClaw', status: 'Connected', detail: 'E2E fixture', }, models: [], metrics: { activeAgents: 1, queuedTasks: 2, successRate: 100, incidents: 0, }, projects: [], tasks: [], activity: [], } } async function fulfillJson(route: Route, body: unknown, status = 200) { await route.fulfill({ status, contentType: 'application/json', body: JSON.stringify(body), }) } function requestBody(request: Request): unknown { const raw = request.postData() if (!raw) return null try { return JSON.parse(raw) } catch { return raw } } function problem(detail: string, status: number) { return { type: 'about:blank', title: status === 403 ? 'Forbidden' : 'Not found', status, detail, } } function operationResult( status: string, primaryRef: { type: string; id: string; label?: string | null }, affectedRefs: Array<{ type: string; id: string; label?: string | null }> = [], ) { return { operationId: `e2e-${status}-${primaryRef.id}`, status, revision: 1, primaryRef, affectedRefs, traceId: 'e2e-trace-1', } } export async function mockNexusApi( page: Page, options: MockOptions = {}, ): Promise { const authenticated = options.authenticated ?? true const role = options.role ?? 'owner' const proposalRequests: CapturedRequest[] = [] const boardRequestUrls: string[] = [] const domainEventRequests: Array<{ lastEventId: string | null }> = [] const taskMutationRequests: CapturedRequest[] = [] const chatRequests: CapturedRequest[] = [] let proposal = buildProposal(options.proposalStatus) let resyncResponseCount = 0 let boardInitialRequestCount = 0 let openClawOverviewRequestCount = 0 let browserTelemetryRequestCount = 0 let injectedFailureCount = 0 let openTaskState = 'Backlog' await page.route('**/api/**', async route => { const request = route.request() const url = new URL(request.url()) const { pathname: path } = url const method = request.method() // The Vite source graph contains paths such as /src/api/client.ts, which // also match Playwright's **/api/** glob. Only intercept the actual HTTP // API boundary and let application modules continue to the dev server. if (!path.startsWith('/api/')) { await route.fallback() return } if ( options.apiFailure && path === options.apiFailure.path && injectedFailureCount < (options.apiFailure.attempts ?? 1) ) { injectedFailureCount += 1 await fulfillJson( route, problem( 'The requested Nexus dependency is temporarily unavailable.', options.apiFailure.status, ), options.apiFailure.status, ) return } if (path === '/api/v1/auth/refresh' && method === 'POST') { if (!authenticated) { await fulfillJson(route, problem('No active E2E session.', 401), 401) return } await fulfillJson(route, { accessToken: `e2e-${role}-token`, expiresAt: '2026-08-01T10:00:00.000Z', user: { id: role === 'owner' ? 'owner-e2e' : 'member-e2e', email: role === 'owner' ? 'bao@example.test' : 'viewer@example.test', displayName: role === 'owner' ? 'Bao Owner' : 'Nexus Viewer', role, }, }) return } if (path === '/api/v1/auth/logout' && method === 'POST') { await route.fulfill({ status: 204 }) return } if (path === '/api/v1/events') { const lastEventId = request.headers()['last-event-id'] ?? null domainEventRequests.push({ lastEventId }) if (options.domainResyncSequence && resyncResponseCount === 0) { const deadline = Date.now() + 2_000 while (!boardRequestUrls.some(value => !value.includes('doneCursor=')) && Date.now() < deadline) { await new Promise(resolve => setTimeout(resolve, 20)) } // Let the initial query paint before the stream requests a REST resync. await new Promise(resolve => setTimeout(resolve, 120)) resyncResponseCount += 1 await route.fulfill({ status: 200, contentType: 'text/event-stream', headers: { 'Cache-Control': 'no-cache' }, body: [ `id: ${options.domainResyncSequence}`, 'event: resync_required', `data: ${JSON.stringify({ sequence: options.domainResyncSequence, eventType: 'resync_required', entity: { type: 'event-stream', id: '*', label: null }, entityRevision: `stream-${options.domainResyncSequence}`, occurredAt: now, payload: { reason: 'stale_or_missing_cursor' }, })}`, '', '', ].join('\n'), }) return } const cursorLine = options.domainResyncSequence ? `id: ${options.domainResyncSequence}\n` : '' await route.fulfill({ status: 200, contentType: 'text/event-stream', headers: { 'Cache-Control': 'no-cache' }, body: `${cursorLine}event: heartbeat\ndata: {}\n\n`, }) return } if (path === '/api/v1/projects' && method === 'GET') { await fulfillJson(route, [projectFixture]) return } if (path === '/api/v1/activity' && method === 'GET') { await fulfillJson(route, { items: [{ id: 71, type: 'task.updated', message: 'Nexus task deep link is ready.', at: now, entity: { type: 'task', id: openTask.id, label: openTask.title, }, }], totalCount: 1, page: 1, pageSize: 200, totalPages: 1, }) return } if (path === `/api/v1/projects/${PROJECT_ID}` && method === 'GET') { await fulfillJson(route, projectFixture) return } if (path === `/api/v1/projects/${PROJECT_ID}/tasks` && method === 'GET') { await fulfillJson(route, [projectTask]) return } if (path === '/api/v1/tasks' && method === 'GET') { await fulfillJson(route, [projectTask, unrelatedTask]) return } if (path === '/api/v1/operations/snapshot') { await fulfillJson(route, operationsSnapshot()) return } if (path === '/api/v1/routing') { await fulfillJson(route, []) return } if (path === '/api/v1/openclaw/overview') { openClawOverviewRequestCount += 1 await fulfillJson(route, openClawOverview()) return } if (path === '/api/v1/openclaw/capabilities') { await fulfillJson(route, []) return } if (path === '/api/v1/openclaw/models/auth-status' && method === 'GET') { await fulfillJson(route, collection([{ provider: 'openai', displayName: 'OpenAI', status: 'ok', expiry: null, profiles: [{ type: 'oauth', status: 'ok', count: 1 }], apiKey: null, usage: { summary: 'Ready', plan: 'team' }, }])) return } if (path === '/api/v1/openclaw/agents' && method === 'GET') { await fulfillJson(route, openClawOverview().agents) return } if (path === '/api/v1/agents/iris' && method === 'GET') { await fulfillJson(route, agentDetailFixture) return } if (path === '/api/v1/agents/iris/activity' && method === 'GET') { await fulfillJson(route, agentActivityFixture) return } if (path === '/api/v1/agents/iris/summary' && method === 'GET') { await fulfillJson(route, agentSummaryFixture) return } if ( path === '/api/v1/openclaw/agents/iris/files' && method === 'GET' ) { if (role !== 'owner') { await fulfillJson(route, problem('Owner access is required.', 403), 403) return } await fulfillJson(route, agentFileCollectionFixture) return } const agentFileMatch = path.match( /^\/api\/v1\/openclaw\/agents\/iris\/files\/([^/]+)$/, ) if (agentFileMatch && method === 'GET') { if (role !== 'owner') { await fulfillJson(route, problem('Owner access is required.', 403), 403) return } const fileName = decodeURIComponent(agentFileMatch[1]) const summary = agentFileCollectionFixture.files.find( file => file.name === fileName, ) if (!summary) { await fulfillJson(route, problem('Agent file not found.', 404), 404) return } await fulfillJson(route, { agentId: 'iris', ...summary, content: `# ${fileName}\n\nOpenClaw returned the authoritative Iris file.`, checkedAt: now, }) return } if ( path === '/api/v1/openclaw/agents/iris/workspace' && method === 'GET' ) { if (role !== 'owner') { await fulfillJson(route, problem('Owner access is required.', 403), 403) return } await fulfillJson(route, { agentId: 'iris', path: url.searchParams.get('path') ?? '', parentPath: null, entries: [], totalEntries: 0, offset: Number(url.searchParams.get('offset') ?? 0), checkedAt: now, }) return } if (path === '/api/v1/docs' && method === 'GET') { if (role !== 'owner') { await fulfillJson(route, problem('Owner access is required.', 403), 403) return } await fulfillJson(route, [docFixture]) return } if (path.startsWith('/api/v1/docs/') && method === 'GET') { if (role !== 'owner') { await fulfillJson(route, problem('Owner access is required.', 403), 403) return } const requestedPath = decodeURIComponent( path.slice('/api/v1/docs/'.length), ) await fulfillJson( route, requestedPath === DOC_PATH ? docDetailFixture : problem('Document not found.', 404), requestedPath === DOC_PATH ? 200 : 404, ) return } if (path === '/api/v1/memory' && method === 'GET') { if (role !== 'owner') { await fulfillJson(route, problem('Owner access is required.', 403), 403) return } await fulfillJson(route, [memoryFixture]) return } if (path === '/api/v1/memory/search' && method === 'GET') { if (role !== 'owner') { await fulfillJson(route, problem('Owner access is required.', 403), 403) return } const query = url.searchParams.get('q')?.toLocaleLowerCase() ?? '' await fulfillJson( route, query.includes('agent') ? [memorySearchFixture] : [], ) return } const memoryMatch = path.match(/^\/api\/v1\/memory\/([^/]+)$/) if (memoryMatch && method === 'GET') { if (role !== 'owner') { await fulfillJson(route, problem('Owner access is required.', 403), 403) return } const requestedName = decodeURIComponent(memoryMatch[1]) await fulfillJson( route, requestedName === MEMORY_NAME ? memoryDetailFixture : problem('Memory file not found.', 404), requestedName === MEMORY_NAME ? 200 : 404, ) return } if (path === '/api/v1/incidents' && method === 'GET') { if (role !== 'owner') { await fulfillJson(route, problem('Owner access is required.', 403), 403) return } await fulfillJson(route, [incidentFixture]) return } const incidentMatch = path.match(/^\/api\/v1\/incidents\/([^/]+)$/) if (incidentMatch && method === 'GET') { if (role !== 'owner') { await fulfillJson(route, problem('Owner access is required.', 403), 403) return } const requestedName = decodeURIComponent(incidentMatch[1]) await fulfillJson( route, requestedName === INCIDENT_NAME ? incidentDetailFixture : problem('Incident not found.', 404), requestedName === INCIDENT_NAME ? 200 : 404, ) return } if (path === '/api/v1/security/status' && method === 'GET') { await fulfillJson(route, securityStatusFixture) return } if (path === '/api/v1/openclaw/runs' && method === 'GET') { await fulfillJson(route, { items: url.searchParams.get('projectId') === PROJECT_ID ? [projectRunFixture] : [], nextCursor: null, checkedAt: now, }) return } if (path === '/api/dashboard/chat/messages' && method === 'GET') { await fulfillJson(route, []) return } if (path === '/api/v1/chat' && method === 'POST') { chatRequests.push({ path, method, headers: request.headers(), body: requestBody(request), }) await fulfillJson(route, { runtime: 'OpenClaw Protocol v4', agentId: 'iris', conversationId: 'e2e-iris-conversation', content: 'OpenClaw accepted the Iris request.', runId: 'run-chat-e2e-1', state: 'running', operation: { operationId: 'run-chat-e2e-1', status: 'running', revision: 1, primaryRef: { type: 'run', id: 'run-chat-e2e-1', label: 'Iris agent proposal run', }, affectedRefs: [], traceId: null, }, }) return } if (path === '/api/dashboard/notifications/unread-count') { await fulfillJson(route, { count: 0 }) return } if (path === '/api/dashboard/notifications/snapshot') { await fulfillJson(route, { notifications: [{ id: '33333333-3333-4333-8333-333333333333', type: 'task_assigned', title: 'Inspect E2E backlog task', message: 'A linked task requires attention.', forUser: 'bao', taskId: openTask.id, isRead: false, createdAt: now, }], unreadCount: 1, forUser: 'bao', }) return } if ( /^\/api\/dashboard\/notifications\/[^/]+\/read$/.test(path) && method === 'PATCH' ) { const notificationId = path.split('/').at(-2)! await fulfillJson(route, { id: notificationId, type: 'task_assigned', title: 'Inspect E2E backlog task', message: 'A linked task requires attention.', forUser: 'bao', taskId: openTask.id, isRead: true, createdAt: now, operation: operationResult( 'completed', { type: 'notification', id: notificationId, label: 'Inspect E2E backlog task' }, [{ type: 'task', id: openTask.id, label: openTask.title }], ), }) return } if (path === '/api/dashboard/notifications/read-all' && method === 'PATCH') { await fulfillJson(route, { marked: 1, operation: operationResult('completed', { type: 'notification', id: '*', label: 'Benachrichtigungen', }), }) return } if (path === '/api/v1/telemetry/browser') { browserTelemetryRequestCount += 1 await route.fulfill({ status: 202 }) return } if (path === '/api/dashboard/agents') { await fulfillJson(route, [ { id: 'iris', name: 'Iris', role: 'Chief of Staff', description: 'Coordinates E2E work.', tags: ['Orchestration'], model: 'openai/gpt-5.4', statusLabel: 'Bereit', statusKind: 'ready', statusDetail: 'Mock runtime is ready.', isActive: false, progress: null, currentTask: null, }, ]) return } const isProposalRequest = path === '/api/v1/openclaw/agents/create-options' || path.startsWith('/api/v1/openclaw/agent-proposals') if (isProposalRequest && role !== 'owner') { await fulfillJson(route, problem('Owner access is required.', 403), 403) return } if (path === '/api/v1/openclaw/agents/create-options' && method === 'GET') { await fulfillJson(route, { canSubmitProposal: true, canProvision: true, state: 'ready', reason: null, workspaceRoot: '/managed/agents', existingAgentIds: ['iris'], models: [ { id: 'openai/gpt-5.4', name: 'GPT-5.4', provider: 'OpenAI', available: true, }, { id: 'unavailable/model', name: 'Unavailable', provider: 'Fixture', available: false, }, ], standardFiles: ['AGENTS.md', 'SOUL.md', 'TOOLS.md'], checkedAt: now, }) return } if (path === '/api/v1/openclaw/agent-proposals' && method === 'GET') { await fulfillJson(route, { items: [proposal], nextCursor: null, checkedAt: now, }) return } if (path === '/api/v1/openclaw/agent-proposals' && method === 'POST') { const body = requestBody(request) as Record proposalRequests.push({ path, method, headers: request.headers(), body, }) const fileEntries = body.files && typeof body.files === 'object' ? Object.entries(body.files as Record) : [] const requestedName = String(body.name || 'Unnamed Agent') proposal = { ...buildProposal('awaiting_approval'), requestedName, requestedAgentId: requestedName.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/(^-|-$)/g, ''), role: body.role || null, description: body.description || null, model: body.model || null, files: fileEntries.map(([name, content], index) => ({ name, contentHash: `e2e-hash-${index}`, size: content.length, content, })), } await fulfillJson(route, { ok: true, state: 'awaiting_approval', message: 'Agent-Vorschlag wurde angelegt.', proposal, recovery: null, correlationId: request.headers()['x-correlation-id'] ?? null, completedAt: now, }, 201) return } if (path === `/api/v1/openclaw/agent-proposals/${PROPOSAL_ID}` && method === 'GET') { await fulfillJson(route, proposal) return } const actionMatch = path.match( new RegExp(`^/api/v1/openclaw/agent-proposals/${PROPOSAL_ID}/(approve|reject|retry)$`), ) if (actionMatch && method === 'POST') { const action = actionMatch[1] const body = requestBody(request) proposalRequests.push({ path, method, headers: request.headers(), body, }) if (action === 'approve' || action === 'retry') { proposal = { ...proposal, status: 'ready', approvedBy: 'Bao Owner', approvedAt: now, openClawAgentId: proposal.requestedAgentId, openClawWorkspace: proposal.workspace, completedAt: now, updatedAt: now, revision: Number(proposal.revision) + 1, } } else { const actionBody = body as Record proposal = { ...proposal, status: 'rejected', rejectedBy: 'Bao Owner', rejectionReason: actionBody.reason || 'Rejected in E2E', rejectedAt: now, completedAt: now, updatedAt: now, revision: Number(proposal.revision) + 1, } } await fulfillJson(route, { ok: true, state: proposal.status, message: action === 'reject' ? 'Agent-Vorschlag wurde abgelehnt.' : 'Agent ist bereit.', proposal, recovery: null, correlationId: request.headers()['x-correlation-id'] ?? null, completedAt: now, }, action === 'reject' ? 200 : 202) return } if (path === '/api/v1/tasks/board' && method === 'GET') { boardRequestUrls.push(url.toString()) const cursor = url.searchParams.get('doneCursor') if (cursor) { await fulfillJson(route, { revision: 'board-r1', offen: [], inProgress: [], review: [], blocked: [], done: [secondDoneTask], nextDoneCursor: null, hasMoreDone: false, }) } else { boardInitialRequestCount += 1 if (boardInitialRequestCount > 1 && options.boardRefreshDelayMs) { await new Promise(resolve => setTimeout(resolve, options.boardRefreshDelayMs)) } const currentOpenTask = { ...openTask, title: boardInitialRequestCount > 1 && options.boardRefreshTitle ? options.boardRefreshTitle : openTask.title, state: openTaskState, } const columns = { offen: openTaskState === 'Backlog' ? [currentOpenTask] : [], inProgress: openTaskState === 'In progress' ? [currentOpenTask, progressTask] : [progressTask], review: openTaskState === 'Review' ? [currentOpenTask] : [], blocked: openTaskState === 'Blocked' ? [currentOpenTask] : [], done: openTaskState === 'Done' ? [currentOpenTask, firstDoneTask] : [firstDoneTask], } await fulfillJson(route, { revision: boardInitialRequestCount > 1 && options.boardRefreshTitle ? 'board-r2' : 'board-r1', ...columns, nextDoneCursor: 'done-page-2', hasMoreDone: true, }) } return } if (path === `/api/dashboard/tasks/${openTask.id}` && method === 'GET') { await fulfillJson(route, { ...openTask, state: openTaskState, }) return } if (path === `/api/dashboard/tasks/${openTask.id}/children` && method === 'GET') { await fulfillJson(route, []) return } if (path === `/api/dashboard/tasks/${openTask.id}/activity` && method === 'GET') { await fulfillJson(route, []) return } const boardCardMatch = path.match(/^\/api\/v1\/tasks\/([^/]+)\/board-card$/) if (boardCardMatch && method === 'GET') { const taskId = decodeURIComponent(boardCardMatch[1]) if (taskId !== openTask.id) { await fulfillJson(route, problem('Task not found.', 404), 404) return } await fulfillJson(route, { ...openTask, state: openTaskState, updatedAt: now, }) return } const moveTaskMatch = path.match(/^\/api\/dashboard\/tasks\/([^/]+)\/move$/) if (moveTaskMatch && method === 'PATCH') { const body = requestBody(request) as Record taskMutationRequests.push({ path, method, headers: request.headers(), body, }) const requestedState = String(body.state || 'Backlog') const canonicalStates: Record = { offen: 'Backlog', inProgress: 'In progress', review: 'Review', blocked: 'Blocked', done: 'Done', } if (decodeURIComponent(moveTaskMatch[1]) === openTask.id) { openTaskState = canonicalStates[requestedState] ?? requestedState } await fulfillJson(route, { ...openTask, state: openTaskState, updatedAt: now, operation: operationResult('state_updated', { type: 'task', id: openTask.id, label: openTask.title, }), }) return } if (path === '/api/dashboard/tasks/agent-overview') { await fulfillJson(route, { waitingForBao: [], waitingForIris: [], waitingForOthers: [], staleTasks: [], staleThreshold: '02:00:00', }) return } if (path === '/api/dashboard/tasks') { await fulfillJson(route, [openTask, progressTask, firstDoneTask]) return } if (path === '/api/dashboard/tasks/board') { await fulfillJson(route, { offen: [openTask], inProgress: [progressTask], review: [], blocked: [], done: [firstDoneTask], }) return } if (path === '/api/dashboard/status') { await fulfillJson(route, { gatewayOk: true, irisStatus: 'Ready', activeAgents: 1, pendingTasks: 2, }) return } if (path === '/api/dashboard/operations' || path === '/api/dashboard/queue') { await fulfillJson(route, []) return } await fulfillJson(route, problem(`No E2E fixture for ${method} ${path}.`, 404), 404) }) return { proposalRequests, boardRequestUrls, domainEventRequests, taskMutationRequests, chatRequests, get resyncResponseCount() { return resyncResponseCount }, get openClawOverviewRequestCount() { return openClawOverviewRequestCount }, get browserTelemetryRequestCount() { return browserTelemetryRequestCount }, get proposal() { return proposal }, } }