@@ -44,16 +44,67 @@ test('computer use semantic actuation previews, budgets, and audits actions', as
|
||||
const audit = new ComputerAudit({ dir }); const session = new ComputerUseSession({ stepsMax: 1, audit }); const sent = [];
|
||||
session.grant();
|
||||
const actuator = new ComputerActuator({ session, input: { send: (event) => sent.push(event) }, find: async ({ ref }) => [{ ref, name: 'Save', role: 'push button', rect: [1, 2, 3, 4] }], atspiAction: async () => ({ semantic: true }), audit, sleep: async () => {} });
|
||||
const result = await actuator.click({ ref: 'r1' }); assert.equal(result.ok, true); assert.equal(session.status().steps_used, 1);
|
||||
const result = await actuator.click({ ref: 'r1' }); assert.equal(result.ok, true); assert.equal(result.name, 'Save'); assert.equal(session.status().steps_used, 1);
|
||||
assert.equal(result.target, undefined);
|
||||
assert.deepEqual(sent, []); const files = await (await import('node:fs/promises')).readdir(dir); assert.equal(files.length, 1); assert.match(await readFile(path.join(dir, files[0]), 'utf8'), /target_hash/);
|
||||
});
|
||||
|
||||
test('semantic click falls back to the control center when AT-SPI has no click action', async () => {
|
||||
const session = new ComputerUseSession(); session.grant(); const sent = [];
|
||||
const actuator = new ComputerActuator({
|
||||
session,
|
||||
input: { send: (event) => sent.push(event) },
|
||||
find: async ({ ref }) => [{ ref, name: 'Discord', role: 'frame', rect: [10, 20, 100, 40] }],
|
||||
atspiAction: async () => { throw new Error('AT-SPI action unavailable: click'); },
|
||||
sleep: async () => {},
|
||||
});
|
||||
const result = await actuator.click({ ref: 'r1' });
|
||||
assert.equal(result.ok, true);
|
||||
assert.equal(sent[0].type, 'pointer');
|
||||
assert.equal(sent[0].x, 60);
|
||||
assert.equal(sent[0].y, 40);
|
||||
});
|
||||
|
||||
test('typing focuses the target then injects keys and does not stringify missing text', async () => {
|
||||
const session = new ComputerUseSession(); session.grant(); const sent = [];
|
||||
const actuator = new ComputerActuator({
|
||||
session,
|
||||
input: { send: (event) => sent.push(event) },
|
||||
find: async () => [{ ref: 'r2', name: 'Message', role: 'entry', rect: [0, 0, 20, 10] }],
|
||||
sleep: async () => {},
|
||||
});
|
||||
const result = await actuator.type({ ref: 'r2', text: 'hello' });
|
||||
assert.equal(result.ok, true);
|
||||
assert.equal(result.result.typed, 5);
|
||||
assert.equal(sent[0].action, 'click');
|
||||
assert.equal(sent[1].action, 'type');
|
||||
assert.equal(sent[1].text, 'hello');
|
||||
const empty = await actuator.type({ ref: 'r2' });
|
||||
assert.equal(empty.result.typed, 0);
|
||||
assert.equal(sent[3].text, '');
|
||||
});
|
||||
|
||||
test('computer use refuses password targets and unconfirmed dangerous keys', async () => {
|
||||
const session = new ComputerUseSession(); session.grant(); const actuator = new ComputerActuator({ session, input: { send() {} }, find: async () => [{ name: 'Password', role: 'password text' }], sleep: async () => {} });
|
||||
await assert.rejects(() => actuator.type({ ref: 'password', text: 'secret' }), /password/);
|
||||
await assert.rejects(() => actuator.key({ combo: 'alt+f4' }), /confirmation/);
|
||||
});
|
||||
|
||||
test('Send buttons do not require extra confirmation after a desktop grant', async () => {
|
||||
const session = new ComputerUseSession(); session.grant(); const sent = [];
|
||||
const actuator = new ComputerActuator({
|
||||
session,
|
||||
input: { send: (event) => sent.push(event) },
|
||||
find: async () => [{ ref: 'r3', name: 'Send', role: 'push button', rect: [0, 0, 40, 20] }],
|
||||
atspiAction: async () => { throw new Error('AT-SPI action unavailable: click'); },
|
||||
sleep: async () => {},
|
||||
});
|
||||
const result = await actuator.click({ ref: 'r3' });
|
||||
assert.equal(result.ok, true);
|
||||
assert.equal(sent[0].action, 'click');
|
||||
assert.equal(sent[0].x, 20);
|
||||
});
|
||||
|
||||
test('libei sender binds bitmask capabilities from libei.h', () => {
|
||||
const pyDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../computer-use/py');
|
||||
const compiled = spawnSync('python3', ['-m', 'py_compile', 'libei_sender.py', 'portal_remote_desktop.py', 'portal_screenshot.py', 'pw_framebuffer.py'], { cwd: pyDir, encoding: 'utf8' });
|
||||
|
||||
Reference in New Issue
Block a user