Oregami
Repositories/oxedyne/daimond

oxedyne/daimond/dev/shot_agentctl.mjs

5.3 KiB, 1 run

created by r2519314175:137, 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// shot_agentctl.mjs — behavioural check of the Agents panel's pause / resume /
2// stop controls (header, acting on all) and the running/paused/queued tally.
3//
4// Drives the real UI: a fan-out of slow (@long) workers, then Pause-all → Resume-all →
5// Stop-all, asserting the tiles' state and the header at each step. Needs dev/serve.mjs
6// (DAIMOND_PORT, default 8777) + dev/mockllm.mjs.
7import { open, shot } from './harness.mjs';
8
9const s = await open({ name: 'agentctl-shot', signIn: true, connect: true });
10const { page } = s;
11const pause = (ms) => page.waitForTimeout(ms);
12
13async function newDiamond(name) {
14 await page.click('#new-diamond-btn', { force: true });
15 await page.waitForSelector('.dlg-input', { timeout: 8000 });
16 await page.fill('.dlg-input', name);
17 await page.click('.dlg-ok');
18 await page.waitForSelector('#steer-input', { timeout: 10000 });
19 await pause(300);
20}
21
22async function fanOut(names) {
23 // Each worker runs a slow @long stream, so it stays running long enough to
24 // be caught and paused.
25 const calls = names.map(n => `spawn_agent {"name":"${n}","task":"@long 60"}`).join(' ;; ');
26 await page.fill('#steer-input', '@tools ' + calls);
27 await page.click('#steer-send');
28 // The governor's predictive dispatch gate may ask to confirm a fan-out that
29 // would outpace the pace budget — accept it and let the workers run.
30 await pause(500);
31 const okBtn = await page.$('.dlg-ok');
32 if (okBtn) { await okBtn.click(); await pause(300); }
33}
34
35// Snapshot the pane + header state.
36const snap = () => page.evaluate(() => {
37 const cards = [...document.querySelectorAll('#agents-list .acard')];
38 const st = (cls) => cards.filter(c => c.classList.contains(cls)).length;
39 const btn = (id) => { const b = document.getElementById(id); return b ? { shown: b.offsetParent !== null, disabled: !!b.disabled } : null; };
40 const ctl = document.getElementById('agents-ctl');
41 return {
42 cards: cards.length,
43 running: st('running'), paused: st('paused'), stopped: st('stopped'), queued: st('queued'),
44 stat: (document.getElementById('agents-stat') || {}).textContent || '',
45 statShown: (() => { const e = document.getElementById('agents-stat'); return e && e.offsetParent !== null; })(),
46 ctlShown: ctl ? ctl.offsetParent !== null : false,
47 holding: ctl ? ctl.classList.contains('holding') : false,
48 btnPause: btn('agents-pause'), btnPlay: btn('agents-play'), btnStop: btn('agents-stop'),
49 };
50});
51
52// ── Fan out four slow workers ───────────────────────────────────────────
53await newDiamond('Runaway test');
54await fanOut(['worker-a', 'worker-b', 'worker-c', 'worker-d']);
55await pause(1600);
56const running = await snap();
57console.log('RUNNING:', JSON.stringify(running));
58await shot(s, 'agentctl-running');
59
60// ── Pause all ───────────────────────────────────────────────────────────
61await page.click('#agents-pause', { force: true });
62await pause(900);
63const paused = await snap();
64console.log('PAUSED :', JSON.stringify(paused));
65await shot(s, 'agentctl-paused');
66
67// ── Resume all ──────────────────────────────────────────────────────────
68await page.click('#agents-play', { force: true });
69await pause(2200);
70const resumed = await snap();
71console.log('RESUMED:', JSON.stringify(resumed));
72await shot(s, 'agentctl-resumed');
73
74// ── Fan out again, then Stop all (the kill switch) ──────────────────────
75await page.click('.diamond-box:has-text("Runaway test")', { force: true });
76await page.waitForSelector('#steer-input', { timeout: 8000 });
77await fanOut(['kill-a', 'kill-b', 'kill-c']);
78await pause(1600);
79const running2 = await snap();
80console.log('RUNNING2:', JSON.stringify(running2));
81await page.click('#agents-stop', { force: true });
82await pause(900);
83const stopped = await snap();
84console.log('STOPPED:', JSON.stringify(stopped));
85await shot(s, 'agentctl-stopped');
86
87// ── Verdicts ────────────────────────────────────────────────────────────
88const realErrs = s.errs.filter(e => !/502|Bad Gateway|\/api\b/.test(e));
89const ok =
90 // Four workers running, header visible with the right button states.
91 running.running >= 3 && running.ctlShown
92 && running.btnPause.disabled === false && running.btnPlay.disabled === true
93 && /running/.test(running.stat)
94 // Pause hangs them all up: paused tiles, resume enabled, pause disabled, held.
95 && paused.paused >= 3 && paused.running === 0
96 && paused.btnPause.disabled === true && paused.btnPlay.disabled === false
97 && paused.holding === true && /paused/.test(paused.stat)
98 // Resume clears the paused state (they run on and finish).
99 && resumed.paused === 0
100 // Stop-all finalises the second fan-out to stopped.
101 && running2.running >= 2
102 && stopped.stopped >= 2 && stopped.running === 0 && stopped.paused === 0
103 && realErrs.length === 0;
104console.log(ok ? '\n✅ PASS — pause / resume / stop all work; counts track state' : '\n❌ FAIL — see above');
105if (realErrs.length) console.log('PAGE ERRORS:', realErrs);
106await s.close();
107process.exit(ok ? 0 : 1);