History opens a seat's conversation from the Waiting card, table row and inspector. It pages the whole branch through the CHAT-02 board routes, renders untrusted text inert, polls with the follow cursor, and marks every switch (branch, newer, reconcile, gone). The WebUI proxy passes only the two conversation routes' queries upstream. Dewey authored it. Filbert asked for changes on r1 (24b046af) and approved r2 (d06de6a7) in review 160dd68d. A relaunch shows 'newer', not 'reconcile', a deviation from brief 2.3 item 6 that Filbert accepted. Co-Authored-By: Claude Opus 5.5 <[email protected]>
52 lines
3.6 KiB
JavaScript
52 lines
3.6 KiB
JavaScript
// Repository-layout fixture for the conversation view (#1507, CHAT-02). The
|
|
// board's history reader reads only <projectRoot>/.pi/state/<seat>/sessions,
|
|
// with the project named after the root directory and a Pi header whose cwd is
|
|
// inside the project. Real board, real routes, real WebUI; temporary data only.
|
|
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 { close } from './fixture.mjs';
|
|
|
|
export const at = s => new Date(Date.now() + s * 1000).toISOString();
|
|
|
|
// Appends Pi entries to one session file, each the child of the one before.
|
|
export function session(file, cwd, { id = 'sess-1', timestamp = at(-600) } = {}) {
|
|
let n = 0, parent = null;
|
|
writeFileSync(file, JSON.stringify({ type: 'session', version: 3, id, timestamp, cwd }) + '\n');
|
|
return {
|
|
add(message, timestamp = at(0)) {
|
|
const entry = `${id}-e${++n}`;
|
|
appendFileSync(file, JSON.stringify({ type: 'message', id: entry, parentId: parent, timestamp, message }) + '\n');
|
|
parent = entry;
|
|
return entry;
|
|
},
|
|
raw(text) { appendFileSync(file, text + '\n'); },
|
|
};
|
|
}
|
|
export const user = text => ({ role: 'user', content: [{ type: 'text', text }] });
|
|
export const assistant = (content, stopReason = 'stop') => ({ role: 'assistant', stopReason, content: typeof content === 'string' ? [{ type: 'text', text: content }] : content });
|
|
export const toolResult = (toolCallId, text, isError = false) => ({ role: 'toolResult', toolCallId, toolName: 'read', content: [{ type: 'text', text }], isError });
|
|
|
|
export async function historyFixture({ seat = 'fixture', project = 'repo' } = {}) {
|
|
const root = mkdtempSync(join(tmpdir(), 'webui-history-'));
|
|
const projectRoot = join(root, project), sessionsDir = join(projectRoot, '.pi', 'state', seat, 'sessions');
|
|
mkdirSync(sessionsDir, { recursive: true });
|
|
const seatsDir = join(root, 'seats'), captures = [];
|
|
writeRegistration(seatsDir, makeRegistration({ resolved: { seat, project, sessionsDir, seatDir: root, launchScript: join(root, 'unused.sh'), layout: 'repo', defaultWorkspace: projectRoot }, task: 'Fixed task', tmux: { session: seat, socket: null }, pid: process.pid, now: () => new Date(Date.now() - 3600000) }));
|
|
const board = await startBoard({ port: 0, specs: [{ agent: seat, project, sessionsDir, tmux: { session: seat } }], boardDir: join(root, 'board'), seatsDir, isAlive: () => true, isPidAlive: () => true,
|
|
exec: (f, args) => { captures.push(args); return { status: 0, stdout: '', stderr: '' }; } });
|
|
const web = await startServer({ port: 0, board: `http://127.0.0.1:${board.address().port}` });
|
|
return { root, projectRoot, sessionsDir, board, web, base: `http://127.0.0.1:${web.address().port}`, captures,
|
|
async close() { await close(web); await close(board); rmSync(root, { recursive: true, force: true }); },
|
|
};
|
|
}
|
|
|
|
// Browser helpers shared by the history tests.
|
|
export function waiter(b) {
|
|
return (expression, ms = 25000) => b.evaluate(`(async()=>{for(const end=Date.now()+${ms};Date.now()<end;){if(${expression})return true;await new Promise(r=>setTimeout(r,100))}throw Error('timeout: '+${JSON.stringify(expression)})})()`);
|
|
}
|
|
export const turns = b => b.evaluate('[...document.querySelectorAll("#conv-log > li")].map(li => [li.querySelector(".who").textContent, li.querySelector(".conv-text")?.textContent ?? [...li.querySelectorAll("summary")].map(s => s.textContent).join(" | ")])');
|