import { test } from 'node:test'; import assert from 'node:assert/strict'; import { readFile } from 'node:fs/promises'; import { chromium } from 'playwright'; const chat = await readFile(new URL('../static/js/chat.js', import.meta.url), 'utf8'); const documentSource = await readFile(new URL('../static/js/document.js', import.meta.url), 'utf8'); const start = chat.indexOf(' let _ttftDisplayTimer = null;'); const end = chat.indexOf(' const clearFirstTokenWaitTimers =', start); assert.ok(start >= 0 && end > start); const statusCode = chat.slice(start, end); const finishStart = chat.indexOf(' let finishEditorButton = null;'); const finishEnd = chat.indexOf(' let roundFinalized = false;', finishStart); assert.ok(finishStart >= 0 && finishEnd > finishStart); const finishCode = chat.slice(finishStart, finishEnd); test('editor counts use the existing agent status spinner and keep ticking', async () => { const browser = await chromium.launch({ headless: true }); try { const page = await browser.newPage(); await page.route('http://progress.test/**', async route => { const path = new URL(route.request().url()).pathname; if (path === '/') return route.fulfill({ contentType: 'text/html', body: '
' }); const source = await readFile(new URL('..' + path, import.meta.url), 'utf8'); return route.fulfill({ contentType: 'text/javascript', body: source }); }); await page.goto('http://progress.test/'); const result = await page.evaluate(async code => { const { create } = await import('/static/js/spinner.js'); const spinner = create('Processing request', 'right', 'wave'); document.querySelector('main').appendChild(spinner.createElement()); const _ttftStartedAt = performance.now() - 54000; const setup = eval(code + ` _editorProgress = {kind:'suggestions', phase:'preparing'}; _startTtftDisplay(); const preparing = spinner.element.textContent; _editorProgress = {kind:'suggestions', phase:'drafting', proposed:9}; const agentClass = spinner.element.classList.contains('ai-spinner'); window.stopStatus = _stopTtftDisplay; ({preparing, agentClass}); `); await new Promise(resolve => setTimeout(resolve, 220)); const proposed = spinner.element.textContent; window.stopStatus(); return { ...setup, proposed }; }, statusCode); assert.match(result.preparing, /Reviewing · 54\.\ds/); assert.match(result.proposed, /9 proposed · 54\.\ds/); assert.equal(result.agentClass, true); assert.equal(documentSource.includes('doc-ai-progress'), false); } finally { await browser.close(); } }); test('finish action uses the exact run and keeps the agent thread visible', async () => { const browser = await chromium.launch({ headless: true }); try { const page = await browser.newPage(); const seen = []; await page.route('http://progress.test/**', async route => { if (new URL(route.request().url()).pathname === '/') { return route.fulfill({ contentType: 'text/html', body: '
' }); } seen.push({ url: route.request().url(), runId: route.request().headers()['x-odysseus-run-id'] }); return route.fulfill({ contentType: 'application/json', body: '{"accepted":true}' }); }); await page.goto('http://progress.test/'); const result = await page.evaluate(async code => { const lastToolThread = document.createElement('div'); lastToolThread.className = 'agent-thread'; document.querySelector('main').appendChild(lastToolThread); const streamSessionId = 'editor-session'; const _streamRunIds = new Map([[streamSessionId, 'exact-run']]); const API_BASE = ''; const uiModule = { showError() { throw new Error('finish failed'); } }; eval(code + '\nofferFinishEditorTurn();'); const button = lastToolThread.querySelector('button'); const agentStyle = button.classList.contains('continue-btn') && button.classList.contains('resume-btn'); button.click(); await new Promise(resolve => setTimeout(resolve, 60)); return { agentStyle, text: button.textContent, threadVisible: lastToolThread.isConnected }; }, finishCode); assert.deepEqual(result, { agentStyle: true, text: 'Finishing…', threadVisible: true }); assert.equal(seen.length, 1); assert.equal(seen[0].runId, 'exact-run'); assert.match(seen[0].url, /\/api\/chat\/finish\/editor-session$/); } finally { await browser.close(); } });