Oregami
Repositories/oxedyne/daimond

oxedyne/daimond/dev/verify_agents.mjs

5.9 KiB, 1 run

created by r2519314175:229, which is this file's identity for as long as the history lasts, whatever it is later renamed to

download · who wrote it · its history

1// The conductor dispatches workers; a finished worker folds in ONCE (not
2// repeatedly), and finished tiles can be cleared.
3//
4// The header used to claim "a failed worker is not offered a fold" as well.
5// Every worker in this fixture succeeds, so that sentence was never under test
6// and has gone rather than being left standing as a claim the run does not make.
7//
8// "FOLDS IN ONCE" is a claim about what is left afterwards: the card that folded
9// carries the folded mark and is no longer OFFERED the fold, and the offer count
10// drops by exactly one. That is checked per card now; the old version counted
11// buttons in the panel as a whole, which a second card losing its button would
12// also satisfy.
13//
14// PROVED AGAINST A SHORT DISPATCH FIRST. `--break two` steers with two
15// spawn_agent calls instead of three, and the dispatch check must go red.
16//
17// node dev/verify_agents.mjs --break two # expected to FAIL
18// node dev/verify_agents.mjs # and then, clean
19import { open, shot, errors } from './harness.mjs';
20
21const ok = [], bad = [];
22const check = (name, pass, detail) => {
23 (pass ? ok : bad).push(name + (detail ? ' — ' + detail : ''));
24 console.log((pass ? ' ok ' : ' FAIL ') + name + (detail ? ' — ' + detail : ''));
25};
26
27const BREAK = (() => {
28 const i = process.argv.indexOf('--break');
29 return i > 0 ? String(process.argv[i + 1] || '') : '';
30})();
31if (BREAK && BREAK !== 'two') {
32 console.error(`unknown break '${BREAK}'; known: two`);
33 process.exit(2);
34}
35
36/// One row per worker card: what it is called, what state it is in, whether it
37/// carries the folded mark and whether it is still being offered a fold.
38const cards = (page) => page.evaluate(() => [...document.querySelectorAll('#panel-agents .acard')].map(c => ({
39 name: ((c.querySelector('.an') || {}).textContent || '').trim().slice(0, 24),
40 status: ((c.querySelector('.pill') || {}).textContent || '').trim(),
41 folded: !!c.querySelector('.afolded'),
42 offer: [...c.querySelectorAll('.abtn')].some(b => /Fold in/.test(b.textContent)),
43})));
44
45const s = await open({ name: 'agents' });
46if (BREAK) console.log(`\n*** RUNNING UNDER --break ${BREAK}: failures below are the point ***\n`);
47
48// New Diamond.
49await s.page.click('#new-diamond-btn');
50await s.page.waitForSelector('.dlg-input', { timeout: 8000 });
51await s.page.fill('.dlg-input', 'Dispatch Test');
52await s.page.click('.dlg-ok');
53await s.page.waitForTimeout(1000);
54
55// Steer with three spawn_agent calls in one turn.
56const spawns = ['{"name":"alpha","task":"do A"}', '{"name":"beta","task":"do B"}', '{"name":"gamma","task":"do C"}']
57 .slice(0, BREAK === 'two' ? 2 : 3);
58await s.page.fill('#chat-input', '@tools ' + spawns.map(a => 'spawn_agent ' + a).join(' ;; '));
59await s.page.keyboard.press('Enter');
60await s.page.waitForTimeout(1500);
61
62// Open the Agents dock panel.
63await s.page.evaluate(() => window.DaimondPanels.show('agents'));
64await s.page.waitForTimeout(500);
65
66// Wait for the three workers to finish (each runs a mock turn).
67await s.page.waitForTimeout(6000);
68await shot(s, 'agents-1-dispatched');
69
70const before = await cards(s.page);
71const clearVisible = await s.page.evaluate(() => {
72 const b = document.getElementById('agents-clear');
73 return !!(b && b.style.display !== 'none');
74});
75console.log('after dispatch:', JSON.stringify(before));
76
77check('THREE WORKERS WERE DISPATCHED FROM ONE TURN', before.length === 3,
78 `${before.length} cards: ${before.map(c => c.name).join(', ') || '(none)'}`);
79// Printed and never judged before: a panel of three workers stuck at "running"
80// read exactly like three that had finished.
81check('and all three finished', before.length > 0 && before.every(c => /done/i.test(c.status)),
82 `statuses ${JSON.stringify(before.map(c => c.status))}`);
83check('each finished worker is offered a fold', before.length > 0 && before.every(c => c.offer),
84 `${before.filter(c => c.offer).length}/${before.length} offered`);
85check('the Clear control is on the panel', clearVisible, '#agents-clear');
86
87// Fold the first foldable worker in.
88const foldBtn = s.page.locator('#panel-agents .acard').first().locator('.abtn', { hasText: 'Fold in' }).first();
89const offered = await foldBtn.count();
90check('there is a fold to press', !!offered, `${offered} "Fold in" on the first card`);
91let after = before;
92if (offered) {
93 await foldBtn.click();
94 await s.page.waitForTimeout(2500); // the propose (reducer) turn
95 // Accept the proposed fold diff in the Centre.
96 const accept = await s.page.$('.diff-accept');
97 if (accept) { await accept.click(); await s.page.waitForTimeout(2000); }
98 after = await cards(s.page);
99}
100console.log('after folding:', JSON.stringify(after));
101await shot(s, 'agents-2-folded');
102
103const folded = after.filter(c => c.folded);
104check('THE WORKER THAT WAS FOLDED IN IS MARKED AS FOLDED', folded.length === 1,
105 `${folded.length} cards carry .afolded`);
106// "Once, not repeatedly": the card that folded is no longer offered the fold,
107// and nobody else lost theirs.
108check('AND IS NOT OFFERED THE FOLD A SECOND TIME',
109 folded.length === 1 && !folded[0].offer, folded.length ? `offer=${folded[0].offer}` : 'nothing folded');
110check('while every other worker keeps its own offer',
111 after.filter(c => c.offer).length === before.filter(c => c.offer).length - 1,
112 `${after.filter(c => c.offer).length} offers left of ${before.filter(c => c.offer).length}`);
113
114// Clear finished.
115const afterClear = await s.page.evaluate(async () => {
116 const b = document.getElementById('agents-clear');
117 if (b) b.click();
118 await new Promise(r => setTimeout(r, 400));
119 return document.querySelectorAll('#panel-agents .acard').length;
120});
121check('CLEARING FINISHED WORKERS EMPTIES THE PANEL', afterClear === 0, `${afterClear} cards left`);
122
123const errs = errors(s).filter(e => !/502|Bad Gateway/.test(e));
124check('nothing threw', errs.length === 0, errs.slice(0, 2).join(' | '));
125
126await s.close();
127console.log(`\n${ok.length} passed, ${bad.length} failed`);
128if (bad.length) { bad.forEach(x => console.log(' FAILED: ' + x)); process.exit(1); }