blob: 80a47db4d95484fc04a661b559c21c7744f511fb [file]
// Moving a window, which was always possible and had no way to be said.
//
// A model asked to move a window reached for a title-bar drag — the only route a
// person has — which needs a coordinate, which needs the window not to be
// covered, which a window driven from the background always is. It spent 57
// calls on that in one run.
//
// `AXPosition` is settable on every window measured (17 of 17), `AXSize` on 14,
// and writing either does not bring the application forward. The capability was
// there; the vocabulary was not.
import test from 'node:test';
import assert from 'node:assert/strict';
import { computerParams } from '../computer-use-codec.js';
function parse(input: unknown) {
return computerParams.safeParse(input);
}
test('a move names where to put the window', () => {
const ok = parse({
action: 'window_action',
observation_id: 'obs_1',
element_id: '0',
window_action: 'move',
position: [220, 164],
});
assert.equal(ok.success, true);
});
test('a move without a position is rejected rather than guessed at', () => {
const bad = parse({
action: 'window_action',
observation_id: 'obs_1',
element_id: '0',
window_action: 'move',
});
assert.equal(bad.success, false);
});
test('a resize names a size, and a size is positive', () => {
assert.equal(
parse({
action: 'window_action',
observation_id: 'obs_1',
element_id: '0',
window_action: 'resize',
size: [800, 600],
}).success,
true,
);
assert.equal(
parse({
action: 'window_action',
observation_id: 'obs_1',
element_id: '0',
window_action: 'resize',
size: [0, 600],
}).success,
false,
);
assert.equal(
parse({
action: 'window_action',
observation_id: 'obs_1',
element_id: '0',
window_action: 'resize',
}).success,
false,
);
});
test('minimise needs no geometry', () => {
assert.equal(
parse({
action: 'window_action',
observation_id: 'obs_1',
element_id: '0',
window_action: 'minimize',
}).success,
true,
);
});
test('a negative position is accepted, because a second display is a real place', () => {
// Measured on this machine: display 2 sits at (-193, -1080) in the space the
// observation reports. Refusing a negative coordinate would make half the
// desktop unaddressable, and macOS clamps what it will not honour anyway.
const ok = parse({
action: 'window_action',
observation_id: 'obs_1',
element_id: '0',
window_action: 'move',
position: [-193, -1049],
});
assert.equal(ok.success, true);
});
test('a minimise names its own cost, because it cannot be taken back', async () => {
// Measured: the moment `minimize_window` succeeds, `list_apps` reports
// `windowCount: 0` for that application and `observe` answers
// `target_missing`. A minimized window is not in `CGWindowListCopyWindowInfo`
// under `.optionOnScreenOnly`, so there is no id left for an unminimize to
// address — and the fresh observation attached to this very result will not
// contain the window either. A model that is not told reads that as the
// window having been closed, or retries.
const { buildComputerUseTools } = await import('../computer-use-tools.js');
const observation = {
observationId: 'backend-1',
appId: 'com.apple.calculator',
pid: 1,
windowId: 2,
elements: [{ elementId: '0', role: 'AXWindow', label: '计算器' }],
};
// The fixture has to lose the window, or it cannot reproduce what happens.
// A backend that keeps answering with an observation makes this assertion
// pass no matter what the code does.
let gone = false;
const [tool] = buildComputerUseTools({
backend: {
async preflight() {
return { accessibility: true, screenRecording: true };
},
async observeApp() {
if (gone) throw new Error('target_missing: the target window no longer exists');
return observation as never;
},
async captureObservation() {
if (gone) throw new Error('target_missing: the target window no longer exists');
return observation as never;
},
async runSemantic(action: { type: string; action?: string }) {
if (action.type === 'window_action' && action.action === 'minimize') gone = true;
return {
outcome: {
ok: true as const,
tier: 'ax' as const,
verified: true,
evidence: { path: 'ax_attribute', effect: 'confirmed' as const },
},
};
},
async run() {
return { outcome: { ok: true as const, tier: 'ax' as const } };
},
} as never,
});
const context = {
abortSignal: new AbortController().signal,
sessionId: 'm',
turnId: 't',
toolCallId: 'c',
} as never;
const observed = (await tool!.impl(
{ action: 'observe', app: 'com.apple.calculator', include_screenshot: false },
context,
)) as { modelText?: string; text: string };
const observationId = /observation_id=(\S+)/.exec(observed.modelText ?? observed.text)?.[1] ?? '';
const minimised = (await tool!.impl(
{
action: 'window_action',
observation_id: observationId,
element_id: '0',
window_action: 'minimize',
},
context,
)) as { modelText?: string; text: string };
assert.match(
minimised.modelText ?? minimised.text,
/only the person at the machine can bring it back/,
);
const moved = (await tool!.impl(
{
action: 'window_action',
observation_id: observationId,
element_id: '0',
window_action: 'move',
position: [10, 10],
},
context,
)) as { modelText?: string; text: string };
// Moving is reversible and says nothing of the sort.
assert.doesNotMatch(moved.modelText ?? moved.text, /bring it back/);
});
test('an action outside the three is not a window action', () => {
assert.equal(
parse({
action: 'window_action',
observation_id: 'obs_1',
element_id: '0',
window_action: 'close',
}).success,
false,
);
});