-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathaci.schema.json
More file actions
362 lines (362 loc) · 36.2 KB
/
Copy pathaci.schema.json
File metadata and controls
362 lines (362 loc) · 36.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://shinken.dev/schema/aci/v0",
"title": "Shinken ACI v0 — wire messages",
"description": "Agent-Computer Interface v0: the typed messages on the WebSocket between the client/Operator and the in-Sandbox Guest Runtime (shinkend). Discriminated by `type`. See docs/05-tech-decisions.md (D2) and docs/10-phase0-plan.md.",
"type": "object",
"required": ["type"],
"discriminator": { "propertyName": "type" },
"oneOf": [
{ "$ref": "#/$defs/Hello" },
{ "$ref": "#/$defs/Welcome" },
{ "$ref": "#/$defs/Ping" },
{ "$ref": "#/$defs/Pong" },
{ "$ref": "#/$defs/Query" },
{ "$ref": "#/$defs/Result" },
{ "$ref": "#/$defs/ActionMsg" },
{ "$ref": "#/$defs/Ack" },
{ "$ref": "#/$defs/ObservationMsg" },
{ "$ref": "#/$defs/ExecOutputMsg" },
{ "$ref": "#/$defs/ExecExitMsg" }
],
"$defs": {
"Hello": {
"type": "object", "additionalProperties": false,
"required": ["type", "v", "client"],
"properties": {
"type": { "const": "hello" },
"v": { "const": 0 },
"client": { "type": "object", "required": ["name", "version"], "properties": { "name": { "type": "string" }, "version": { "type": "string" } } },
"accept": { "type": "object", "properties": { "observation_types": { "type": "array", "items": { "$ref": "#/$defs/ObservationType" } }, "binary_frames": { "type": "boolean", "description": "opt in to binary media framing: when true AND the runtime advertises capabilities.binary_frames, every image-bearing observation arrives as one binary WebSocket message (u32 LE header_len | JSON header per $defs.BinaryFrameHeader | raw codec payload) instead of base64-in-JSON text. Omitted/false keeps text frames." } } },
"token": { "type": "string", "description": "bearer token required by every TCP runtime; the runtime compares it in constant time (#135)." }
}
},
"Welcome": {
"type": "object", "additionalProperties": false,
"required": ["type", "v", "server", "capabilities"],
"properties": {
"type": { "const": "welcome" },
"v": { "const": 0 },
"server": { "type": "object", "required": ["name", "version", "platform"], "properties": { "name": { "type": "string" }, "version": { "type": "string" }, "platform": { "$ref": "#/$defs/Platform" } } },
"capabilities": {
"type": "object", "additionalProperties": false,
"required": ["schema_version", "verbs", "targets", "observation_types"],
"properties": {
"schema_version": { "type": "integer", "const": 0 },
"verbs": { "type": "array", "items": { "$ref": "#/$defs/Verb" } },
"targets": { "type": "array", "items": { "$ref": "#/$defs/TargetKind" } },
"observation_types": { "type": "array", "items": { "$ref": "#/$defs/ObservationType" } },
"max_long_edge": { "type": "integer", "minimum": 1 },
"image_formats": { "type": "array", "items": { "$ref": "#/$defs/ImageFormat" }, "description": "image codecs this runtime can encode; a client must not request a format outside this list. Absent on pre-negotiation welcomes, which a client treats as png-only." },
"binary_frames": { "type": "boolean", "description": "whether this runtime can deliver image-bearing observations as binary WebSocket messages when the client opts in via hello.accept.binary_frames (see $defs.BinaryFrameHeader). Absent on pre-binary welcomes, which a client treats as false (text frames only)." },
"frame_dedup": { "type": "boolean", "description": "whether this runtime understands content-negotiated screenshots: it emits `frame_hash` on screenshot observations, honors `if_none_match` on the screenshot action, and answers a hash match with the compact `not_modified` observation (no payload). Absent on older welcomes, which a client treats as false — a client MUST NOT send if_none_match unless this is advertised (older runtimes reject unknown action fields)." },
"observe_after_act": { "type": "boolean", "description": "whether this runtime honors the per-action `observe` argument (see $defs.ObserveSpec): a mutating action's ack is followed by a fresh observation with cause = the action's call_id. Absent on older welcomes, which a client treats as false (the client must not send `observe`)." },
"structured_observation": { "type": "boolean", "description": "whether this runtime ships the guest-side structured-observation engine: the `observe` verb (a11y tree with stable element refs, tree_text full/diff rendering, settle), element_ref action targets resolved guest-side, and the `invoke_action`/`set_value` element verbs. A capability of the binary — backend (AT-SPI) availability is a runtime condition answered with typed errors. Absent on pre-engine welcomes, which a client treats as false (SDK-local fallback)." }
}
}
}
},
"Ping": { "type": "object", "additionalProperties": false, "required": ["type"], "properties": { "type": { "const": "ping" }, "t": { "type": "number" } } },
"Pong": { "type": "object", "additionalProperties": false, "required": ["type"], "properties": { "type": { "const": "pong" }, "t": { "type": "number" } } },
"Query": {
"type": "object", "additionalProperties": false,
"required": ["type", "call_id", "q"],
"properties": { "type": { "const": "query" }, "call_id": { "type": "string" }, "q": { "enum": ["platform", "screen_size", "ready", "list_windows"] } }
},
"Result": {
"type": "object", "additionalProperties": false,
"required": ["type", "call_id", "ok"],
"properties": { "type": { "const": "result" }, "call_id": { "type": "string" }, "ok": { "type": "boolean" }, "value": {}, "error": { "type": "string" } }
},
"ActionMsg": {
"type": "object", "additionalProperties": false,
"required": ["type", "call_id", "action"],
"properties": { "type": { "const": "action" }, "call_id": { "type": "string" }, "action": { "$ref": "#/$defs/Action" } }
},
"Ack": {
"type": "object", "additionalProperties": false,
"required": ["type", "call_id", "ok"],
"properties": { "type": { "const": "ack" }, "call_id": { "type": "string" }, "ok": { "type": "boolean" }, "error": { "type": "string" } }
},
"ObservationMsg": {
"type": "object", "additionalProperties": false,
"required": ["type", "obs_id"],
"properties": {
"type": { "const": "observation" },
"obs_id": { "type": "string" },
"cause": { "type": "string", "description": "action call_id; omitted on server-pushed stream frames" },
"stream": { "type": "string", "description": "screencast stream id (set on server-pushed frames)" },
"seq": { "type": "integer", "minimum": 0, "description": "monotonic frame index within a stream" },
"display": { "$ref": "#/$defs/CoordinateSpace" },
"tree": { "enum": ["full", "diff"], "description": "what tree_text contains: the full rendered tree, or a diff against revision diff_of (the structured `elements` array is always the FULL live list)" },
"tree_text": { "type": "string", "description": "legible serialization of the a11y tree for the model: a header line (app, window, revision), one numbered indented line per element (`e<id> <role> [(states)] [\"title\"] [Value:…] [Actions:…]`), and a `focus:` trailer. In diff form: `~` changed / `+` added lines plus a summarized `- removed:` id-range line, or an explicit no-change notice" },
"revision": { "type": "integer", "minimum": 1, "description": "per-session monotonic observation revision (increments on every structured observe)" },
"diff_of": { "type": "integer", "minimum": 1, "description": "the revision this diff was rendered against; only present when tree=diff" },
"focus": { "type": "string", "description": "element_ref of the focused element (e.g. \"e12\"); absent when nothing focused" },
"node_count": { "type": "integer", "minimum": 0, "description": "number of elements in this capture" },
"capture_ms": { "type": "number", "minimum": 0, "description": "guest-side capture time (settle + walk + render), milliseconds" },
"elements": { "type": "array", "items": { "$ref": "#/$defs/Element" } },
"delta": { "type": "object", "properties": { "added": { "type": "array", "items": { "$ref": "#/$defs/Element" } }, "removed": { "type": "array", "items": { "type": "string" } }, "changed": { "type": "array", "items": { "$ref": "#/$defs/Element" } } } },
"image": { "type": "object", "required": ["ref", "w", "h"], "properties": { "ref": { "type": "string" }, "w": { "type": "integer" }, "h": { "type": "integer" }, "scope": { "$ref": "#/$defs/Scope" }, "format": { "$ref": "#/$defs/ImageFormat", "description": "codec of `ref` bytes; defaults to png when absent" } } },
"tiles": { "type": "array", "minItems": 1, "items": { "$ref": "#/$defs/Tile" }, "description": "dirty-tile delta frame (start_screencast delta=true): only the tiles that changed since the previous delivered frame, INSTEAD of `image`. Tile coordinates are in the delivered (post-downscale) resolution of the stream's last full keyframe; the consumer composites them onto that keyframe. Each tile's `ref` is encoded in the stream's format/quality." },
"frame_hash": { "type": "string", "description": "content hash of the captured frame's RAW pixels (post-scope/downscale, pre-encode — codec-independent: xxh3-128 lowercase hex over RGB with dims as seed; 32 chars since the 64-bit fnv1a era, but OPAQUE to clients — echo it back verbatim, never parse it; a hash minted by an older runtime simply never matches and yields a full frame). Set on one-shot screenshot observations from a frame_dedup runtime; the value a client echoes back as the screenshot action's if_none_match." },
"not_modified": { "const": true, "description": "content-negotiated screenshot (if_none_match): the freshly captured frame's hash equals the request's if_none_match, so the payload is omitted — the client already holds these exact pixels, keyed by frame_hash. Always carries frame_hash + cause and never image/tiles. Sent as a JSON text frame even on a binary-negotiated session (there is no payload to carry)." },
"pointer": { "type": "array", "items": { "type": "integer" }, "minItems": 2, "maxItems": 2, "description": "live pointer position in global point_px action coordinates [x, y] — observation METADATA, never composited into the frame (captures are cursor-free by design; frame_hash dedup and idle suppression depend on that). Set on one-shot screenshot and not_modified replies when the backend can report it; omitted on screencast stream frames and on backends that cannot say." }
},
"allOf": [
{ "if": { "required": ["stream"] }, "then": { "required": ["stream", "seq"], "anyOf": [ { "required": ["image"] }, { "required": ["tiles"] } ] } },
{ "if": { "required": ["seq"] }, "then": { "required": ["stream"] } },
{ "if": { "required": ["tiles"] }, "then": { "required": ["stream"], "not": { "required": ["image"] } } },
{ "if": { "required": ["not_modified"] }, "then": { "required": ["not_modified", "frame_hash", "cause"], "allOf": [ { "not": { "required": ["image"] } }, { "not": { "required": ["tiles"] } } ] } }
]
},
"Tile": {
"type": "object", "additionalProperties": false,
"required": ["x", "y", "w", "h", "ref"],
"properties": {
"x": { "type": "integer", "minimum": 0, "description": "tile left edge (px) in the delivered resolution" },
"y": { "type": "integer", "minimum": 0, "description": "tile top edge (px) in the delivered resolution" },
"w": { "type": "integer", "minimum": 1 },
"h": { "type": "integer", "minimum": 1 },
"ref": { "type": "string", "description": "base64 image of this tile, encoded per the stream's format/quality" }
}
},
"BinaryFrameHeader": {
"type": "object", "additionalProperties": false,
"required": ["type", "obs_id"],
"description": "NOT a top-level text message (deliberately absent from the oneOf): the JSON header inside a binary WebSocket media frame, sent only on a session that negotiated hello.accept.binary_frames against capabilities.binary_frames. The frame layout is `u32 LE header_len | this header | payload area`; the header mirrors ObservationMsg with every image/tile `ref` (base64 string) replaced by `off`/`len` byte offsets into the payload area (relative to its start, byte 4+header_len of the message). Carries exactly one of `image` or `tiles`.",
"properties": {
"type": { "const": "observation" },
"obs_id": { "type": "string" },
"cause": { "type": "string", "description": "action call_id; set on one-shot screenshots, omitted on stream frames" },
"stream": { "type": "string" },
"seq": { "type": "integer", "minimum": 0 },
"display": { "$ref": "#/$defs/CoordinateSpace" },
"image": {
"type": "object", "additionalProperties": false,
"required": ["off", "len", "w", "h"],
"properties": {
"off": { "type": "integer", "minimum": 0, "description": "payload byte offset of the image bytes" },
"len": { "type": "integer", "minimum": 0, "description": "image byte length" },
"w": { "type": "integer" },
"h": { "type": "integer" },
"scope": { "$ref": "#/$defs/Scope" },
"format": { "$ref": "#/$defs/ImageFormat" }
}
},
"tiles": { "type": "array", "minItems": 1, "items": { "$ref": "#/$defs/BinaryTile" } },
"frame_hash": { "type": "string", "description": "content hash of the frame's RAW pixels (see ObservationMsg.frame_hash) — set on one-shot screenshot headers from a frame_dedup runtime" },
"pointer": { "type": "array", "items": { "type": "integer" }, "minItems": 2, "maxItems": 2, "description": "live global pointer position in action pixels [x, y]" }
},
"allOf": [
{ "if": { "required": ["stream"] }, "then": { "required": ["stream", "seq"], "anyOf": [ { "required": ["image"] }, { "required": ["tiles"] } ] } },
{ "if": { "required": ["seq"] }, "then": { "required": ["stream"] } },
{ "if": { "required": ["tiles"] }, "then": { "required": ["stream"], "not": { "required": ["image"] } } }
]
},
"BinaryTile": {
"type": "object", "additionalProperties": false,
"required": ["x", "y", "w", "h", "off", "len"],
"properties": {
"x": { "type": "integer", "minimum": 0 },
"y": { "type": "integer", "minimum": 0 },
"w": { "type": "integer", "minimum": 1 },
"h": { "type": "integer", "minimum": 1 },
"off": { "type": "integer", "minimum": 0, "description": "payload byte offset of this tile's bytes (tiles are concatenated in tile order)" },
"len": { "type": "integer", "minimum": 0, "description": "tile byte length" }
}
},
"Action": {
"type": "object", "additionalProperties": false,
"required": ["verb"],
"properties": {
"verb": { "$ref": "#/$defs/Verb" },
"target": { "$ref": "#/$defs/Target" },
"to": { "$ref": "#/$defs/Target", "description": "drag destination — the pointer goes down at `target`, moves along an interpolated path, and releases at `to`" },
"button": { "$ref": "#/$defs/PointerButton", "description": "pointer button for drag/mouse_down/mouse_up; omitted = left" },
"duration_ms": { "type": "integer", "minimum": 0, "description": "drag gesture duration (ms) spread across the interpolated path; omitted = 0 (fastest). The runtime clamps absurd values" },
"observe": { "$ref": "#/$defs/ObserveSpec", "description": "act-returns-observation: on a mutating verb, the runtime follows the ack with a fresh observation (cause = this action's call_id), captured per these screenshot-shaped parameters. Requires the welcome's capabilities.observe_after_act; rejected on non-mutating verbs" },
"text": { "type": "string" },
"keys": { "type": "string", "description": "e.g. 'ctrl+s'" },
"dx": { "type": "number" },
"dy": { "type": "number" },
"ms": { "type": "integer", "minimum": 0 },
"scope": { "$ref": "#/$defs/Scope" },
"fps": { "type": "number", "minimum": 0.1, "maximum": 30, "description": "start_screencast target frame rate (frames/sec), rejected outside the runtime bounds" },
"max_long_edge": { "type": "integer", "minimum": 1, "description": "downscale cap (px) for screencast/screenshot frames" },
"format": { "$ref": "#/$defs/ImageFormat", "description": "wire codec for screenshot/start_screencast frames: png (default, lossless) or jpeg (bandwidth lever); omitted = png. Must be one of the welcome's advertised image_formats." },
"quality": { "type": "integer", "minimum": 1, "maximum": 100, "description": "jpeg quality 1-100 (ignored for png); omitted = runtime default" },
"delta": { "type": "boolean", "description": "start_screencast: dirty-tile delta mode. The runtime diffs each captured frame against the previous delivered one in 64px tiles and pushes only the changed tiles (`tiles` on the observation) instead of a full `image`; a full keyframe is sent on the first frame, after a resume, and periodically thereafter. Unchanged frames are idle-suppressed as usual. Omitted = false (full frames)." },
"resume_stream": { "type": "string", "description": "on start_screencast, asks the runtime to continue the named logical stream: if the runtime still holds that stream's state, pushed frames carry the SAME `stream` id and `seq` continues from where it left off (the frame gap is detectable from the first frame); otherwise a fresh stream starts (new id, seq 0) and the client learns continuity was lost" },
"if_none_match": { "type": "string", "description": "screenshot: a `frame_hash` from a previous screenshot observation (this session's or — across forked replicas with near-identical screens — another session's). If the freshly captured frame's raw-pixel hash equals it, the runtime answers with the compact `not_modified` observation instead of re-sending the payload. Only meaningful against a runtime advertising capabilities.frame_dedup; a client MUST NOT send it otherwise (older runtimes reject unknown action fields)." },
"structured": { "type": "boolean", "description": "observe: capture the structured (a11y) tree. Omitted defaults to true — observe IS the structured verb (pixels are `screenshot`); an explicit false is rejected by the runtime" },
"diff": { "type": "boolean", "description": "observe: render tree_text as a diff (`~`/`+`/`-` lines) against this session's previous revision; falls back to the full tree when no baseline exists or the diff exceeds the runtime's line budget. The `elements` array stays the full live list either way" },
"settle_ms": { "type": "integer", "minimum": 0, "description": "observe: debounce a11y change notifications for this quiesce window (ms, runtime-clamped) before walking, so the tree is captured after the UI settles; the total settle wait is hard-capped runtime-side" },
"argv": { "type": "array", "minItems": 1, "items": { "type": "string" }, "description": "exec: the program + arguments, executed directly (no shell interpretation) — the DEFAULT form. Exactly one of argv/shell is required" },
"shell": { "type": "string", "description": "exec: a shell command line, run via the guest's `/bin/sh -c` — the explicit opt-in alternative to argv (shell parsing is a deliberate choice, never a silent default). Mutually exclusive with argv" },
"cwd": { "type": "string", "description": "exec: working directory for the child process (guest path); omitted = the runtime's cwd" },
"env": { "type": "object", "additionalProperties": { "type": "string" }, "description": "exec: extra environment variables for the child (merged over the runtime's environment)" },
"timeout_ms": { "type": "integer", "minimum": 1, "description": "exec: kill-the-process-group deadline (ms, runtime-clamped to a hard max). Omitted = the runtime default (60 s). A timeout is reported honestly (timed_out=true), never disguised as an exit code" },
"stdin": { "type": "string", "description": "exec: text written to the child's stdin, which is then closed; omitted = stdin is closed immediately (/dev/null)" },
"stream": { "type": "boolean", "description": "exec: streamed form. The action is answered with an ack, stdout/stderr then arrive incrementally as server-pushed `exec_output` events (cause = this call_id; raw-byte binary frames on a binary-negotiated session) and the run terminates with one `exec_exit` event. Omitted/false = buffered form: one `result` whose value is $defs.ExecResult" },
"pty": { "const": false, "description": "RESERVED: PTY allocation for the exec channel (interactive shells, TUI programs) is a designed follow-up — a stream kind over the same exec_output/exec_exit events, not a second protocol. This revision pins the field name and rejects true" },
"app": { "type": "string", "minLength": 1, "description": "launch_app: the application to start — an executable name resolved on the guest's PATH or an absolute path, spawned detached with the session environment (Linux v1: $DISPLAY + the session D-Bus address shinkend itself runs under). activate_window: a window selector — the first list_windows entry whose title contains this string (case-insensitive) is activated" },
"args": { "type": "array", "items": { "type": "string" }, "description": "launch_app: argv tail passed verbatim to the executable (never through a shell — there is no interpolation surface)" },
"window_id": { "type": "integer", "minimum": 0, "description": "activate_window: the window to raise+focus — an `id` from the list_windows query (the same id usable as the window:<id> capture scope). Linux v1 sends the EWMH _NET_ACTIVE_WINDOW client message, falling back to raise+set-input-focus on a WM-less display" }
},
"allOf": [
{ "if": { "required": ["verb"], "properties": { "verb": { "enum": ["click", "double_click", "right_click", "move", "scroll"] } } }, "then": { "required": ["target"] } },
{ "if": { "required": ["verb"], "properties": { "verb": { "const": "drag" } } }, "then": { "required": ["target", "to"] } },
{ "if": { "required": ["verb"], "properties": { "verb": { "const": "type_text" } } }, "then": { "required": ["text"] } },
{ "if": { "required": ["verb"], "properties": { "verb": { "const": "key" } } }, "then": { "required": ["keys"] } },
{ "if": { "required": ["verb"], "properties": { "verb": { "enum": ["invoke_action", "set_value"] } } }, "then": { "required": ["target"] } },
{ "if": { "required": ["verb"], "properties": { "verb": { "const": "set_value" } } }, "then": { "required": ["text"] } },
{ "if": { "required": ["observe"] }, "then": { "properties": { "verb": { "$ref": "#/$defs/MutatingVerb" } } } },
{ "if": { "anyOf": [ { "required": ["structured"] }, { "required": ["diff"] }, { "required": ["settle_ms"] } ] }, "then": { "properties": { "verb": { "const": "observe" } } } },
{ "if": { "anyOf": [ { "required": ["argv"] }, { "required": ["shell"] }, { "required": ["cwd"] }, { "required": ["env"] }, { "required": ["timeout_ms"] }, { "required": ["stdin"] }, { "required": ["stream"] }, { "required": ["pty"] } ] }, "then": { "properties": { "verb": { "const": "exec" } } } },
{ "if": { "required": ["verb"], "properties": { "verb": { "const": "exec" } } }, "then": { "oneOf": [ { "required": ["argv"], "not": { "required": ["shell"] } }, { "required": ["shell"], "not": { "required": ["argv"] } } ] } },
{ "if": { "required": ["verb"], "properties": { "verb": { "const": "clipboard_set" } } }, "then": { "required": ["text"] } },
{ "if": { "required": ["verb"], "properties": { "verb": { "const": "launch_app" } } }, "then": { "required": ["app"] } },
{ "if": { "required": ["verb"], "properties": { "verb": { "const": "activate_window" } } }, "then": { "anyOf": [ { "required": ["window_id"] }, { "required": ["app"] } ] } },
{ "if": { "required": ["args"] }, "then": { "properties": { "verb": { "const": "launch_app" } } } },
{ "if": { "required": ["window_id"] }, "then": { "properties": { "verb": { "const": "activate_window" } } } },
{ "if": { "required": ["app"] }, "then": { "properties": { "verb": { "enum": ["launch_app", "activate_window"] } } } }
]
},
"Verb": { "enum": ["click", "double_click", "right_click", "move", "drag", "mouse_down", "mouse_up", "scroll", "type_text", "key", "screenshot", "start_screencast", "stop_screencast", "wait", "observe", "invoke_action", "set_value", "exec", "clipboard_get", "clipboard_set", "launch_app", "activate_window"], "description": "the typed action vocabulary. Reply shapes: most verbs answer with an `ack`; `screenshot`/`observe` answer with an `observation`; buffered `exec` answers with a `result` whose value is $defs.ExecResult (streamed exec: ack, then `exec_output`/`exec_exit` events); `clipboard_get` (a read) answers with a `result` whose value is `{text}` — v1 is text-only and size-capped runtime-side, binary clipboard formats are future work" },
"MutatingVerb": { "enum": ["click", "double_click", "right_click", "move", "drag", "mouse_down", "mouse_up", "scroll", "type_text", "key", "invoke_action", "set_value", "clipboard_set", "launch_app", "activate_window"], "description": "the verbs that mutate guest state and therefore admit the per-action `observe` argument — the coordinate tier, the element verbs, and the desktop verbs (clipboard_set/launch_app/activate_window); the reads (screenshot/screencast/observe/wait/clipboard_get) do not, and neither does `exec` (its effects are not a UI gesture — observe-after-exec is not a meaningful contract)" },
"PointerButton": { "enum": ["left", "middle", "right"], "description": "pointer button name; omitted = left" },
"ObserveSpec": {
"type": "object", "additionalProperties": false,
"description": "act-returns-observation parameters — the same capture levers as a one-shot screenshot. The follow-up observation carries cause = the action's call_id (after the ack; a binary-negotiated session receives it as a binary frame).",
"properties": {
"scope": { "$ref": "#/$defs/Scope" },
"format": { "$ref": "#/$defs/ImageFormat" },
"quality": { "type": "integer", "minimum": 1, "maximum": 100 },
"max_long_edge": { "type": "integer", "minimum": 1 }
}
},
"ActionBatch": { "type": "object", "additionalProperties": false, "required": ["actions"], "description": "SDK-side batching convention (#73), NOT a wire message: it is deliberately absent from the top-level oneOf. The SDK dispatches each action as an independent `action` frame and aggregates results; batch_id/stop_on_error never appear on the wire. This $def documents/validates that client-side batch shape only.", "properties": { "batch_id": { "type": "string" }, "stop_on_error": { "type": "boolean", "description": "halt the batch on the first failing action (default true)" }, "actions": { "type": "array", "minItems": 1, "items": { "$ref": "#/$defs/Action" }, "description": "ordered actions executed serially under a shared batch_id (#73)" } } },
"Scope": { "oneOf": [ { "enum": ["screen", "active_window"] }, { "type": "string", "pattern": "^window:(0x[0-9a-fA-F]+|[0-9]+)$" } ], "description": "capture region: screen, active_window, or window:<id> (decimal or 0x-hex)" },
"Target": {
"oneOf": [
{ "type": "object", "additionalProperties": false, "required": ["kind", "x", "y"], "properties": { "kind": { "const": "point_px" }, "x": { "type": "number" }, "y": { "type": "number" } } },
{ "type": "object", "additionalProperties": false, "required": ["kind", "x", "y"], "properties": { "kind": { "const": "point_norm" }, "x": { "type": "number", "minimum": 0, "maximum": 1 }, "y": { "type": "number", "minimum": 0, "maximum": 1 } } },
{ "type": "object", "additionalProperties": false, "required": ["kind", "ref"], "properties": { "kind": { "const": "element_ref" }, "ref": { "type": "string" }, "source": { "$ref": "#/$defs/ElementSource" } } }
]
},
"TargetKind": { "enum": ["point_px", "point_norm", "element_ref"] },
"Element": {
"type": "object", "additionalProperties": false,
"required": ["ref", "role", "bbox"],
"properties": {
"ref": { "type": "string", "description": "stable per-session element id (e<N>): monotonically minted, kept while the element lives, evicted (never reused) when it disappears; a stale ref on an action answers a machine-readable `stale_element_ref:` error meaning re-observe" },
"role": { "type": "string" },
"name": { "type": "string" },
"description": { "type": "string" },
"value": { "type": "string" },
"states": { "type": "array", "items": { "type": "string" } },
"actions": { "type": "array", "items": { "type": "string" }, "description": "AT-SPI action names invokable via the invoke_action verb" },
"focused": { "type": "boolean", "description": "whether this element holds keyboard focus (also surfaced as the observation's `focus` ref)" },
"bbox": { "type": "array", "items": { "type": "number" }, "minItems": 4, "maxItems": 4, "description": "[x, y, w, h] in display pixels" },
"source": { "$ref": "#/$defs/ElementSource" },
"provenance": { "type": "object", "description": "backend-specific node identity for re-resolution (e.g. CDP backendDOMNodeId/AX nodeId, AT-SPI path)" }
}
},
"ExecOutputMsg": {
"type": "object", "additionalProperties": false,
"required": ["type", "cause", "seq", "channel", "data_b64"],
"description": "one stdout/stderr chunk of a STREAMED exec (`exec` with stream=true), server-pushed on the same demux as screencast frames. This is the JSON-text form; a binary-negotiated session carries the chunk bytes raw instead, framed per $defs.BinaryExecOutputHeader",
"properties": {
"type": { "const": "exec_output" },
"cause": { "type": "string", "description": "the exec action's call_id" },
"seq": { "type": "integer", "minimum": 0, "description": "monotonic chunk index across BOTH channels of one exec — total output order is reconstructible" },
"channel": { "enum": ["stdout", "stderr"] },
"data_b64": { "type": "string", "description": "base64 of the raw chunk bytes (chunk size is runtime-capped). Bytes, not text: output need not be UTF-8" }
}
},
"ExecExitMsg": {
"type": "object", "additionalProperties": false,
"required": ["type", "cause", "exit_code", "timed_out", "duration_ms", "truncated"],
"description": "the terminal event of a STREAMED exec: exactly one per stream:true action, after the last exec_output. Always a JSON text frame",
"properties": {
"type": { "const": "exec_exit" },
"cause": { "type": "string", "description": "the exec action's call_id" },
"exit_code": { "type": ["integer", "null"], "description": "the child's exit code; null when it was killed by a signal (see `signal`) or never spawned (see `error`)" },
"signal": { "type": ["integer", "null"], "description": "the signal that killed the child, when one did (e.g. 9 after a timeout group-kill)" },
"timed_out": { "type": "boolean", "description": "true when the runtime killed the process group at the deadline — reported honestly, never disguised as an exit code" },
"duration_ms": { "type": "number", "minimum": 0, "description": "wall time from spawn to reap" },
"truncated": { "type": "boolean", "description": "true when the stream's total output budget was exhausted and later chunks were dropped (the child still ran to completion/timeout)" },
"error": { "type": "string", "description": "spawn/runtime failure (e.g. `exec_spawn_failed: No such file or directory`) — the run produced no process; exit_code is null" }
}
},
"ExecResult": {
"type": "object", "additionalProperties": false,
"required": ["exit_code", "timed_out", "stdout", "stderr", "stdout_truncated", "stderr_truncated", "duration_ms"],
"description": "the `value` of the `result` answering a BUFFERED exec (`exec` without stream). Documented as a $def like ActionBatch — `result.value` is schema-open, but the runtime emits and the SDK returns exactly this shape. stdout/stderr are UTF-8 with lossy replacement (use the streamed form + binary frames for byte-exact output), each capped at the runtime's per-channel budget with honest truncation flags",
"properties": {
"exit_code": { "type": ["integer", "null"], "description": "null when killed by a signal (timeout) — see `signal`/`timed_out`" },
"signal": { "type": ["integer", "null"] },
"timed_out": { "type": "boolean" },
"stdout": { "type": "string" },
"stderr": { "type": "string" },
"stdout_truncated": { "type": "boolean" },
"stderr_truncated": { "type": "boolean" },
"duration_ms": { "type": "number", "minimum": 0 }
}
},
"BinaryExecOutputHeader": {
"type": "object", "additionalProperties": false,
"required": ["type", "cause", "seq", "channel", "data"],
"description": "NOT a top-level text message (deliberately absent from the oneOf, like BinaryFrameHeader): the JSON header inside a binary WebSocket exec-output frame, sent only on a session that negotiated hello.accept.binary_frames. Same frame layout as media frames — `u32 LE header_len | this header | payload area` — with the chunk's raw bytes located by `data.off`/`data.len`; the `type` field is the kind discriminator a demux switches on (`observation` vs `exec_output`)",
"properties": {
"type": { "const": "exec_output" },
"cause": { "type": "string" },
"seq": { "type": "integer", "minimum": 0 },
"channel": { "enum": ["stdout", "stderr"] },
"data": {
"type": "object", "additionalProperties": false,
"required": ["off", "len"],
"properties": {
"off": { "type": "integer", "minimum": 0, "description": "payload byte offset of the chunk bytes" },
"len": { "type": "integer", "minimum": 0, "description": "chunk byte length" }
}
}
}
},
"ElementSource": { "enum": ["atspi", "uia", "ax", "cdp", "som"] },
"ImageFormat": { "enum": ["png", "jpeg"], "description": "wire image codec: png (lossless default) or jpeg (lossy bandwidth lever)" },
"CoordinateSpace": {
"type": "object", "additionalProperties": false,
"required": ["origin", "w", "h", "dpr", "source_rect", "delivered"],
"properties": {
"origin": { "const": "top-left", "description": "origin convention shared by global, source, and delivered spaces" },
"w": { "type": "integer", "minimum": 1, "description": "width of the full global point_px action space" },
"h": { "type": "integer", "minimum": 1, "description": "height of the full global point_px action space" },
"dpr": { "type": "number", "exclusiveMinimum": 0, "description": "device pixels per logical UI point; source_rect and actions remain expressed in capture/action pixels" },
"source_rect": { "$ref": "#/$defs/CoordinateRect", "description": "captured pre-downscale rectangle in global point_px coordinates" },
"delivered": { "$ref": "#/$defs/CoordinateSize", "description": "post-downscale image dimensions actually delivered to the client/model" }
}
},
"CoordinateRect": {
"type": "object", "additionalProperties": false,
"required": ["x", "y", "w", "h"],
"properties": {
"x": { "type": "integer", "description": "left edge in global point_px coordinates; may be negative for an off-screen window" },
"y": { "type": "integer", "description": "top edge in global point_px coordinates; may be negative for an off-screen window" },
"w": { "type": "integer", "minimum": 1 },
"h": { "type": "integer", "minimum": 1 }
}
},
"CoordinateSize": {
"type": "object", "additionalProperties": false,
"required": ["w", "h"],
"properties": {
"w": { "type": "integer", "minimum": 1 },
"h": { "type": "integer", "minimum": 1 }
}
},
"ObservationType": { "enum": ["a11y", "screenshot", "screencast", "video", "som"] },
"Platform": { "enum": ["linux", "windows", "macos"] }
}
}