Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 29 additions & 10 deletions dist/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@ import { promisify } from "node:util";
import { failureAvailability, windowAvailability } from "./availability.js";
import { canonicalDate } from "./date-value.js";
import { saveCapture, validateSave } from "./capture-file.js";
import { DESKTOP_BUTTONS, DESKTOP_CLICKS, DESKTOP_DIRECTIONS, DESKTOP_ELEMENT_ACTIONS, DESKTOP_MODIFIERS, DESKTOP_SYSTEM_KEYS, DESKTOP_SELECTIONS, isDesktopAction, isDesktopKey, isDesktopManagement, } from "./protocol.js";
import { DESKTOP_BUTTONS, DESKTOP_CLICKS, DESKTOP_DIRECTIONS, DESKTOP_ELEMENT_ACTIONS, DESKTOP_EXPECT_OPS, DESKTOP_MODIFIERS, DESKTOP_SYSTEM_KEYS, DESKTOP_SELECTIONS, isDesktopAction, isDesktopKey, isDesktopManagement, } from "./protocol.js";
import { NOTIFICATIONS_MS, notificationAction, readNotifications, validateNotificationAction, } from "./notifications.js";
import { setCell } from "./cell.js";
import { buildTable, TABLE_ROWS_MAX, TableError } from "./table.js";
import { waitFor } from "./wait.js";
import { expectAfter, validateExpect, waitFor } from "./wait.js";
const exec = promisify(execFile);
const MAX_TEXT = 32_000;
/** A scoped inspection lists more elements; recognized text has its own native line and byte limits. */
Expand Down Expand Up @@ -1377,15 +1377,34 @@ async function execute(native, request, abort) {
return actionResult({ outcome: "refused", reason }, "refused");
throw new Error(reason);
}
const reads = {
observe: (read, signal) => execute(native, read, signal),
elements: (input, signal) => native(["elements"], signal, {
input: JSON.stringify(input),
target: { pid: input.pid, window: input.window },
}),
};
// A wait bounds itself; each of its polls is one ordinary read with its own 30-second limit.
if (request.op === "wait") {
return await waitFor({
observe: (read, signal) => execute(native, read, signal),
elements: (input, signal) => native(["elements"], signal, {
input: JSON.stringify(input),
target: { pid: input.pid, window: input.window },
}),
}, request, abort);
if (request.op === "wait")
return await waitFor(reads, request, abort);
// An expectation reads its condition before input and polls it after an unknown outcome, never repeating input.
if (isDesktopAction(request) && "expect" in request && request.expect !== undefined) {
const { expect, ...action } = request;
let condition;
try {
if (!DESKTOP_EXPECT_OPS.includes(request.op)) {
throw new Error(`${request.op} doesn't take expect.`);
}
condition = validateExpect(expect);
}
catch (error) {
return actionResult({
outcome: "refused",
reason: (error instanceof Error ? error.message : String(error)).slice(0, 2000),
message: "No input was sent.",
}, "refused");
}
return await expectAfter(reads, condition, (signal) => execute(native, action, signal), abort);
}
// Notifications compose several reads, each with its own 30-second limit, under one overall limit.
if (request.op === "notifications" || request.op === "notification-action") {
Expand Down
9 changes: 9 additions & 0 deletions dist/protocol.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,10 @@ export type DesktopWait = {
text?: string;
timeout_ms?: number;
};
/** A desktop_wait condition an action checks after an unknown outcome; its timeout_ms is shorter. */
export type DesktopExpect = Omit<DesktopWait, "op">;
/** The actions that take expect; others opt in by joining this list and their tool's schema. */
export declare const DESKTOP_EXPECT_OPS: readonly ["click", "key", "system-key", "drag", "insert"];
/** Writes the full-resolution capture, or the part under region in global desktop points, as a PNG. */
export type DesktopCaptureSave = {
save_path?: string;
Expand Down Expand Up @@ -194,6 +198,7 @@ export type DesktopRequest = DesktopManagement | DesktopLaunch | DesktopOpen | D
point?: DesktopPoint;
kind?: DesktopClick;
modifiers?: DesktopModifier[];
expect?: DesktopExpect;
} | {
op: "scroll";
snapshot: string;
Expand All @@ -211,6 +216,7 @@ export type DesktopRequest = DesktopManagement | DesktopLaunch | DesktopOpen | D
duration_ms?: number;
modifiers?: DesktopModifier[];
path?: DesktopPoint[];
expect?: DesktopExpect;
} | {
op: "hover";
snapshot: string;
Expand All @@ -233,6 +239,7 @@ export type DesktopRequest = DesktopManagement | DesktopLaunch | DesktopOpen | D
snapshot: string;
text: string;
route?: "paste" | "type";
expect?: DesktopExpect;
} | {
op: "element-action";
snapshot: string;
Expand Down Expand Up @@ -273,10 +280,12 @@ export type DesktopRequest = DesktopManagement | DesktopLaunch | DesktopOpen | D
}[];
/** Presses key this many times, up to 32. */
repeat?: number;
expect?: DesktopExpect;
} | {
op: "system-key";
key: DesktopKey | DesktopSystemKey;
modifiers?: DesktopModifier[];
expect?: DesktopExpect;
};
export type DesktopPoint = {
x: number;
Expand Down
2 changes: 2 additions & 0 deletions dist/protocol.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ export const DESKTOP_WAIT_CONDITIONS = [
"app_frontmost",
"app_quit",
];
/** The actions that take expect; others opt in by joining this list and their tool's schema. */
export const DESKTOP_EXPECT_OPS = ["click", "key", "system-key", "drag", "insert"];
export const DESKTOP_CLICKS = ["single", "double", "right", "middle", "triple"];
export const DESKTOP_DIRECTIONS = ["up", "down", "left", "right"];
export const DESKTOP_BUTTONS = ["left", "right"];
Expand Down
Loading
Loading