import test from 'node:test'; import assert from 'node:assert/strict'; import { DesktopObserver } from '../computer-use/observer.js'; test('Phase 6 observer returns stable refs and ranked semantic matches', async () => { const observer = new DesktopObserver({ screenshot: { capture: async () => '/tmp/frame.png' }, normalizer: { normalize: async () => ({ path: '/tmp/frame.webp' }) }, shell: { windows: async () => ({ available: true, windows: [{ id: 1 }] }), focused: async () => ({ id: 1 }) }, atspi: { tree: async () => [{ role: 'switch', name: 'Night Light', rect: [10, 20, 30, 30] }, { role: 'button', name: 'Cancel', rect: [1, 2, 3, 4] }] }, }); const bundle = await observer.observe({ includeOcr: false }); assert.equal(bundle.screenshot_path, '/tmp/frame.webp'); assert.equal(bundle.tree[0].ref, 'r1'); assert.equal(bundle.frame_source, 'screenshot'); assert.equal((await observer.find({ query: 'night light' }))[0].ref, 'r1'); const zoom = await observer.zoom({ ref: 'r1' }); assert.deepEqual(zoom.rect, [10, 20, 30, 30]); }); test('observer returns when the Shell helper never connects', async () => { const observer = new DesktopObserver({ screenshot: { capture: async () => '/tmp/frame.png' }, normalizer: { normalize: async () => ({ path: '/tmp/frame.webp' }) }, shell: { connect: () => new Promise(() => {}), windows: async () => assert.fail('shell windows'), focused: async () => assert.fail('shell focused') }, atspi: { tree: async () => [{ role: 'label', name: 'Discord', rect: [0, 0, 10, 10] }] }, timeouts: { shell: 40 }, }); const started = Date.now(); const bundle = await observer.observe({ includeOcr: false }); assert.ok(Date.now() - started < 1000); assert.equal(bundle.screenshot_path, '/tmp/frame.webp'); assert.equal(bundle.tree[0].name, 'Discord'); assert.match(bundle.unavailable.join(' '), /Shell helper/); }); test('observer reads the PipeWire frame buffer instead of the Screenshot portal', async () => { let shots = 0; const observer = new DesktopObserver({ screenshot: { capture: async () => { shots += 1; return '/tmp/portal.png'; } }, framebuffer: { capture: async () => '/tmp/pw.png' }, normalizer: { normalize: async (input) => ({ path: `${input}.webp` }) }, shell: { windows: async () => ({ available: true, windows: [] }), focused: async () => null }, atspi: { tree: async () => [] }, }); const bundle = await observer.observe({ includeTree: false, includeOcr: false }); assert.equal(shots, 0); assert.equal(bundle.frame_source, 'pipewire'); assert.equal(bundle.screenshot_path, '/tmp/pw.png.webp'); }); test('observer returns after a hung screenshot instead of staying on THINKING', async () => { const observer = new DesktopObserver({ screenshot: { capture: () => new Promise(() => {}) }, shell: { windows: async () => ({ available: true, windows: [] }), focused: async () => null }, atspi: { tree: async () => [] }, timeouts: { screenshot: 40 }, }); const started = Date.now(); const bundle = await observer.observe({ includeTree: false, includeOcr: false }); assert.ok(Date.now() - started < 1000); assert.equal(bundle.screenshot_path, null); assert.match(bundle.unavailable.join(' '), /frame/); }); test('Phase 6 observer reports portal failure instead of inventing a frame', async () => { const observer = new DesktopObserver({ screenshot: { capture: async () => { throw new Error('no session bus'); } }, shell: { windows: async () => ({ available: false, windows: [] }), focused: async () => null }, atspi: { tree: async () => [] } }); const bundle = await observer.observe({ includeTree: false, includeOcr: false }); assert.equal(bundle.screenshot_path, null); assert.match(bundle.unavailable[0], /frame/); });