@dipoleml/dcode-cli 1.2.1 → 1.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/dcode.cjs +2214 -1891
- package/dist/packages/browser-use-plugin/.dcode-plugin/plugin.json +8 -8
- package/dist/packages/dcode-cua-plugin/.dcode-plugin/plugin.json +10 -0
- package/dist/packages/dcode-cua-plugin/docs/computer-use.md +470 -0
- package/dist/packages/dcode-cua-plugin/package.json +12 -0
- package/dist/packages/dcode-cua-plugin/scripts/bump-dcode-cua-producer.mjs +305 -0
- package/dist/packages/dcode-cua-plugin/scripts/check-cua-baseline.mjs +155 -0
- package/dist/packages/dcode-cua-plugin/scripts/check-sdk.mjs +4 -0
- package/dist/packages/dcode-cua-plugin/scripts/check-version-coherence.mjs +478 -0
- package/dist/packages/dcode-cua-plugin/scripts/computer-use-client.mjs +1208 -0
- package/dist/packages/dcode-cua-plugin/scripts/resolve-pnpm-invocation.mjs +26 -0
- package/dist/packages/dcode-cua-plugin/scripts/sync-cache.mjs +116 -0
- package/dist/packages/dcode-cua-plugin/scripts/sync-skill-from-dcode-cua.mjs +115 -0
- package/dist/packages/dcode-cua-plugin/skills/computer-use/SKILL.md +306 -0
- package/dist/packages/node-repl-host/dist/mcp/server.js +3 -3
- package/dist/packages/node-repl-host/package.json +1 -1
- package/dist/provider/dcode-builtin.json +6748 -6748
- package/node_modules/@dcode/contracts/dist/events/event-reducer-helpers.d.ts +1 -1
- package/node_modules/@dcode/contracts/dist/events/event-reducer-helpers.js +5 -1
- package/node_modules/@dcode/contracts/dist/events/event-reducer.js +26 -5
- package/node_modules/@dcode/contracts/dist/interfaces/session.port.d.ts +5 -1
- package/node_modules/@dcode/contracts/dist/tools/bash.js +4 -1
- package/node_modules/@dcode/i18n/dist/locales/en-US.js +11 -23
- package/node_modules/@dcode/i18n/dist/locales/fr-CA.js +9 -21
- package/node_modules/@dcode/i18n/dist/types.d.ts +0 -12
- package/node_modules/@dcode/model-option-map/package.json +1 -1
- package/node_modules/@dcode/shared/package.json +1 -1
- package/node_modules/@dcode/tui/dist/app-model-ref.js +9 -2
- package/node_modules/@dcode/tui/dist/index.js +57 -65
- package/node_modules/@dcode/tui/dist/theme/defaults.js +21 -15
- package/package.json +3 -3
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "browser-use",
|
|
3
|
-
"version": "0.5.1",
|
|
4
|
-
"description": "Built-in browser automation runtime and guidance for Desktop IAB and explicitly enabled CLI-managed headless CDP: open, navigate, inspect, click, type, screenshot, record workspace WebM videos, and verify web pages and local dev targets.",
|
|
5
|
-
"author": { "name": "
|
|
6
|
-
"license": "MIT",
|
|
7
|
-
"skills": "skills"
|
|
8
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "browser-use",
|
|
3
|
+
"version": "0.5.1",
|
|
4
|
+
"description": "Built-in browser automation runtime and guidance for Desktop IAB and explicitly enabled CLI-managed headless CDP: open, navigate, inspect, click, type, screenshot, record workspace WebM videos, and verify web pages and local dev targets.",
|
|
5
|
+
"author": { "name": "Dipole ML Inc." },
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"skills": "skills"
|
|
8
|
+
}
|
|
@@ -0,0 +1,470 @@
|
|
|
1
|
+
# Computer Use
|
|
2
|
+
|
|
3
|
+
Fetch this reference on demand with
|
|
4
|
+
`nodeRepl.write(await agent.documentation.get("computer-use"))`. It largely repeats
|
|
5
|
+
the resident Computer Use skill, so read it only when you need an argument shape or
|
|
6
|
+
a response field that page does not give.
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
Control native apps on the user's computer by reading or operating UI. Prefer a
|
|
10
|
+
purpose-built skill, connector, API or CLI when one can complete the task.
|
|
11
|
+
|
|
12
|
+
- Use `node_repl` (JavaScript) for all Computer Use actions.
|
|
13
|
+
- Do not use AppleScript, `osascript`, JXA, System Events, shell commands, or any
|
|
14
|
+
other UI-automation technology unless the user explicitly asks for it.
|
|
15
|
+
- Main agent only. Never delegate Computer Use to a subagent.
|
|
16
|
+
- Use only the APIs described here.
|
|
17
|
+
|
|
18
|
+
## Bootstrap every call
|
|
19
|
+
|
|
20
|
+
`mcp__node_repl__js` creates a fresh Worker per call. JavaScript globals, imports,
|
|
21
|
+
module cache and any binding do not survive into the next call, so `const app`
|
|
22
|
+
does **not** live past the end of the cell. The UI state itself does survive: the
|
|
23
|
+
host keeps each app's accessibility state and raster, so re-binding in the next
|
|
24
|
+
cell is cheap and does not re-observe.
|
|
25
|
+
|
|
26
|
+
The first executable statement of every CUA cell must be this bootstrap, and the
|
|
27
|
+
bootstrap and the actions must be in the **same** cell:
|
|
28
|
+
|
|
29
|
+
```js
|
|
30
|
+
const root =
|
|
31
|
+
process.env.DCODE_CUA_PLUGIN_ROOT ??
|
|
32
|
+
process.env.DCODE_PLUGIN_ROOT ??
|
|
33
|
+
process.env.CLAUDE_PLUGIN_ROOT;
|
|
34
|
+
const { join } = await import("node:path");
|
|
35
|
+
const { pathToFileURL } = await import("node:url");
|
|
36
|
+
const { setupComputerUseRuntime } = await import(
|
|
37
|
+
pathToFileURL(join(root, "scripts", "computer-use-client.mjs")).href,
|
|
38
|
+
);
|
|
39
|
+
await setupComputerUseRuntime({ globals: globalThis });
|
|
40
|
+
|
|
41
|
+
const app = await agent.computerUse.getApp("Notes");
|
|
42
|
+
await app.click(42);
|
|
43
|
+
await app.getAXState();
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## API
|
|
47
|
+
|
|
48
|
+
```typescript
|
|
49
|
+
type Vec2 = [x: number, y: number];
|
|
50
|
+
type ObservationOptions = { emit?: boolean };
|
|
51
|
+
type StateOptions = ObservationOptions & { disableDiffing?: boolean };
|
|
52
|
+
type StateAndScreenshot = { state: string; screenshot?: Uint8Array };
|
|
53
|
+
type Direction = "up" | "down" | "left" | "right" | "u" | "d" | "l" | "r";
|
|
54
|
+
type MouseButton = "left" | "right" | "middle" | "l" | "r" | "m";
|
|
55
|
+
type SelectionType = "text" | "cursor_before" | "cursor_after";
|
|
56
|
+
type Strategy = "auto" | "a11y" | "event";
|
|
57
|
+
|
|
58
|
+
type ClickOptions = {
|
|
59
|
+
mouseButton?: MouseButton;
|
|
60
|
+
clickCount?: number;
|
|
61
|
+
modifiers?: string; // "cmd+shift"; macOS cmd, Linux/Windows ctrl
|
|
62
|
+
strategy?: Strategy;
|
|
63
|
+
};
|
|
64
|
+
type SelectTextOptions = { prefix?: string; suffix?: string; selectionType?: SelectionType };
|
|
65
|
+
type PasteOptions = { format?: "text" | "md" | "html" };
|
|
66
|
+
type PressKeyOptions = { holdSeconds?: number; strategy?: Strategy };
|
|
67
|
+
type ScrollOptions = { strategy?: Strategy };
|
|
68
|
+
type DragOptions = { modifiers?: string; strategy?: Strategy };
|
|
69
|
+
|
|
70
|
+
type AXElement = {
|
|
71
|
+
index: number;
|
|
72
|
+
kind: string;
|
|
73
|
+
title: string | null;
|
|
74
|
+
value: string | null;
|
|
75
|
+
actions: string[];
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
interface Target {
|
|
79
|
+
getAXState(options?: StateOptions): Promise<string>;
|
|
80
|
+
getScreenshot(options?: ObservationOptions): Promise<Uint8Array>;
|
|
81
|
+
getAXStateAndScreenshot(options?: StateOptions): Promise<StateAndScreenshot>;
|
|
82
|
+
elements(): Promise<AXElement[]>;
|
|
83
|
+
|
|
84
|
+
paste(text: string, options?: PasteOptions): Promise<void>;
|
|
85
|
+
click(target: number | Vec2, options?: ClickOptions): Promise<void>;
|
|
86
|
+
drag(from: number | Vec2, to: number | Vec2, options?: DragOptions): Promise<void>;
|
|
87
|
+
pressKey(key: string, options?: PressKeyOptions): Promise<void>;
|
|
88
|
+
scroll(target: number | Vec2, direction: Direction, pages?: number,
|
|
89
|
+
options?: ScrollOptions): Promise<void>;
|
|
90
|
+
selectText(elementIndex: number, text: string, options?: SelectTextOptions): Promise<void>;
|
|
91
|
+
setValue(elementIndex: number, value: string): Promise<void>;
|
|
92
|
+
typeText(text: string): Promise<void>;
|
|
93
|
+
performSecondaryAction(elementIndex: number, action: string): Promise<void>;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
interface App extends Target {}
|
|
97
|
+
|
|
98
|
+
type AppRef = { name?: string; bundle_id?: string; pid?: number; window_id?: number };
|
|
99
|
+
|
|
100
|
+
type AppInfo = {
|
|
101
|
+
pid: number;
|
|
102
|
+
name: string | null;
|
|
103
|
+
bundle_id: string | null;
|
|
104
|
+
active: boolean;
|
|
105
|
+
};
|
|
106
|
+
type State = { apps: AppInfo[] };
|
|
107
|
+
|
|
108
|
+
type AccessStatus = {
|
|
109
|
+
ready: boolean;
|
|
110
|
+
accessibility: "granted" | "denied" | "unknown";
|
|
111
|
+
screenRecording: "granted" | "denied" | "unknown";
|
|
112
|
+
message?: string;
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
// Every registered tool is a method here, on every platform. See "Tool results"
|
|
116
|
+
// for what a call resolves to.
|
|
117
|
+
type Computer = { readonly target: "mac" | "windows" | "linux" } & Record<
|
|
118
|
+
string,
|
|
119
|
+
(args?: object) => Promise<unknown>
|
|
120
|
+
>;
|
|
121
|
+
|
|
122
|
+
declare const agent: {
|
|
123
|
+
computerUse: {
|
|
124
|
+
getState(options?: ObservationOptions): Promise<State>;
|
|
125
|
+
getApp(target: string | AppRef): Promise<App>;
|
|
126
|
+
listApps(options?: ObservationOptions): Promise<AppInfo[]>;
|
|
127
|
+
computer: Computer;
|
|
128
|
+
|
|
129
|
+
requestAccess(capabilities?: string[]): Promise<AccessStatus>;
|
|
130
|
+
stop(reason?: string): Promise<void>;
|
|
131
|
+
};
|
|
132
|
+
};
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
`agent.computerUse.computer` exposes the low-level tool surface. Prefer the bound
|
|
136
|
+
API above; reach for a tool only for what the API does not express.
|
|
137
|
+
|
|
138
|
+
## Tool arguments
|
|
139
|
+
|
|
140
|
+
Every tool takes **one** arguments object; the names below are its keys, not
|
|
141
|
+
positional parameters. Arguments are validated strictly — an undeclared key is
|
|
142
|
+
refused with `unrecognized_keys`, and a missing required key reports
|
|
143
|
+
`expected object, received undefined` for that field — so do not invent options
|
|
144
|
+
and do not flatten a nested value into the top level:
|
|
145
|
+
|
|
146
|
+
```js
|
|
147
|
+
// right
|
|
148
|
+
await agent.computerUse.computer.get_app_state({
|
|
149
|
+
app_ref: { bundle_id: "com.apple.Notes" },
|
|
150
|
+
include_screenshot: true,
|
|
151
|
+
});
|
|
152
|
+
// wrong: `app_ref` is missing, so the union for it reports "received undefined"
|
|
153
|
+
await agent.computerUse.computer.get_app_state({
|
|
154
|
+
bundle_id: "com.apple.Notes",
|
|
155
|
+
include_screenshot: true,
|
|
156
|
+
});
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
```
|
|
160
|
+
list_apps({})
|
|
161
|
+
list_windows({app_ref})
|
|
162
|
+
get_app_state({app_ref, include_screenshot?=false, disable_diffing?=false})
|
|
163
|
+
|
|
164
|
+
left_click({target, mouse_button?="left", click_count?=1, modifiers?="",
|
|
165
|
+
strategy?="auto", app_ref?, return_state?="none"})
|
|
166
|
+
left_click_drag({from_target, to, modifiers?="", app_ref?, return_state?="none"})
|
|
167
|
+
scroll({target, scroll_direction, scroll_amount, strategy?="auto", app_ref?,
|
|
168
|
+
return_state?="none"})
|
|
169
|
+
|
|
170
|
+
type({text, target?, app_ref?, strategy?="auto", return_state?="none"})
|
|
171
|
+
set_value({target, value, strategy?="auto", app_ref?, return_state?="none"})
|
|
172
|
+
select_text({target, text_range?, app_ref?, return_state?="none"})
|
|
173
|
+
key({text, repeat?, hold_seconds?, app_ref?, strategy?="auto", return_state?="none"})
|
|
174
|
+
paste({text, format?="text", app_ref?, return_state?="none"})
|
|
175
|
+
perform_action({target, action, app_ref?, return_state?="none"})
|
|
176
|
+
|
|
177
|
+
request_access({capabilities?})
|
|
178
|
+
stop_computer_control({reason?})
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
- `app_ref` — `{name: "Notes"}` for a display name, `{bundle_id:
|
|
182
|
+
"com.apple.Notes"}` for an identifier, or `{pid: 1234}`. Add `window_id` to
|
|
183
|
+
bind a single window. A bare string is read as a **bundle id**, never as a
|
|
184
|
+
display name. On Windows a display name is the name the OS lists (the
|
|
185
|
+
Start-menu name), not a window title.
|
|
186
|
+
- `target` — `{type: "element", index}` for an accessibility element, or
|
|
187
|
+
`{type: "coordinate", x, y}` for a raster pixel, optionally with `frame_id`. An
|
|
188
|
+
element index addresses the most recent observation of that app, so pass
|
|
189
|
+
`app_ref` with the action to say which app it belongs to. A `frame_id` names one
|
|
190
|
+
exact raster; omit it and the coordinate binds the session's most recent
|
|
191
|
+
actionable raster. The bound API supplies one when this cell took a raster and
|
|
192
|
+
omits it otherwise — either way you never handle a frame id.
|
|
193
|
+
- `scroll_direction` — `up | down | left | right`. `scroll_amount` is in pages,
|
|
194
|
+
clamped to 0–100.
|
|
195
|
+
- `text_range` — `[start, length]` into the element's current value. `select_text`
|
|
196
|
+
without it selects the whole value.
|
|
197
|
+
- `strategy` — `auto | a11y | event`. `auto` prefers accessibility and falls back
|
|
198
|
+
to synthetic events; the other two force one path. `event` sends global input and
|
|
199
|
+
requires the target app (and `window_id`, when given) to be frontmost already —
|
|
200
|
+
it never activates anything, so on a background app it is refused with
|
|
201
|
+
`FOREGROUND_REQUIRED` and nothing is sent. Target an element instead.
|
|
202
|
+
- `return_state` — `compact | full | none` (default `none`). Returns the app state
|
|
203
|
+
in the same call, saving a separate observation. The bound API does its own
|
|
204
|
+
observing, so this only matters when you call a tool directly.
|
|
205
|
+
- `modifiers` — a `+`-separated chord held for the duration of the action, e.g.
|
|
206
|
+
`"cmd+shift"`.
|
|
207
|
+
- `repeat` — how many times `key` re-sends the chord. Use it instead of a loop of
|
|
208
|
+
`pressKey` calls.
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
## Tool results
|
|
212
|
+
|
|
213
|
+
The bound API returns typed values. `computer.*` instead hands back what the tool
|
|
214
|
+
itself produced, after one unwrapping step, so a call resolves to one of three
|
|
215
|
+
shapes:
|
|
216
|
+
|
|
217
|
+
- **The parsed payload.** A result carrying exactly one text block is
|
|
218
|
+
`JSON.parse`d for you, so you get the tool's own shape with no `content` to
|
|
219
|
+
walk. `list_apps` and `list_windows` land here.
|
|
220
|
+
- **A plain string.** The same single-text-block result, when its text is not
|
|
221
|
+
JSON. `get_app_state` without a screenshot lands here, so you get the rendered
|
|
222
|
+
tree and nothing else: the `structuredContent` that carried `state_id` and the
|
|
223
|
+
element rows does not survive the unwrap. Use a bound app's `elements()` for
|
|
224
|
+
that table.
|
|
225
|
+
- **The raw MCP envelope,** `{ content: [...] }`. Returned unchanged for anything
|
|
226
|
+
else, most often because `include_screenshot: true` added an image block. Read
|
|
227
|
+
the blocks out of `content`; there is no parsed payload, and the envelope also
|
|
228
|
+
carries host-only fields this contract does not cover.
|
|
229
|
+
|
|
230
|
+
So branch on the shape instead of assuming one:
|
|
231
|
+
|
|
232
|
+
```js
|
|
233
|
+
const wins = await agent.computerUse.computer.list_windows({
|
|
234
|
+
app_ref: { name: "Notes" },
|
|
235
|
+
});
|
|
236
|
+
nodeRepl.write(`windows: ${JSON.stringify(wins)}`); // parsed payload
|
|
237
|
+
|
|
238
|
+
const shot = await agent.computerUse.computer.get_app_state({
|
|
239
|
+
app_ref: { name: "Notes" },
|
|
240
|
+
include_screenshot: true,
|
|
241
|
+
});
|
|
242
|
+
const image = shot.content.find((block) => block.type === "image"); // envelope
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
Never pass a whole envelope to `nodeRepl.write` or `JSON.stringify`: the image
|
|
246
|
+
block is large, and a bound app's `getAXStateAndScreenshot()` already submits the
|
|
247
|
+
picture for you.
|
|
248
|
+
|
|
249
|
+
## Windows
|
|
250
|
+
|
|
251
|
+
A capture is always scoped to one window of one app, so a multi-window app needs
|
|
252
|
+
you to say which window. `list_windows` returns one row per window, in the app's
|
|
253
|
+
own window order:
|
|
254
|
+
|
|
255
|
+
- `window_id` — stable integer. Pass it back inside `app_ref` to address that
|
|
256
|
+
window.
|
|
257
|
+
- `title` — the window title; `""` or `null` for a window that carries none.
|
|
258
|
+
`""` means the window has an `AXTitle` that is empty — a Qt or custom-drawn
|
|
259
|
+
panel typically looks like this. `null` means the title could not be read at
|
|
260
|
+
all, which happens both for an AX window whose `AXTitle` read fails and for a
|
|
261
|
+
row the broker merged in from CoreGraphics, so it does not tell the two apart.
|
|
262
|
+
Use `subrole` for that distinction.
|
|
263
|
+
- `subrole` — the window's `AXSubrole`, present only on a real AX window. A row
|
|
264
|
+
merged in from CoreGraphics (a WindowServer surface the accessibility tree does
|
|
265
|
+
not expose) has no `subrole` at all, so the field's presence is what separates
|
|
266
|
+
"a window you can bind and operate" from "a leftover surface". Its own value
|
|
267
|
+
discriminates weakly: a survey of 34 windows across 30 apps found 31 of them
|
|
268
|
+
reporting `AXStandardWindow`, with `AXDialog` the only other value that carried
|
|
269
|
+
information. macOS only.
|
|
270
|
+
- `text_preview` — a content sample for a window with no usable title: the first
|
|
271
|
+
few strings found inside it, joined and truncated. **Never an identifier.** The
|
|
272
|
+
order comes from the accessibility child order, which is creation order and
|
|
273
|
+
carries no semantic guarantee — in the same app a dialog yields its heading
|
|
274
|
+
while a main window yields toolbar noise. It is collected only when `title` is
|
|
275
|
+
empty, omitted when nothing could be collected, and it can contain whatever the
|
|
276
|
+
window displays, including the user's own data. Read it as a hint for choosing
|
|
277
|
+
which window to observe, then confirm by observing that window. macOS only.
|
|
278
|
+
- `bounds` — `[x, y, width, height]` in global screen points. Diagnostic only:
|
|
279
|
+
these must never be copied into a coordinate target.
|
|
280
|
+
- `main` — the app's AX main window. `focused` — the app's AX focused (key)
|
|
281
|
+
window. Neither one means the app is the system-frontmost application;
|
|
282
|
+
`listApps()` reports that as `active`.
|
|
283
|
+
- `onscreen` — `false` marks a window CoreGraphics still owns after the app closed
|
|
284
|
+
its real windows. That row is neither user-visible nor operable, so skip it when
|
|
285
|
+
choosing a target. It carries a different meaning from occluded or minimized,
|
|
286
|
+
both of which stay operable and stay `true`.
|
|
287
|
+
- `index` — the row's position in this response, not an element index and not a
|
|
288
|
+
window id.
|
|
289
|
+
|
|
290
|
+
Bind a window and every call in that cell stays on it:
|
|
291
|
+
|
|
292
|
+
```js
|
|
293
|
+
const app = await agent.computerUse.getApp({ pid: 23192, window_id: 27367 });
|
|
294
|
+
await app.getAXState();
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
`agent.computerUse.getWindow(target, windowId)` binds the same way and reads
|
|
298
|
+
better when the target is a name. Either form throws `STALE_STATE` when that
|
|
299
|
+
window cannot be resolved, instead of quietly capturing a different one.
|
|
300
|
+
|
|
301
|
+
Without a `window_id` the app's main/key window is captured, and that resolution
|
|
302
|
+
runs again on **every** observation. A modal that opened since the last
|
|
303
|
+
observation therefore becomes the captured window. Element indices are
|
|
304
|
+
per-window and are renumbered by each observation, so an index taken before the
|
|
305
|
+
window changed can address an unrelated control afterwards.
|
|
306
|
+
|
|
307
|
+
So when an action fails unexpectedly, or the tree reads like a different part of
|
|
308
|
+
the app, check the observation's `window` block first: a modal dialog appears
|
|
309
|
+
there as the captured window. Read its contents, then decide. Dismiss it only
|
|
310
|
+
when it is not the intended target — press Escape, or use the dialog's own cancel
|
|
311
|
+
control — and observe again afterwards. When the dialog *is* the task, act on it.
|
|
312
|
+
Judging "my previous action worked" from a tree that turns out to be the dialog's
|
|
313
|
+
is the specific mistake this section exists to prevent: the element you were
|
|
314
|
+
looking for is absent because you are reading a different window, not because
|
|
315
|
+
the action removed it.
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
## Workflow
|
|
319
|
+
|
|
320
|
+
After performing one or more UI actions, call `getAXState()` before deciding what
|
|
321
|
+
to do next: that is how you see what your actions did.
|
|
322
|
+
|
|
323
|
+
An element index addresses the app's most recent observation, so several actions
|
|
324
|
+
may reuse one index without re-observing between them — click an index, then type
|
|
325
|
+
into it, in the same cell. Observing renumbers the tree, so take indices from the
|
|
326
|
+
newest one. A vanished element fails closed with `ELEMENT_UNAVAILABLE`.
|
|
327
|
+
|
|
328
|
+
The accessibility tree comes back as a diff only against a tree this cell already
|
|
329
|
+
showed you, listing the removed, added and changed elements; the omitted rows are
|
|
330
|
+
unchanged and their indices stay valid. The first tree after binding, and the
|
|
331
|
+
first after a screenshot-only observation, are always complete. Pass
|
|
332
|
+
`{ disableDiffing: true }` for a full tree at any point. If a standalone
|
|
333
|
+
`getAXState()` reports no change, do not immediately repeat it without an
|
|
334
|
+
intervening action.
|
|
335
|
+
|
|
336
|
+
Minimize round trips while keeping the state fresh:
|
|
337
|
+
|
|
338
|
+
- Batch deterministic actions and the resulting `getAXState()` into one call.
|
|
339
|
+
- `agent.computerUse.getApp(...)` returns the binding and displays nothing; call
|
|
340
|
+
`getAXState()` when you need to see the state.
|
|
341
|
+
- Prefer a directly relevant result already visible in the current state over
|
|
342
|
+
opening broader intermediate UI such as "Show All".
|
|
343
|
+
- Once the requested result is visibly present, stop exploring and respond.
|
|
344
|
+
|
|
345
|
+
```typescript
|
|
346
|
+
await target.click(42);
|
|
347
|
+
await target.setValue(42, "openai.com");
|
|
348
|
+
await target.pressKey("Return");
|
|
349
|
+
await target.typeText("hello");
|
|
350
|
+
await target.scroll(42, "down", 1);
|
|
351
|
+
await target.scroll([640, 480], "down", 1);
|
|
352
|
+
await target.selectText(42, "hello");
|
|
353
|
+
await target.performSecondaryAction(42, "Expand");
|
|
354
|
+
await target.getAXState();
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
## Output
|
|
358
|
+
|
|
359
|
+
- For text, use `nodeRepl.write(...)`. For images, `nodeRepl.emitImage(...)`.
|
|
360
|
+
- `getAXState()`, `getScreenshot()`, `getAXStateAndScreenshot()`,
|
|
361
|
+
`agent.computerUse.getState()` and `agent.computerUse.listApps()`
|
|
362
|
+
**display their own result**; `agent.computerUse.getApp(...)` does not — it binds only. Never pass their return value to `write` or
|
|
363
|
+
`emitImage`: a second raster in one result breaks the one-raster rule and the
|
|
364
|
+
frame is removed entirely, leaving no picture at all. Pass `{ emit: false }` to
|
|
365
|
+
suppress the display and still get the value back.
|
|
366
|
+
- Action methods display nothing and resolve to `undefined` on success.
|
|
367
|
+
|
|
368
|
+
## Errors
|
|
369
|
+
|
|
370
|
+
Actions resolve to `undefined` when they succeed and throw `ComputerUseError`
|
|
371
|
+
when they do not. The fields you act on:
|
|
372
|
+
|
|
373
|
+
- `code` — `PERMISSION_DENIED`, `NOT_AUTHORIZED`, `APP_NOT_FOUND`,
|
|
374
|
+
`AMBIGUOUS_APP`, `LAUNCH_FAILED`, `INVALID_APP`, `ELEMENT_UNAVAILABLE`,
|
|
375
|
+
`STALE_STATE`, `NOT_SETTABLE`, `NOT_SELECTABLE`, `ACTION_UNAVAILABLE`,
|
|
376
|
+
`FOREGROUND_REQUIRED`, `CONTROLLER_BUSY`, `CONTROL_STOPPED`, `SCREEN_LOCKED`,
|
|
377
|
+
`HELPER_UNAVAILABLE`, `VERSION_MISMATCH`, `TIMEOUT`,
|
|
378
|
+
`STRUCTURED_STATE_UNAVAILABLE`, `INTERNAL`.
|
|
379
|
+
- `actionSent` — whether the action may already have reached the app. Retry a
|
|
380
|
+
non-idempotent action only when this is `false`; otherwise observe first.
|
|
381
|
+
- `FOREGROUND_REQUIRED` comes only from `strategy: "event"` (and from `paste` on a
|
|
382
|
+
background app). On macOS nothing in this surface can bring an app forward, so do
|
|
383
|
+
not retry it and do not look for an activation call: switch to an element target,
|
|
384
|
+
`setValue`, or an accessibility action, which all work on a background app.
|
|
385
|
+
- `retry` — `"reobserve"`, `"retry"` or `"never"`.
|
|
386
|
+
|
|
387
|
+
```js
|
|
388
|
+
try {
|
|
389
|
+
await app.performSecondaryAction(7, "Show Menu");
|
|
390
|
+
} catch (e) {
|
|
391
|
+
if (e.code === "ACTION_UNAVAILABLE") {
|
|
392
|
+
await app.getAXState({ disableDiffing: true }); // the tree lists each element's actions
|
|
393
|
+
} else if (e.code === "CONTROLLER_BUSY") {
|
|
394
|
+
nodeRepl.write(`another DCode Computer Use session owns control: ${e.details.owner}`);
|
|
395
|
+
} else if (e.actionSent) {
|
|
396
|
+
await app.getAXState(); // may already have landed; look first
|
|
397
|
+
} else throw e;
|
|
398
|
+
}
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
`CONTROLLER_BUSY` is never retryable. Report the owner from the error and ask the
|
|
402
|
+
user to close the other session. Stop immediately after `agent.computerUse.stop()`, a kill
|
|
403
|
+
switch, or a permission refusal, and do not switch to a different UI-automation
|
|
404
|
+
technology after an access refusal.
|
|
405
|
+
|
|
406
|
+
## Notes
|
|
407
|
+
|
|
408
|
+
- `elements()` returns the element table as data, including the rows the rendered
|
|
409
|
+
tree trimmed for length, so filter it in JS instead of guessing a hidden index.
|
|
410
|
+
It observes without displaying anything, which costs one broker round trip and
|
|
411
|
+
makes the next `getAXState()` return a full tree rather than a diff — reach for
|
|
412
|
+
it when the tree said `indices are sparse`, not on every turn.
|
|
413
|
+
- A container can also report only part of its children, and that is a different
|
|
414
|
+
limit from the one above: the header says `showing A-B of N items`, and those
|
|
415
|
+
rows never entered the observation, so `elements()` does not recover them
|
|
416
|
+
either. This is what a long list or a spreadsheet grid looks like — the rows you
|
|
417
|
+
need may simply have no index. Reach the rest through the app itself: scroll the
|
|
418
|
+
container and observe again, or move the app's own selection (arrow keys, a
|
|
419
|
+
Name Box, a search field) and read the value back from the focused element.
|
|
420
|
+
- Prefer element-index actions over coordinate actions whenever an accessibility
|
|
421
|
+
element is available. They are semantic, precise, background-safe and do not
|
|
422
|
+
steal the user's focus. Fall back to a screenshot and coordinates only when
|
|
423
|
+
accessibility cannot locate or express the target.
|
|
424
|
+
- Coordinates are integer pixels of the **latest returned raster**. Take them only
|
|
425
|
+
by looking at that raster. Element and window bounds in the tree are diagnostic
|
|
426
|
+
global screen points and must never be used as a coordinate. The SDK binds the
|
|
427
|
+
raster internally; you never pass a frame id.
|
|
428
|
+
- A pointer action can be accepted and still do nothing where you aimed. Some apps
|
|
429
|
+
ignore the position carried by a synthesized pointer event and act on wherever the
|
|
430
|
+
real pointer happens to sit, so the same coordinate produces a different result
|
|
431
|
+
depending on where the user left the mouse. The next observation says so with
|
|
432
|
+
`[effect_evidence unchanged]`; when it does, **do not repeat the coordinate** —
|
|
433
|
+
act on an element index, or drive the app from the keyboard (arrow keys,
|
|
434
|
+
shortcuts, or a value typed into a field), which lands on a background app.
|
|
435
|
+
- The tree always lists each element's `actions`. `performSecondaryAction` accepts
|
|
436
|
+
only an action the element advertises — do not guess one.
|
|
437
|
+
- `selectText` locates text inside an editable element. Use `prefix`/`suffix` to
|
|
438
|
+
disambiguate repeated matches and `selectionType` to place the cursor instead of
|
|
439
|
+
selecting. An ambiguous match is refused rather than resolved to the first hit.
|
|
440
|
+
- `pressKey` accepts xdotool-style keysyms as well as short names: `"a"`,
|
|
441
|
+
`"Return"`, `"Tab"`, `"Control_L+a"`, `"super+c"`, `"Up"`. macOS uses `cmd`;
|
|
442
|
+
Linux and Windows use `ctrl`. `{ holdSeconds }` holds the key or chord.
|
|
443
|
+
- Use `paste` for rich text, or for a target `setValue` cannot set; for plain text
|
|
444
|
+
into a settable element `setValue` is the better path. It borrows the system
|
|
445
|
+
pasteboard and restores the user's clipboard afterwards. A paste no app reads
|
|
446
|
+
fails with a timeout rather than reporting success. A paste shortcut cannot name
|
|
447
|
+
a target field, so it lands wherever the keyboard focus is — check the result.
|
|
448
|
+
- There is no need to open or launch apps; `agent.computerUse.getApp(...)` launches the app in
|
|
449
|
+
the background if it is not already running. There is no separate launch tool.
|
|
450
|
+
Its argument is a display name, a
|
|
451
|
+
bundle identifier, or the same `{name|bundle_id|pid}` object the tools take.
|
|
452
|
+
For a string the SDK sends a
|
|
453
|
+
dotted, space-free string as `bundle_id` and anything else as `name`, and
|
|
454
|
+
retries with the other field once if the app is not found, so either form
|
|
455
|
+
works. There is no path form: `app_ref` has no path field.
|
|
456
|
+
Nothing here fronts a window or takes the user's focus, except on Windows:
|
|
457
|
+
launching a non-packaged app takes focus, and `include_screenshot=true` on a
|
|
458
|
+
minimized window restores it. A minimized window's tree stays fully usable, so
|
|
459
|
+
keep the default unless you need pixels.
|
|
460
|
+
- Observations wait an appropriate amount of time before capturing. Do **not**
|
|
461
|
+
pause or delay (no `setTimeout`) before getting UI state; rely on that wait.
|
|
462
|
+
- When the state does not show what you expected, observe once more before trying
|
|
463
|
+
a different approach: an animation or a load may still have been in flight.
|
|
464
|
+
|
|
465
|
+
Persist until the request is fully completed end to end. Attempting an action is
|
|
466
|
+
not completion: verify that the returned UI state visibly shows the requested
|
|
467
|
+
result. If an action leaves the state unchanged, produces no results, or only
|
|
468
|
+
reaches an intermediate page, try another approach. Respond only after the
|
|
469
|
+
requested state is visibly present, or explain a concrete blocker you cannot
|
|
470
|
+
resolve.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json.schemastore.org/package.json",
|
|
3
|
+
"name": "@dcode/cli-computer-use-plugin",
|
|
4
|
+
"version": "0.6.3",
|
|
5
|
+
"description": "DCode CUA SDK and skill package; execution is provided by the shared node_repl host.",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"main": "./scripts/computer-use-client.mjs",
|
|
9
|
+
"dependencies": {
|
|
10
|
+
"@dcode/dcode-cua": "workspace:*"
|
|
11
|
+
}
|
|
12
|
+
}
|