import { test } from 'node:test'; import assert from 'node:assert/strict'; import { mkdtempSync, mkdirSync, writeFileSync, appendFileSync, rmSync } from 'node:fs'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { startServer as startBoard } from '../../control-board/src/serve.mjs'; import { makeRegistration, writeRegistration } from '../../seat/src/seat.mjs'; import { startServer } from '../src/serve.mjs'; import { browser } from './browser.mjs'; import { close } from './fixture.mjs'; const oldAt = '2026-09-14T10:00:00.000Z', launchAt = '2026-09-15T10:00:00.000Z', newAt = '2026-09-15T10:01:00.000Z'; const notice = `relaunched at ${launchAt}, no messages since`; test('both presentations replace old activity with relaunch notice, label retained history, then resume after new activity', { timeout: 60000 }, async () => { const root = mkdtempSync(join(tmpdir(), 'webui-relaunch-')); let board, web, b; try { const sessionsDir = join(root, 'sessions'); mkdirSync(sessionsDir); const file = join(sessionsDir, 's.jsonl'); const line = (at, text) => JSON.stringify({ type: 'message', timestamp: at, message: { role: 'assistant', stopReason: 'stop', content: [{ type: 'text', text }] } }) + '\n'; const history = line(oldAt, 'Input needed: OLD_FIXTURE_REPLY '); writeFileSync(file, history); const seatsDir = join(root, 'seats'); writeRegistration(seatsDir, { ...makeRegistration({ resolved: { seat: 'fixture', project: 'repo', sessionsDir, seatDir: root, launchScript: join(root, 'unused.sh'), layout: 'repo', defaultWorkspace: root }, task: 'Fixed task', tmux: { session: 'fixture', socket: null }, pid: process.pid, now: () => new Date(launchAt) }), taskSetBy: 'fixture-setter' }); board = await startBoard({ port: 0, specs: [{ agent: 'fixture', project: 'repo', sessionsDir, tmux: {} }], boardDir: join(root, 'board'), seatsDir, isAlive: () => true, isPidAlive: () => true, exec: () => { throw Error('no transport expected'); } }); const boardURL = `http://127.0.0.1:${board.address().port}`; web = await startServer({ port: 0, board: boardURL }); const webURL = `http://127.0.0.1:${web.address().port}`; const row = (await (await fetch(webURL + '/api/board')).json()).sessions[0]; assert.equal(row.relaunchedAt, launchAt); assert.equal(row.lastActivity, oldAt); assert.match(row.lastAssistantText, /OLD_FIXTURE_REPLY/); assert.equal(row.state, 'waiting'); assert.equal(row.taskSetBy, 'fixture-setter'); b = await browser(); await b.viewport(320, 1000); const wait = expression => b.evaluate(`(async()=>{for(let i=0;i<100;i++){if(${expression})return true;await new Promise(r=>setTimeout(r,50))}throw Error('timeout')})()`); await b.navigate(boardURL); await wait('document.querySelector(".session-row")'); const legacyRow = await b.evaluate('document.querySelector(".session-row").textContent'); assert.ok(legacyRow.includes(notice)); assert.equal(legacyRow.includes('OLD_FIXTURE_REPLY'), false); await b.evaluate('document.querySelector(".row-toggle").click()'); assert.match(await b.evaluate('document.querySelector(".detail-row").textContent'), /Historical last message.*OLD_FIXTURE_REPLY/s); assert.equal(await b.evaluate('!!document.querySelector("img")'), false); assert.equal(await b.evaluate('document.documentElement.scrollWidth<=innerWidth'), true); await b.navigate(webURL); await wait('document.querySelector("table.sessions [data-open]")'); assert.ok((await b.evaluate('document.querySelector("table.sessions").textContent')).includes(notice)); const card = await b.evaluate('document.querySelector("#waiting").textContent'); assert.ok(card.includes(notice)); assert.equal(card.includes('OLD_FIXTURE_REPLY'), false); await b.evaluate('document.querySelector("table.sessions [data-open]").click()'); const inspector = await b.evaluate('document.querySelector("#inspection").textContent'); assert.ok(inspector.includes(notice)); assert.match(inspector, /Historical last assistant text.*OLD_FIXTURE_REPLY/s); assert.match(inspector, /Historical last activity/); assert.match(inspector, /fixture-setter/); assert.equal(await b.evaluate('!!document.querySelector("#reply")'), true, 'reply eligibility unchanged'); assert.equal(await b.evaluate('!!document.querySelector("img")'), false); assert.equal(await b.evaluate('document.documentElement.scrollWidth<=innerWidth'), true); appendFileSync(file, line(newAt, 'NEW_FIXTURE_REPLY')); await b.evaluate('document.querySelector("#refresh").click()'); await wait('document.querySelector("#inspection").textContent.includes("NEW_FIXTURE_REPLY")'); assert.equal((await b.evaluate('document.querySelector("#inspection").textContent')).includes('relaunched at'), false); assert.equal((await b.evaluate('document.querySelector("#inspection").textContent')).includes('Historical last'), false); await b.navigate(boardURL); await wait('document.querySelector(".session-row")?.textContent.includes("NEW_FIXTURE_REPLY")'); assert.equal((await b.evaluate('document.querySelector(".session-row").textContent')).includes('relaunched at'), false); } finally { if (b) await b.close(); if (web) await close(web); if (board) await close(board); rmSync(root, { recursive: true, force: true }); } });