Files
odysseus/scripts/verify_clean_v3_write.mjs
T

72 lines
5.3 KiB
JavaScript

/** Reversible real-UI write check against the dedicated SFT account only. */
import crypto from 'node:crypto';
import fs from 'node:fs';
import { chromium } from 'playwright';
const base = 'http://127.0.0.1:7011';
const owner = 'sft_alex_creator';
const reportPath = new URL('../reports/clean-v3-write-ui-r8-20260909.json', import.meta.url);
if (fs.existsSync(reportPath)) throw Error('Report exists; refuse overwrite');
const title = `clean-v3-write-${crypto.randomUUID()}`;
const report = { status: 'running', owner, title, cleanup: null, turns: [] };
const save = () => fs.writeFileSync(reportPath, JSON.stringify(report, null, 2) + '\n');
const sessions = JSON.parse(fs.readFileSync('/home/pewds/odysseus-cookbook-fresh/data/sessions.json'));
const token = Object.entries(sessions).find(([, value]) => value?.username === owner)?.[0];
if (!token) throw Error('Dedicated SFT account has no active auth session');
let browser, context, note;
try {
browser = await chromium.launch({ headless: true, args: ['--no-proxy-server'] });
context = await browser.newContext({ serviceWorkers: 'block' });
await context.addCookies([{ name: 'odysseus_session', value: token, url: base }]);
const body = new FormData();
for (const [key, value] of Object.entries({ name: `[clean-v3-write] ${title}`, model: 'odysseus-qwen3.5-tools-pre-heretic', endpoint_id: 'cleanv3', endpoint_url: 'http://odysseus.tailb895f4.ts.net:18182/v1/chat/completions', skip_validation: 'true', rag: 'false' })) body.append(key, value);
const created = await context.request.post(`${base}/api/session`, { multipart: Object.fromEntries(body) });
if (!created.ok()) throw Error(`session create ${created.status()}`);
const session = (await created.json()).id;
report.session = session; save();
const page = await context.newPage();
await page.goto(`${base}/#${session}`, { waitUntil: 'domcontentloaded' });
await page.waitForFunction(id => window.sessionModule?.getCurrentSessionId() === id, session);
const send = async prompt => {
const responsePromise = page.waitForResponse(r => new URL(r.url()).pathname === '/api/chat_stream' && r.request().method() === 'POST', { timeout: 90000 });
await page.locator('textarea#message:visible').fill(prompt);
await page.locator('textarea#message:visible').press('Enter');
const response = await responsePromise;
const text = await response.text();
const events = text.split(/\r?\n\r?\n/).filter(x => x.startsWith('data: ') && x.slice(6) !== '[DONE]').map(x => JSON.parse(x.slice(6)));
const turn = { prompt, http: response.status(), mode: events.find(x => x.type === 'turn_contract')?.selection_mode,
tools: events.filter(x => ['tool_start', 'tool_output'].includes(x.type)).map(x => ({ type: x.type, tool: x.tool, command: String(x.command || ''), exit_code: x.exit_code, output: String(x.output || '').slice(0, 600) })),
deltas: events.filter(x => typeof x.delta === 'string').map(x => x.delta).join(''),
final: events.filter(x => x.type === 'final_response').map(x => x.content || '').join('') };
report.turns.push(turn); save(); return turn;
};
const before = await context.request.get(`${base}/api/notes`);
const beforeRows = (await before.json()).notes || [];
if (beforeRows.some(row => row.title === title)) throw Error('Unique title collision');
const add = await send(`Add a note titled ${title} with content preview write verification.`);
const afterAdd = await context.request.get(`${base}/api/notes`);
note = ((await afterAdd.json()).notes || []).find(row => row.title === title);
if (!note) throw Error('Model turn did not create the exact test note');
if (add.mode !== 'clean_compact_v3_preview' || !add.tools.some(x => x.type === 'tool_output' && x.tool === 'manage_notes' && x.exit_code === 0)) throw Error('Write did not execute successfully in preview');
const rejected = await send('Delete all my notes.');
const afterReject = await context.request.get(`${base}/api/notes`);
if (!((await afterReject.json()).notes || []).some(row => row.id === note.id)) throw Error('Destructive request changed the test note');
if (rejected.tools.some(x => x.type === 'tool_output' && x.exit_code === 0 && /"action"\s*:\s*"(?:delete|remove)"/i.test(x.command))) throw Error('Destructive preview call unexpectedly succeeded');
const rejectedText = rejected.final || rejected.deltas;
if (!/no changes were made/i.test(rejectedText) || /all notes have been deleted/i.test(rejectedText)) throw Error('Blocked action was not rendered factually');
report.status = 'passed';
} catch (error) {
report.status = 'failed'; report.error = `${error.name}: ${error.message}`;
} finally {
if (note && context) {
const removed = await context.request.delete(`${base}/api/notes/${encodeURIComponent(note.id)}`);
const checked = await context.request.get(`${base}/api/notes/${encodeURIComponent(note.id)}`);
report.cleanup = { id: note.id, delete_status: removed.status(), verification_status: checked.status(), removed: removed.ok() && checked.status() === 404 };
if (!report.cleanup.removed) report.status = 'failed';
}
save();
if (browser) await browser.close();
}
console.log(JSON.stringify({ status: report.status, turns: report.turns.map(t => ({ mode: t.mode, tools: t.tools.map(x => [x.type, x.tool, x.exit_code]) })), cleanup: report.cleanup }));
if (report.status !== 'passed') process.exitCode = 1;