mirror of
https://github.com/callstack/agent-device.git
synced 2026-09-14 20:06:34 +08:00
59d28e8446
* refactor: add provider-first device lab tests * refactor: tighten device lab provider seams * test: cover provider lab contracts * docs: record device lab harness direction * ci: run device lab integration tests * test: move device lab under integration * test: extract device lab helpers * refactor: centralize apps filter defaults * test: drop lab-covered unit tests * test: fold platform happy paths into device lab * test: reuse device lab helpers * test: move device lab to in-process harness * test: replace session handler cases with device lab * test: harden device lab scenario contracts * docs: define unit test retention policy * test: expand provider device lab coverage * test: harden provider device lab coverage * test: cover manifest install and runner session contracts * chore: remove unused provider cleanup code * test: split android find device lab scenario * test: track provider lab architecture progress * test: clarify provider lab roadmap progress * test: advance provider lab session coverage * test: move menubar click routing to device lab * test: move menubar snapshots to device lab * refactor: centralize screenshot flag plumbing * refactor: colocate screenshot flag metadata * test: cover all public commands in device lab * test: move macos wait success to device lab * test: drop redundant perf and diff units * test: move push payload paths to device lab * test: move network parsing to device lab * test: move log cleanup to device lab * test: move log restart and boot to device lab * test: move ios physical boot to device lab * test: cover perf startup in device lab * test: extract android and ios device lab worlds * test: trim device lab world surface * test: split snapshot capture unit coverage * test: deepen device lab coverage and trim handler units * test: clean up device lab migration scaffolding * test: report device lab public command coverage * refactor: make Apple provider seams semantic * refactor: tighten device inventory and Linux provider seams * refactor: tighten request provider scoping * refactor: add semantic macos host provider * test: broaden device lab find coverage * test: cover workflow flags in device lab * refactor: promote linux input provider seam * test: clarify device lab flag coverage * test: classify snapshot force-full progress * test: enforce device lab progress in ci * test: stabilize device lab ci * test: move packaged metro smoke to integration * test: drop stale provider seam coverage * test: harden provider scope regression coverage * refactor: remove stale platform barrels * refactor: keep linux clipboard and screenshots semantic * refactor: move macos host tools behind provider * fix: honor remote artifact output paths * test: deepen runtime coverage for daemon and runner paths * test: share loopback test helpers * refactor: make daemon runtime importable * fix: honor replay target metadata * chore: tighten final device lab quality gates * test: share device lab setup helpers * test: remove generic apple lab fallback * test: deduplicate device lab helpers * chore: tighten fallow duplication signal * refactor: share apple diagnostic helpers * fix: detect active android ime during fill verification * test: consolidate provider-backed integration suite * ci: fix fallow and iOS smoke setup * chore: consolidate cleanup after ci fixes * test: split vitest unit and integration projects * docs: mention MCP discovery metadata * docs: add agent skills context pointers * fix: close provider recording coverage gaps * fix: restore mcp compatibility smoke * test: cover provider edge regressions * test: consolidate loopback helpers * docs: remove stale provider routing reference * fix: harden final provider review issues * chore: defer mcp cleanup from provider refactor
195 lines
5.7 KiB
TypeScript
195 lines
5.7 KiB
TypeScript
import fs from 'node:fs';
|
|
import assert from 'node:assert/strict';
|
|
import { listLinuxDevices } from '../../../src/platforms/linux/devices.ts';
|
|
import { createLocalLinuxToolProvider } from '../../../src/platforms/linux/tool-provider.ts';
|
|
import type { DeviceInfo } from '../../../src/utils/device.ts';
|
|
import { validPng } from './assertions.ts';
|
|
import { PROVIDER_SCENARIO_LINUX } from './fixtures.ts';
|
|
import {
|
|
createProviderScenarioHarness,
|
|
restoreEnv,
|
|
type ProviderScenarioHarness,
|
|
} from './harness.ts';
|
|
import type { FlatToolCall } from './providers.ts';
|
|
|
|
export type LinuxDesktopWorld = {
|
|
daemon: ProviderScenarioHarness;
|
|
localLinuxDevices: DeviceInfo[];
|
|
toolCalls: Array<[string, string[]]>;
|
|
desktopCalls: Array<[string, string]>;
|
|
semanticCalls: FlatToolCall[];
|
|
close: () => Promise<void>;
|
|
};
|
|
|
|
export async function createLinuxDesktopWorld(): Promise<LinuxDesktopWorld> {
|
|
const previousSessionType = process.env.XDG_SESSION_TYPE;
|
|
const previousWaylandDisplay = process.env.WAYLAND_DISPLAY;
|
|
const previousAuthHook = process.env.AGENT_DEVICE_HTTP_AUTH_HOOK;
|
|
const previousPlatform = process.platform;
|
|
|
|
Object.defineProperty(process, 'platform', { value: 'darwin', configurable: true });
|
|
assert.deepEqual(await listLinuxDevices(), []);
|
|
Object.defineProperty(process, 'platform', { value: 'linux', configurable: true });
|
|
process.env.XDG_SESSION_TYPE = 'x11';
|
|
delete process.env.WAYLAND_DISPLAY;
|
|
delete process.env.AGENT_DEVICE_HTTP_AUTH_HOOK;
|
|
|
|
const localLinuxDevices = await listLinuxDevices();
|
|
const toolCalls: Array<[string, string[]]> = [];
|
|
const desktopCalls: Array<[string, string]> = [];
|
|
const semanticCalls: FlatToolCall[] = [];
|
|
let clipboardText = '';
|
|
|
|
const linuxToolProvider = createLocalLinuxToolProvider({
|
|
whichCommand: async (cmd) =>
|
|
cmd === 'gnome-calculator' || cmd === 'xdotool' || cmd === 'wmctrl',
|
|
runCommand: async (cmd, args) => {
|
|
toolCalls.push([cmd, args]);
|
|
return { stdout: '', stderr: '', exitCode: 0 };
|
|
},
|
|
desktop: {
|
|
openTarget: async (target) => {
|
|
desktopCalls.push(['open', target]);
|
|
},
|
|
closeApp: async (app) => {
|
|
desktopCalls.push(['close', app]);
|
|
},
|
|
},
|
|
accessibility: {
|
|
captureTree: async (surface) => {
|
|
semanticCalls.push(['accessibility', surface]);
|
|
return {
|
|
nodes: linuxCalculatorSnapshotNodes(),
|
|
truncated: false,
|
|
surface,
|
|
};
|
|
},
|
|
},
|
|
clipboard: {
|
|
readText: async () => {
|
|
semanticCalls.push(['clipboard', 'read']);
|
|
return clipboardText;
|
|
},
|
|
writeText: async (text) => {
|
|
semanticCalls.push(['clipboard', 'write', text]);
|
|
clipboardText = text;
|
|
},
|
|
},
|
|
screenshot: {
|
|
capture: async (outPath, options) => {
|
|
semanticCalls.push([
|
|
'screenshot',
|
|
outPath,
|
|
String(options?.fullscreen ?? ''),
|
|
String(options?.stabilize ?? ''),
|
|
String(options?.surface ?? ''),
|
|
]);
|
|
fs.writeFileSync(outPath, validPng());
|
|
},
|
|
},
|
|
input: {
|
|
click: async (x, y, button) => {
|
|
semanticCalls.push(['input', 'click', String(x), String(y), button]);
|
|
},
|
|
doubleClick: async (x, y) => {
|
|
semanticCalls.push(['input', 'double-click', String(x), String(y)]);
|
|
},
|
|
longPress: async (x, y, durationMs) => {
|
|
semanticCalls.push(['input', 'long-press', String(x), String(y), String(durationMs)]);
|
|
},
|
|
drag: async (x1, y1, x2, y2, durationMs) => {
|
|
semanticCalls.push([
|
|
'input',
|
|
'drag',
|
|
String(x1),
|
|
String(y1),
|
|
String(x2),
|
|
String(y2),
|
|
String(durationMs),
|
|
]);
|
|
},
|
|
scroll: async (direction, options) => {
|
|
semanticCalls.push([
|
|
'input',
|
|
'scroll',
|
|
direction,
|
|
String(options?.amount ?? ''),
|
|
String(options?.pixels ?? ''),
|
|
]);
|
|
},
|
|
typeText: async (text, options) => {
|
|
semanticCalls.push(['input', 'type', text, String(options?.delayMs ?? 0)]);
|
|
},
|
|
key: async (combo) => {
|
|
semanticCalls.push(['input', 'key', combo]);
|
|
},
|
|
},
|
|
});
|
|
|
|
const daemon = await createProviderScenarioHarness({
|
|
linuxToolProvider: () => linuxToolProvider,
|
|
deviceInventoryProvider: async () => [PROVIDER_SCENARIO_LINUX],
|
|
});
|
|
|
|
let closed = false;
|
|
return {
|
|
daemon,
|
|
localLinuxDevices,
|
|
toolCalls,
|
|
desktopCalls,
|
|
semanticCalls,
|
|
close: async () => {
|
|
if (closed) return;
|
|
closed = true;
|
|
await daemon.close();
|
|
Object.defineProperty(process, 'platform', { value: previousPlatform, configurable: true });
|
|
restoreEnv('XDG_SESSION_TYPE', previousSessionType);
|
|
restoreEnv('WAYLAND_DISPLAY', previousWaylandDisplay);
|
|
restoreEnv('AGENT_DEVICE_HTTP_AUTH_HOOK', previousAuthHook);
|
|
},
|
|
};
|
|
}
|
|
|
|
function linuxCalculatorSnapshotNodes(): Array<{
|
|
index: number;
|
|
role: string;
|
|
label: string;
|
|
rect: { x: number; y: number; width: number; height: number };
|
|
enabled: boolean;
|
|
hittable: boolean;
|
|
depth: number;
|
|
parentIndex?: number;
|
|
}> {
|
|
return [
|
|
{
|
|
index: 0,
|
|
role: 'frame',
|
|
label: 'Calculator',
|
|
rect: { x: 0, y: 0, width: 320, height: 480 },
|
|
enabled: true,
|
|
hittable: true,
|
|
depth: 0,
|
|
},
|
|
{
|
|
index: 1,
|
|
role: 'push button',
|
|
label: '5',
|
|
rect: { x: 40, y: 80, width: 40, height: 40 },
|
|
enabled: true,
|
|
hittable: true,
|
|
depth: 1,
|
|
parentIndex: 0,
|
|
},
|
|
{
|
|
index: 2,
|
|
role: 'push button',
|
|
label: 'Clear',
|
|
rect: { x: 90, y: 80, width: 70, height: 40 },
|
|
enabled: true,
|
|
hittable: true,
|
|
depth: 1,
|
|
parentIndex: 0,
|
|
},
|
|
];
|
|
}
|