@namzu/sdk 48.2.0 → 48.3.0
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/CHANGELOG.md +6 -0
- package/dist/tools/builtins/computer-use-coordinates.d.ts +13 -1
- package/dist/tools/builtins/computer-use-coordinates.d.ts.map +1 -1
- package/dist/tools/builtins/computer-use-coordinates.js +46 -0
- package/dist/tools/builtins/computer-use-coordinates.js.map +1 -1
- package/dist/tools/builtins/computer-use.d.ts +135 -0
- package/dist/tools/builtins/computer-use.d.ts.map +1 -1
- package/dist/tools/builtins/computer-use.js +255 -54
- package/dist/tools/builtins/computer-use.js.map +1 -1
- package/dist/types/computer-use/index.d.ts +56 -1
- package/dist/types/computer-use/index.d.ts.map +1 -1
- package/dist/types/computer-use/index.js.map +1 -1
- package/package.json +1 -1
- package/src/tools/builtins/computer-use-coordinates.ts +58 -1
- package/src/tools/builtins/computer-use.ts +336 -54
- package/src/types/computer-use/index.ts +65 -7
|
@@ -3,7 +3,7 @@ import { resolveProviderCapabilities } from '../../provider/capabilities.js';
|
|
|
3
3
|
import { sleep } from '../../utils/backoff.js';
|
|
4
4
|
import { defineTool } from '../defineTool.js';
|
|
5
5
|
import { neutralizeEnvelopeDelimiter, wrapUntrusted } from '../untrusted-envelope.js';
|
|
6
|
-
import { ScreenshotFrames, assumedDisplay, desktopRectOnImage, pointOnImage, toDisplayPoint, toDisplayRect, toImagePoint, } from './computer-use-coordinates.js';
|
|
6
|
+
import { ScreenshotFrames, assumedDisplay, desktopRectOnImage, pointOnImage, toDisplayPoint, toDisplayRect, toImagePoint, toWindowPoint, } from './computer-use-coordinates.js';
|
|
7
7
|
import { STANDARD_SCREENSHOT_LIMITS, cropAndFitPng, fitPng, pngSize, } from './computer-use-image.js';
|
|
8
8
|
export { HIGH_RES_SCREENSHOT_LIMITS, STANDARD_SCREENSHOT_LIMITS, screenshotTargetSize, } from './computer-use-image.js';
|
|
9
9
|
export const COMPUTER_USE_TOOL_NAME = 'computer_use';
|
|
@@ -50,21 +50,36 @@ const mouseClickSchema = z.object({
|
|
|
50
50
|
type: z.literal('mouse_click'),
|
|
51
51
|
at: pointSchema,
|
|
52
52
|
button: mouseButtonSchema,
|
|
53
|
+
/** Accepted only to return an explicit refusal; pointer foreground is unsafe. */
|
|
54
|
+
delivery_mode: z.enum(['background', 'foreground']).optional(),
|
|
53
55
|
});
|
|
54
56
|
const mouseDragSchema = z.object({
|
|
55
57
|
type: z.literal('mouse_drag'),
|
|
56
58
|
from: pointSchema,
|
|
57
59
|
to: pointSchema,
|
|
58
60
|
button: mouseButtonSchema,
|
|
61
|
+
delivery_mode: z.enum(['background', 'foreground']).optional(),
|
|
59
62
|
});
|
|
60
63
|
const scrollSchema = z.object({
|
|
61
64
|
type: z.literal('scroll'),
|
|
62
65
|
at: pointSchema,
|
|
63
66
|
direction: z.enum(['up', 'down', 'left', 'right']),
|
|
64
67
|
amount: z.number().int().positive(),
|
|
68
|
+
delivery_mode: z.enum(['background', 'foreground']).optional(),
|
|
69
|
+
});
|
|
70
|
+
const typeTextSchema = z.object({
|
|
71
|
+
type: z.literal('type_text'),
|
|
72
|
+
text: z.string(),
|
|
73
|
+
/** Accepted only to refuse an unsafe atomic foreground focus click. */
|
|
74
|
+
at: pointSchema.optional(),
|
|
75
|
+
delivery_mode: z.enum(['background', 'foreground']).optional(),
|
|
76
|
+
});
|
|
77
|
+
const keySchema = z.object({
|
|
78
|
+
type: z.literal('key'),
|
|
79
|
+
keys: z.string(),
|
|
80
|
+
at: pointSchema.optional(),
|
|
81
|
+
delivery_mode: z.enum(['background', 'foreground']).optional(),
|
|
65
82
|
});
|
|
66
|
-
const typeTextSchema = z.object({ type: z.literal('type_text'), text: z.string() });
|
|
67
|
-
const keySchema = z.object({ type: z.literal('key'), keys: z.string() });
|
|
68
83
|
const waitSchema = z.object({ type: z.literal('wait'), ms: z.number().int().nonnegative() });
|
|
69
84
|
const listWindowsSchema = z.object({ type: z.literal('list_windows') });
|
|
70
85
|
const focusWindowSchema = z.object({
|
|
@@ -107,7 +122,7 @@ const batchItemSchema = z.discriminatedUnion('type', [
|
|
|
107
122
|
]);
|
|
108
123
|
const withFrame = { screenshot_id: screenshotIdSchema };
|
|
109
124
|
const actionSchema = z.discriminatedUnion('type', [
|
|
110
|
-
z.object({ type: z.literal('screenshot') }),
|
|
125
|
+
z.object({ type: z.literal('screenshot'), window_id: z.string().min(1).optional() }),
|
|
111
126
|
z.object({ type: z.literal('zoom'), region: regionSchema, ...withFrame }),
|
|
112
127
|
cursorPositionSchema.extend(withFrame),
|
|
113
128
|
mouseMoveSchema.extend(withFrame),
|
|
@@ -240,8 +255,15 @@ function hostActionAvailable(caps, action) {
|
|
|
240
255
|
(action !== 'mouse_click' || caps.mouseClickButtons?.length !== 0) &&
|
|
241
256
|
(action !== 'mouse_drag' || caps.mouseDragButtons?.length !== 0));
|
|
242
257
|
}
|
|
258
|
+
function windowCaptureAvailable(host, caps) {
|
|
259
|
+
return (caps.windowCapture === true &&
|
|
260
|
+
typeof host.captureWindow === 'function' &&
|
|
261
|
+
typeof host.executeWindow === 'function');
|
|
262
|
+
}
|
|
243
263
|
function availableActions(host, caps) {
|
|
244
|
-
const
|
|
264
|
+
const displayScreenshot = hostActionAvailable(caps, 'screenshot');
|
|
265
|
+
const window = windowCaptureAvailable(host, caps);
|
|
266
|
+
const screenshot = displayScreenshot || window;
|
|
245
267
|
const windows = caps.windows === true &&
|
|
246
268
|
typeof host.listWindows === 'function' &&
|
|
247
269
|
typeof host.focusWindow === 'function';
|
|
@@ -250,9 +272,20 @@ function availableActions(host, caps) {
|
|
|
250
272
|
typeof host.uiAct === 'function';
|
|
251
273
|
const available = ALL_ACTIONS.filter((action) => {
|
|
252
274
|
switch (action) {
|
|
275
|
+
case 'screenshot':
|
|
276
|
+
return screenshot;
|
|
253
277
|
case 'zoom':
|
|
254
278
|
case 'wait':
|
|
255
279
|
return screenshot;
|
|
280
|
+
case 'mouse_click':
|
|
281
|
+
return hostActionAvailable(caps, action) || (window && caps.mouseClickButtons?.length !== 0);
|
|
282
|
+
case 'mouse_drag':
|
|
283
|
+
return hostActionAvailable(caps, action) || (window && caps.mouseDragButtons?.length !== 0);
|
|
284
|
+
case 'type_text':
|
|
285
|
+
case 'key':
|
|
286
|
+
return hostActionAvailable(caps, action) || window;
|
|
287
|
+
case 'scroll':
|
|
288
|
+
return hostActionAvailable(caps, action) || (window && caps.windowScroll !== false);
|
|
256
289
|
case 'list_windows':
|
|
257
290
|
case 'focus_window':
|
|
258
291
|
return windows;
|
|
@@ -269,19 +302,21 @@ function availableActions(host, caps) {
|
|
|
269
302
|
available.push('batch');
|
|
270
303
|
return available;
|
|
271
304
|
}
|
|
272
|
-
function unavailableHostActions(caps) {
|
|
305
|
+
function unavailableHostActions(host, caps) {
|
|
273
306
|
const unavailable = [];
|
|
307
|
+
const window = windowCaptureAvailable(host, caps);
|
|
274
308
|
if (!caps.screenshot)
|
|
275
|
-
unavailable.push('screenshot');
|
|
309
|
+
unavailable.push(window ? 'display screenshot' : 'screenshot');
|
|
276
310
|
if (!caps.cursorPosition)
|
|
277
311
|
unavailable.push('cursor_position');
|
|
278
312
|
if (!caps.mouse)
|
|
279
|
-
unavailable.push('mouse');
|
|
313
|
+
unavailable.push(window ? 'display mouse' : 'mouse');
|
|
280
314
|
if (!caps.keyboard)
|
|
281
|
-
unavailable.push('keyboard');
|
|
315
|
+
unavailable.push(window ? 'display keyboard' : 'keyboard');
|
|
282
316
|
if (caps.supportedActions) {
|
|
317
|
+
const available = new Set(availableActions(host, caps));
|
|
283
318
|
for (const action of HOST_ACTIONS) {
|
|
284
|
-
if (!
|
|
319
|
+
if (!available.has(action) && !unavailable.includes(action))
|
|
285
320
|
unavailable.push(action);
|
|
286
321
|
}
|
|
287
322
|
}
|
|
@@ -292,11 +327,15 @@ function unavailableHostActions(caps) {
|
|
|
292
327
|
// ---------------------------------------------------------------------------
|
|
293
328
|
function buildDescription(host, caps, settings) {
|
|
294
329
|
const available = availableActions(host, caps);
|
|
295
|
-
const unavailable = unavailableHostActions(caps);
|
|
296
|
-
const
|
|
297
|
-
`Controls the user's desktop on a ${caps.displayServer} host: screenshots, mouse and keyboard, for GUI tasks
|
|
298
|
-
|
|
299
|
-
|
|
330
|
+
const unavailable = unavailableHostActions(host, caps);
|
|
331
|
+
const intro = hostActionAvailable(caps, 'screenshot')
|
|
332
|
+
? `Controls the user's desktop on a ${caps.displayServer} host: screenshots, mouse and keyboard, for GUI tasks.`
|
|
333
|
+
: windowCaptureAvailable(host, caps)
|
|
334
|
+
? `Controls named windows on a ${caps.displayServer} host: window screenshots, mouse and keyboard, for GUI tasks.`
|
|
335
|
+
: available.length > 0
|
|
336
|
+
? `Offers the available computer controls on a ${caps.displayServer} host.`
|
|
337
|
+
: `Computer control is unavailable on this ${caps.displayServer} host; this tool reports the limitation.`;
|
|
338
|
+
const lines = [intro, `Available actions: ${available.join('; ') || 'none'}.`];
|
|
300
339
|
if (unavailable.length > 0) {
|
|
301
340
|
lines.push(caps.unavailableReason
|
|
302
341
|
? `Unavailable on this host: ${unavailable.join(', ')} — ${caps.unavailableReason} Do not retry; tell the user.`
|
|
@@ -304,7 +343,7 @@ function buildDescription(host, caps, settings) {
|
|
|
304
343
|
}
|
|
305
344
|
if (!available.includes('screenshot'))
|
|
306
345
|
return finish(lines, caps, available);
|
|
307
|
-
lines.push('Coordinates: every x/y you send is a pixel of the most recent screenshot this tool returned — origin at its top-left, x to the right, y down — never a screen pixel. Each screenshot states its id and size (for example "s3: 1456x819"); stay inside it.
|
|
346
|
+
lines.push('Coordinates: every x/y you send is a pixel of the most recent screenshot this tool returned — origin at its top-left, x to the right, y down — never a screen pixel. Each screenshot states its id and size (for example "s3: 1456x819"); stay inside it. Display screenshots map onto the display. To aim at an earlier display screenshot, pass its id as screenshot_id.', 'Take a screenshot before your first click.');
|
|
308
347
|
if (settings.screenshotAfterActions)
|
|
309
348
|
lines.push(`After any action that changes something, the tool waits ${settings.settleMs} ms and returns a new screenshot — do not call screenshot again after acting.`);
|
|
310
349
|
lines.push('zoom returns a closer, sharper view of region {x, y, width, height} of the screenshot; your coordinates still refer to the screenshot, not to the zoomed image.', `wait pauses for ms milliseconds (at most ${settings.maxWaitMs} per call, a batch's waits together) and then shows the screen.`);
|
|
@@ -316,9 +355,13 @@ function buildDescription(host, caps, settings) {
|
|
|
316
355
|
lines.push('To start a Windows program, a shell command (Start-Process notepad, or cmd.exe /c start calc) is surest when you have a shell tool. The Start menu searches by display names in the system language, and ENTER on a name it does not find opens a web search in the browser.');
|
|
317
356
|
if (available.includes('list_windows'))
|
|
318
357
|
lines.push('list_windows names the open windows with their ids; focus_window brings one to the front.');
|
|
358
|
+
if (windowCaptureAvailable(host, caps))
|
|
359
|
+
lines.push(`When the user names an application or window, ${available.includes('list_windows') ? 'use list_windows then screenshot {window_id}' : 'use screenshot {window_id} with a host-provided window id'} to capture pixels for that window. Its x/y are pixels of that window image; clicks, drags, typing and keys target that window. Window pointer input uses background delivery only; if a click or drag is refused, inspect a fresh image before any other action. ${caps.windowScroll === false ? `Window pixel scrolling is unavailable on this host; for a browser page use key PAGE_DOWN or ARROW_DOWN with delivery_mode:"foreground"${hostActionAvailable(caps, 'screenshot') ? ', or deliberately take a display screenshot for display scrolling' : ''}. ` : ''}To type into a browser field, first mouse_click its point in the window image, then type_text with delivery_mode:"foreground" if background text is unavailable. This may briefly bring the verified window to the front. For browser shortcuts such as CTRL+L, use key with delivery_mode:"foreground". A stale or moved window is refused; take a fresh window screenshot. ${hostActionAvailable(caps, 'screenshot') ? 'Plain screenshot still shows the display. ' : 'screenshot requires window_id on this host. '}Browser permission bubbles outside the window may be absent. If the window image is blank or shows another window, do not click it; focus the target and capture it again.`);
|
|
319
360
|
if (available.includes('ui_snapshot'))
|
|
320
361
|
lines.push(`ui_snapshot {window_id} reads a window's controls (its accessibility tree) as text, each control you can act on with a ref such as e12; without window_id it reads the window in front. ui_act {ref, action, value} acts on one control: invoke (press a button, open a menu item), set_value (replace a field's text with value), toggle, select, expand, collapse. Prefer ui_act to clicking pixels when the control is in the tree: it does not depend on coordinates or on which window is in front, and a batch of ui_act steps (for example pressing several buttons) runs in one call. Refs are valid only until the next ui_snapshot; take a new one after the window changes.`);
|
|
321
|
-
lines.push(
|
|
362
|
+
lines.push(windowCaptureAvailable(host, caps)
|
|
363
|
+
? 'After a display screenshot, typing and keys go to whichever window has focus: confirm on that screenshot that the right window is in front before type_text or key. After a named-window screenshot, input targets the captured window.'
|
|
364
|
+
: 'Typing and keys go to whichever window has focus: confirm on a screenshot that the right window is in front before type_text or key.');
|
|
322
365
|
if (available.includes('list_windows'))
|
|
323
366
|
lines.push('type_text and key are refused while a terminal window is in front — usually the one running this agent, or the user’s own; bring the window you mean to the front first (focus_window, or click it).');
|
|
324
367
|
return finish(lines, caps, available);
|
|
@@ -360,8 +403,8 @@ const ACTION_REQUIREMENTS = {
|
|
|
360
403
|
mouse_click: 'mouse_click needs at (button defaults to left)',
|
|
361
404
|
mouse_drag: 'mouse_drag needs from and to (button defaults to left)',
|
|
362
405
|
scroll: 'scroll needs at, direction, and amount',
|
|
363
|
-
type_text: 'type_text needs text',
|
|
364
|
-
key: 'key needs keys',
|
|
406
|
+
type_text: 'type_text needs text; delivery_mode is optional on a window screenshot',
|
|
407
|
+
key: 'key needs keys; delivery_mode is optional on a window screenshot',
|
|
365
408
|
wait: 'wait needs ms',
|
|
366
409
|
list_windows: 'list_windows needs no other fields',
|
|
367
410
|
focus_window: 'focus_window needs window_id',
|
|
@@ -369,6 +412,11 @@ const ACTION_REQUIREMENTS = {
|
|
|
369
412
|
ui_act: 'ui_act needs ref and action, and value for set_value',
|
|
370
413
|
batch: 'batch needs actions',
|
|
371
414
|
};
|
|
415
|
+
function actionRequirement(action, host, caps) {
|
|
416
|
+
return action === 'screenshot' && windowCaptureAvailable(host, caps)
|
|
417
|
+
? `screenshot ${hostActionAvailable(caps, 'screenshot') ? 'takes an optional' : 'requires'} window_id to capture pixels for that window`
|
|
418
|
+
: ACTION_REQUIREMENTS[action];
|
|
419
|
+
}
|
|
372
420
|
function hostModelSchema(host, caps, settings) {
|
|
373
421
|
// An unavailable host remains a diagnostic tool. Avoid invalid empty enums
|
|
374
422
|
// on provider wires; every execution is still refused before host access.
|
|
@@ -401,13 +449,28 @@ function hostModelSchema(host, caps, settings) {
|
|
|
401
449
|
type: 'string',
|
|
402
450
|
description: 'Key or key chord to press, for example ENTER or CTRL+R.',
|
|
403
451
|
},
|
|
452
|
+
...(windowCaptureAvailable(host, caps)
|
|
453
|
+
? {
|
|
454
|
+
delivery_mode: {
|
|
455
|
+
type: 'string',
|
|
456
|
+
enum: ['background', 'foreground'],
|
|
457
|
+
description: 'For type_text or key on a window screenshot only. Foreground may briefly bring that verified window forward when background input is unavailable. Background is the default.',
|
|
458
|
+
},
|
|
459
|
+
}
|
|
460
|
+
: {}),
|
|
404
461
|
ms: {
|
|
405
462
|
type: 'integer',
|
|
406
463
|
description: `Milliseconds to wait, 0 to ${settings.maxWaitMs}.`,
|
|
407
464
|
},
|
|
408
465
|
};
|
|
409
|
-
if (present.includes('focus_window') ||
|
|
410
|
-
|
|
466
|
+
if (present.includes('focus_window') ||
|
|
467
|
+
(!forItems &&
|
|
468
|
+
(present.includes('ui_snapshot') ||
|
|
469
|
+
(present.includes('screenshot') && windowCaptureAvailable(host, caps)))))
|
|
470
|
+
fields.window_id = {
|
|
471
|
+
type: 'string',
|
|
472
|
+
description: 'A window id from list_windows or supplied by the host.',
|
|
473
|
+
};
|
|
411
474
|
if (present.includes('ui_act')) {
|
|
412
475
|
fields.ref = {
|
|
413
476
|
type: 'string',
|
|
@@ -439,7 +502,7 @@ function hostModelSchema(host, caps, settings) {
|
|
|
439
502
|
type: {
|
|
440
503
|
type: 'string',
|
|
441
504
|
enum: actions,
|
|
442
|
-
description: `Desktop action. ${actions.map((action) =>
|
|
505
|
+
description: `Desktop action. ${actions.map((action) => actionRequirement(action, host, caps)).join('; ')}.`,
|
|
443
506
|
},
|
|
444
507
|
...fieldSchemas(false),
|
|
445
508
|
};
|
|
@@ -480,6 +543,9 @@ function hostModelSchema(host, caps, settings) {
|
|
|
480
543
|
function pointLabel(point) {
|
|
481
544
|
return `(${point.x}, ${point.y})`;
|
|
482
545
|
}
|
|
546
|
+
function deliveryLabel(mode) {
|
|
547
|
+
return mode === 'foreground' ? ' in foreground' : '';
|
|
548
|
+
}
|
|
483
549
|
function quotedText(value) {
|
|
484
550
|
const oneLine = value.replace(/\s+/g, ' ');
|
|
485
551
|
const visible = oneLine.length > 64 ? `${oneLine.slice(0, 63)}…` : oneLine;
|
|
@@ -517,7 +583,7 @@ function uiActLabel(input, describe) {
|
|
|
517
583
|
function itemLabel(input, describe) {
|
|
518
584
|
switch (input.type) {
|
|
519
585
|
case 'screenshot':
|
|
520
|
-
return 'Capture screenshot';
|
|
586
|
+
return input.window_id ? `Capture window ${input.window_id}` : 'Capture screenshot';
|
|
521
587
|
case 'zoom':
|
|
522
588
|
return `Zoom into ${input.region.width}x${input.region.height} at ${pointLabel(input.region)}`;
|
|
523
589
|
case 'cursor_position':
|
|
@@ -531,9 +597,9 @@ function itemLabel(input, describe) {
|
|
|
531
597
|
case 'scroll':
|
|
532
598
|
return `Scroll ${input.direction} ${input.amount} at ${pointLabel(input.at)}`;
|
|
533
599
|
case 'type_text':
|
|
534
|
-
return `Type ${quotedText(input.text)}`;
|
|
600
|
+
return `Type ${quotedText(input.text)}${deliveryLabel(input.delivery_mode)}`;
|
|
535
601
|
case 'key':
|
|
536
|
-
return `Press ${input.keys}`;
|
|
602
|
+
return `Press ${input.keys}${deliveryLabel(input.delivery_mode)}`;
|
|
537
603
|
case 'wait':
|
|
538
604
|
return waitLabel(input.ms);
|
|
539
605
|
case 'list_windows':
|
|
@@ -595,6 +661,10 @@ function isOutcomeUnknown(value, action) {
|
|
|
595
661
|
function errorText(error) {
|
|
596
662
|
return error instanceof Error ? error.message : String(error);
|
|
597
663
|
}
|
|
664
|
+
/** Driver IDs are normally short opaque tokens; never put arbitrary host text in tool instructions. */
|
|
665
|
+
function visibleWindowId(id) {
|
|
666
|
+
return /^[A-Za-z0-9_.:-]{1,64}$/.test(id) ? id : '[window id]';
|
|
667
|
+
}
|
|
598
668
|
class StepFailure extends Error {
|
|
599
669
|
unknown;
|
|
600
670
|
constructor(message, unknown) {
|
|
@@ -641,7 +711,7 @@ function uiElementLine(element, ref, frame) {
|
|
|
641
711
|
const actions = (element.actions ?? []).filter((action) => UI_ACTIONS.includes(action));
|
|
642
712
|
if (actions.length > 0)
|
|
643
713
|
parts.push(`[${actions.join(', ')}]`);
|
|
644
|
-
const at = frame && element.bounds ? centreOnImage(frame, element.bounds) : undefined;
|
|
714
|
+
const at = frame && !frame.window && element.bounds ? centreOnImage(frame, element.bounds) : undefined;
|
|
645
715
|
if (at)
|
|
646
716
|
parts.push(`@${pointLabel(at)}`);
|
|
647
717
|
}
|
|
@@ -691,6 +761,7 @@ export function createComputerUseTool(host, options = {}) {
|
|
|
691
761
|
windows: false,
|
|
692
762
|
regionCapture: false,
|
|
693
763
|
uiTree: false,
|
|
764
|
+
windowCapture: false,
|
|
694
765
|
unavailableReason: options.unavailableReason,
|
|
695
766
|
}
|
|
696
767
|
: host.capabilities;
|
|
@@ -702,12 +773,16 @@ export function createComputerUseTool(host, options = {}) {
|
|
|
702
773
|
let uiRefs = new Map();
|
|
703
774
|
let uiSnapshots = 0;
|
|
704
775
|
let uiRefCount = 0;
|
|
776
|
+
let uiSnapshotWindowId;
|
|
777
|
+
let uiSnapshotFrame;
|
|
705
778
|
const describeUiRef = (ref) => {
|
|
706
779
|
const entry = uiRefs.get(ref);
|
|
707
780
|
return entry ? `${uiElementText(entry.element)} (${ref})` : undefined;
|
|
708
781
|
};
|
|
709
782
|
const label = (input) => itemLabel(input, describeUiRef);
|
|
710
783
|
const refusal = (type) => {
|
|
784
|
+
if (available.has(type))
|
|
785
|
+
return null;
|
|
711
786
|
const required = requiredCapability(type);
|
|
712
787
|
const why = caps.unavailableReason
|
|
713
788
|
? ` ${caps.unavailableReason} Do not retry; tell the user.`
|
|
@@ -718,8 +793,19 @@ export function createComputerUseTool(host, options = {}) {
|
|
|
718
793
|
return `computer_use: action "${type}" is not supported on this host.${why}`;
|
|
719
794
|
return null;
|
|
720
795
|
};
|
|
721
|
-
/** Capture the display, fit it, and number it. */
|
|
722
|
-
const capture = async () => {
|
|
796
|
+
/** Capture the display or one explicitly named window, fit it, and number it. */
|
|
797
|
+
const capture = async (windowId) => {
|
|
798
|
+
if (windowId !== undefined) {
|
|
799
|
+
if (!windowCaptureAvailable(host, caps))
|
|
800
|
+
throw new StepFailure('this host cannot capture a named window; no desktop screenshot was taken');
|
|
801
|
+
const shot = await host.captureWindow(windowId);
|
|
802
|
+
const image = await fitPng(shot.data, settings.limits);
|
|
803
|
+
if (image.sourceWidth !== shot.width || image.sourceHeight !== shot.height)
|
|
804
|
+
throw new StepFailure('the window PNG dimensions changed during capture; take a new screenshot');
|
|
805
|
+
return { frame: frames.recordWindow(image, shot), image };
|
|
806
|
+
}
|
|
807
|
+
if (!hostActionAvailable(caps, 'screenshot'))
|
|
808
|
+
throw new StepFailure('this host requires window_id for screenshots; no display screenshot was taken');
|
|
723
809
|
const result = await host.execute({ type: 'screenshot' });
|
|
724
810
|
if (result.type !== 'screenshot')
|
|
725
811
|
throw new Error(`computer_use: the host answered a screenshot with "${result.type}"`);
|
|
@@ -733,6 +819,8 @@ export function createComputerUseTool(host, options = {}) {
|
|
|
733
819
|
const frame = frames.get(id);
|
|
734
820
|
if (!frame)
|
|
735
821
|
throw new StepFailure(`there is no screenshot ${id} (the latest is ${frames.latest()?.id ?? 'none'}); take a screenshot and use its coordinates`);
|
|
822
|
+
if (frame.window && frames.latest() !== frame)
|
|
823
|
+
throw new StepFailure(`window screenshot ${id} is stale; take a new window screenshot before acting`);
|
|
736
824
|
return frame;
|
|
737
825
|
}
|
|
738
826
|
const latest = frames.latest();
|
|
@@ -752,20 +840,32 @@ export function createComputerUseTool(host, options = {}) {
|
|
|
752
840
|
const mapPoint = (frame, point, field) => {
|
|
753
841
|
if (!pointOnImage(frame, point))
|
|
754
842
|
throw new StepFailure(`${field} ${pointLabel(point)} is outside screenshot ${frame.id}, which is ${frame.imageWidth}x${frame.imageHeight} (x 0–${frame.imageWidth - 1}, y 0–${frame.imageHeight - 1}); coordinates are pixels of the screenshot, not of the screen`);
|
|
755
|
-
return toDisplayPoint(frame, point);
|
|
843
|
+
return frame.window ? toWindowPoint(frame, point) : toDisplayPoint(frame, point);
|
|
756
844
|
};
|
|
757
|
-
const runHost = async (action) => {
|
|
845
|
+
const runHost = async (action, frame) => {
|
|
758
846
|
try {
|
|
847
|
+
if (frame?.window) {
|
|
848
|
+
if (action.type === 'screenshot' ||
|
|
849
|
+
action.type === 'cursor_position' ||
|
|
850
|
+
action.type === 'mouse_move')
|
|
851
|
+
throw new StepFailure(`${action.type} has no window-scoped input path; use a display screenshot`);
|
|
852
|
+
if (frames.latest() !== frame)
|
|
853
|
+
throw new StepFailure(`window screenshot ${frame.id} is stale; capture the window again`);
|
|
854
|
+
await host.executeWindow(frame.window.captureId, action);
|
|
855
|
+
return 'done';
|
|
856
|
+
}
|
|
759
857
|
const result = await host.execute(action);
|
|
760
858
|
if (result.type === 'cursor_position') {
|
|
761
|
-
const
|
|
762
|
-
if (!
|
|
859
|
+
const selected = frame ?? frames.latest();
|
|
860
|
+
if (!selected)
|
|
763
861
|
return `at display pixel ${pointLabel(result.point)}`;
|
|
764
|
-
return `at ${pointLabel(toImagePoint(
|
|
862
|
+
return `at ${pointLabel(toImagePoint(selected, result.point))} in ${selected.id}`;
|
|
765
863
|
}
|
|
766
864
|
return 'done';
|
|
767
865
|
}
|
|
768
866
|
catch (error) {
|
|
867
|
+
if (error instanceof StepFailure)
|
|
868
|
+
throw error;
|
|
769
869
|
if (isOutcomeUnknown(error, action.type))
|
|
770
870
|
throw new StepFailure(error.message, error);
|
|
771
871
|
throw new StepFailure(errorText(error));
|
|
@@ -783,43 +883,82 @@ export function createComputerUseTool(host, options = {}) {
|
|
|
783
883
|
run,
|
|
784
884
|
});
|
|
785
885
|
switch (item.type) {
|
|
786
|
-
case 'cursor_position':
|
|
787
|
-
frameFor(frameId);
|
|
788
|
-
|
|
886
|
+
case 'cursor_position': {
|
|
887
|
+
const frame = frameFor(frameId);
|
|
888
|
+
if (frame.window)
|
|
889
|
+
throw new StepFailure('cursor_position is display-relative; take a display screenshot first');
|
|
890
|
+
return step(() => runHost({ type: 'cursor_position' }, frame));
|
|
891
|
+
}
|
|
789
892
|
case 'mouse_move': {
|
|
790
893
|
const frame = frameFor(frameId);
|
|
894
|
+
if (frame.window)
|
|
895
|
+
throw new StepFailure('mouse_move cannot move the OS pointer in window scope; use a click or a display screenshot');
|
|
791
896
|
const to = mapPoint(frame, item.to, 'to');
|
|
792
897
|
return step(() => runHost({ type: 'mouse_move', to }));
|
|
793
898
|
}
|
|
794
899
|
case 'mouse_click': {
|
|
900
|
+
if (item.delivery_mode)
|
|
901
|
+
throw new StepFailure('window pointer delivery_mode is unavailable; foreground input cannot verify which window is under the cursor');
|
|
795
902
|
const buttons = caps.mouseClickButtons;
|
|
796
903
|
if (buttons && !buttons.includes(item.button))
|
|
797
904
|
throw new StepFailure(`computer_use: action "mouse_click" does not support button "${item.button}" on this host.`);
|
|
798
905
|
const frame = frameFor(frameId);
|
|
799
906
|
const at = mapPoint(frame, item.at, 'at');
|
|
800
|
-
return step(() => runHost({ type: 'mouse_click', at, button: item.button }));
|
|
907
|
+
return step(() => runHost({ type: 'mouse_click', at, button: item.button }, frame));
|
|
801
908
|
}
|
|
802
909
|
case 'mouse_drag': {
|
|
910
|
+
if (item.delivery_mode)
|
|
911
|
+
throw new StepFailure('window pointer delivery_mode is unavailable; foreground input cannot verify which window is under the cursor');
|
|
803
912
|
const buttons = caps.mouseDragButtons;
|
|
804
913
|
if (buttons && !buttons.includes(item.button))
|
|
805
914
|
throw new StepFailure(`computer_use: action "mouse_drag" does not support button "${item.button}" on this host.`);
|
|
806
915
|
const frame = frameFor(frameId);
|
|
807
916
|
const from = mapPoint(frame, item.from, 'from');
|
|
808
917
|
const to = mapPoint(frame, item.to, 'to');
|
|
809
|
-
return step(() => runHost({ type: 'mouse_drag', from, to, button: item.button }));
|
|
918
|
+
return step(() => runHost({ type: 'mouse_drag', from, to, button: item.button }, frame));
|
|
810
919
|
}
|
|
811
920
|
case 'scroll': {
|
|
812
921
|
const frame = frameFor(frameId);
|
|
922
|
+
if (item.delivery_mode)
|
|
923
|
+
throw new StepFailure('window pointer delivery_mode is unavailable; foreground scrolling cannot verify which window receives the wheel');
|
|
924
|
+
if (frame.window && caps.windowScroll === false)
|
|
925
|
+
throw new StepFailure('window pixel scrolling is unavailable on this host; use foreground PAGE_DOWN or ARROW_DOWN in this window, or take a deliberate display screenshot before display scrolling');
|
|
813
926
|
const at = mapPoint(frame, item.at, 'at');
|
|
814
|
-
return step(() => runHost({ type: 'scroll', at, direction: item.direction, amount: item.amount }));
|
|
927
|
+
return step(() => runHost({ type: 'scroll', at, direction: item.direction, amount: item.amount }, frame));
|
|
815
928
|
}
|
|
816
|
-
case 'type_text':
|
|
929
|
+
case 'type_text': {
|
|
930
|
+
if (item.at)
|
|
931
|
+
throw new StepFailure('type_text at is unavailable: a foreground focus click cannot verify which window is under the point; click the field first, then type');
|
|
817
932
|
requireLook();
|
|
818
|
-
|
|
819
|
-
|
|
933
|
+
const frame = frameFor(frameId);
|
|
934
|
+
if (item.delivery_mode && !frame.window)
|
|
935
|
+
throw new StepFailure('type_text delivery_mode requires a window screenshot');
|
|
936
|
+
if (frame.window && TERMINAL_APPS.test(frame.window.window.app))
|
|
937
|
+
throw new StepFailure('computer_use never types into a terminal window');
|
|
938
|
+
return step(() => runHost({
|
|
939
|
+
type: 'type_text',
|
|
940
|
+
text: item.text,
|
|
941
|
+
...(item.delivery_mode ? { delivery_mode: item.delivery_mode } : {}),
|
|
942
|
+
}, frame));
|
|
943
|
+
}
|
|
944
|
+
case 'key': {
|
|
945
|
+
if (item.at)
|
|
946
|
+
throw new StepFailure('key at is unavailable: a foreground focus click cannot verify which window is under the point; click the field first, then send the key');
|
|
820
947
|
requireLook();
|
|
821
|
-
|
|
948
|
+
const frame = frameFor(frameId);
|
|
949
|
+
if (item.delivery_mode && !frame.window)
|
|
950
|
+
throw new StepFailure('key delivery_mode requires a window screenshot; take one with screenshot {window_id}');
|
|
951
|
+
if (frame.window && TERMINAL_APPS.test(frame.window.window.app))
|
|
952
|
+
throw new StepFailure('computer_use never sends keys to a terminal window');
|
|
953
|
+
return step(() => runHost({
|
|
954
|
+
type: 'key',
|
|
955
|
+
keys: item.keys,
|
|
956
|
+
...(item.delivery_mode ? { delivery_mode: item.delivery_mode } : {}),
|
|
957
|
+
}, frame));
|
|
958
|
+
}
|
|
822
959
|
case 'wait':
|
|
960
|
+
if (!hostActionAvailable(caps, 'screenshot') && !frames.latest() && !uiSnapshotWindowId)
|
|
961
|
+
throw new StepFailure('this host has no display screenshot; capture or inspect a named window before waiting');
|
|
823
962
|
return step(async (signal) => {
|
|
824
963
|
await sleep(item.ms, signal);
|
|
825
964
|
return 'done';
|
|
@@ -877,7 +1016,8 @@ export function createComputerUseTool(host, options = {}) {
|
|
|
877
1016
|
uiSnapshots += 1;
|
|
878
1017
|
const id = `u${uiSnapshots}`;
|
|
879
1018
|
const refs = new Map();
|
|
880
|
-
const
|
|
1019
|
+
const latest = frames.latest();
|
|
1020
|
+
const frame = latest?.window ? undefined : latest;
|
|
881
1021
|
const lines = [];
|
|
882
1022
|
let chars = 0;
|
|
883
1023
|
let shown = 0;
|
|
@@ -911,10 +1051,12 @@ export function createComputerUseTool(host, options = {}) {
|
|
|
911
1051
|
};
|
|
912
1052
|
visit(snapshot.root, 0);
|
|
913
1053
|
uiRefs = refs;
|
|
1054
|
+
uiSnapshotWindowId = snapshot.windowId ?? input.window_id;
|
|
1055
|
+
uiSnapshotFrame = frames.latest();
|
|
914
1056
|
const window = snapshot.windowId && /^[\w.:-]{1,64}$/.test(snapshot.windowId) ? snapshot.windowId : undefined;
|
|
915
1057
|
const header = [
|
|
916
1058
|
`UI snapshot ${id}${window ? ` of window ${window}` : ''}: ${shown} controls shown, ${refs.size} with a ref you can pass to ui_act. Refs are valid until the next ui_snapshot.`,
|
|
917
|
-
...(frame
|
|
1059
|
+
...(frame && !frame.window
|
|
918
1060
|
? [
|
|
919
1061
|
`@(x, y) is a control's centre on screenshot ${frame.id}, for a click when ui_act cannot reach it.`,
|
|
920
1062
|
]
|
|
@@ -993,7 +1135,8 @@ export function createComputerUseTool(host, options = {}) {
|
|
|
993
1135
|
}
|
|
994
1136
|
if (windows.length === 0)
|
|
995
1137
|
return 'no windows are open';
|
|
996
|
-
const
|
|
1138
|
+
const latest = frames.latest();
|
|
1139
|
+
const frame = latest?.window ? undefined : latest;
|
|
997
1140
|
const lines = windows.slice(0, MAX_LISTED_WINDOWS).map((window) => {
|
|
998
1141
|
const where = frame
|
|
999
1142
|
? (() => {
|
|
@@ -1026,6 +1169,16 @@ export function createComputerUseTool(host, options = {}) {
|
|
|
1026
1169
|
}, [...lines, ...more].join('\n'))}`;
|
|
1027
1170
|
};
|
|
1028
1171
|
const describeFrame = (frame) => {
|
|
1172
|
+
if (frame.window) {
|
|
1173
|
+
const { window } = frame.window;
|
|
1174
|
+
const focusWarning = window.focused
|
|
1175
|
+
? ''
|
|
1176
|
+
: ' This window was not first in the host window list when captured. The driver may have used a screen-region fallback containing another window; focus this window and recapture before pixel actions if the image looks covered.';
|
|
1177
|
+
const coverage = frame.window.captureCoverage === 'not_observable_in_window_scope'
|
|
1178
|
+
? ' Browser permission bubbles outside the window may be absent.'
|
|
1179
|
+
: '';
|
|
1180
|
+
return `Screenshot ${frame.id}: ${frame.imageWidth}x${frame.imageHeight} pixels captured for window ${visibleWindowId(window.id)}. Send coordinates in this image's pixels (x 0–${frame.imageWidth - 1}, y 0–${frame.imageHeight - 1}); actions target this window. Take a new window screenshot if it moves or changes size. If the image is blank or shows another window, do not click it; focus the target and capture it again.${focusWarning}${coverage}`;
|
|
1181
|
+
}
|
|
1029
1182
|
const { display } = frame;
|
|
1030
1183
|
const scaled = frame.imageWidth !== display.width || frame.imageHeight !== display.height;
|
|
1031
1184
|
return scaled
|
|
@@ -1036,14 +1189,23 @@ export function createComputerUseTool(host, options = {}) {
|
|
|
1036
1189
|
id: frame.id,
|
|
1037
1190
|
width: frame.imageWidth,
|
|
1038
1191
|
height: frame.imageHeight,
|
|
1039
|
-
|
|
1192
|
+
...(frame.window
|
|
1193
|
+
? {
|
|
1194
|
+
window: {
|
|
1195
|
+
id: visibleWindowId(frame.window.window.id),
|
|
1196
|
+
pid: frame.window.window.pid,
|
|
1197
|
+
bounds: frame.window.window.bounds,
|
|
1198
|
+
},
|
|
1199
|
+
captureCoverage: frame.window.captureCoverage,
|
|
1200
|
+
}
|
|
1201
|
+
: { display: frame.display }),
|
|
1040
1202
|
mimeType: 'image/png',
|
|
1041
1203
|
encoding: 'base64',
|
|
1042
1204
|
});
|
|
1043
1205
|
const pin = (frame) => [
|
|
1044
1206
|
{
|
|
1045
1207
|
key: 'computer_use.screenshot',
|
|
1046
|
-
text: `computer_use coordinates are pixels of screenshot ${frame.id} (${frame.imageWidth}x${frame.imageHeight}), origin top-left.`,
|
|
1208
|
+
text: `computer_use coordinates are pixels of screenshot ${frame.id} (${frame.imageWidth}x${frame.imageHeight}), origin top-left${frame.window ? `, bound to window ${visibleWindowId(frame.window.window.id)}` : ''}.`,
|
|
1047
1209
|
},
|
|
1048
1210
|
];
|
|
1049
1211
|
const imageBlock = (image) => ({
|
|
@@ -1061,7 +1223,9 @@ export function createComputerUseTool(host, options = {}) {
|
|
|
1061
1223
|
: [];
|
|
1062
1224
|
const screenshotResult = (shot) => ({
|
|
1063
1225
|
success: true,
|
|
1064
|
-
output:
|
|
1226
|
+
output: shot.frame.window
|
|
1227
|
+
? `Screenshot ${shot.frame.id} captured (${shot.frame.imageWidth}x${shot.frame.imageHeight} of window ${visibleWindowId(shot.frame.window.window.id)}).`
|
|
1228
|
+
: `Screenshot ${shot.frame.id} captured (${shot.frame.imageWidth}x${shot.frame.imageHeight} of the ${shot.frame.display.width}x${shot.frame.display.height} display).`,
|
|
1065
1229
|
content: [
|
|
1066
1230
|
{ type: 'text', text: [describeFrame(shot.frame), ...firstLookHint(shot.frame)].join('\n') },
|
|
1067
1231
|
imageBlock(shot.image),
|
|
@@ -1082,7 +1246,12 @@ export function createComputerUseTool(host, options = {}) {
|
|
|
1082
1246
|
throw new StepFailure(`region ${region.width}x${region.height} at ${pointLabel(region)} is outside screenshot ${frame.id} (${frame.imageWidth}x${frame.imageHeight})`);
|
|
1083
1247
|
const rect = toDisplayRect(frame, onImage);
|
|
1084
1248
|
let fitted;
|
|
1085
|
-
if (
|
|
1249
|
+
if (frame.window) {
|
|
1250
|
+
if (!frame.window.data)
|
|
1251
|
+
throw new StepFailure(`window screenshot ${frame.id} is stale; capture the window again`);
|
|
1252
|
+
fitted = await cropAndFitPng(frame.window.data, rect, settings.limits);
|
|
1253
|
+
}
|
|
1254
|
+
else if (caps.regionCapture === true && typeof host.captureRegion === 'function') {
|
|
1086
1255
|
const piece = await host.captureRegion(rect);
|
|
1087
1256
|
fitted = await fitPng(piece.data, settings.limits);
|
|
1088
1257
|
}
|
|
@@ -1146,6 +1315,20 @@ export function createComputerUseTool(host, options = {}) {
|
|
|
1146
1315
|
// the screenshot in step 5 should not arrive after steps 1–4 changed
|
|
1147
1316
|
// the desktop.
|
|
1148
1317
|
const planned = [];
|
|
1318
|
+
const actionFrame = frameId ? frames.get(frameId) : frames.latest();
|
|
1319
|
+
const explicitDisplayFrame = frameId !== undefined && actionFrame !== undefined && !actionFrame.window;
|
|
1320
|
+
if (actionFrame?.window &&
|
|
1321
|
+
items.some((item, index) => item.type === 'focus_window' &&
|
|
1322
|
+
items
|
|
1323
|
+
.slice(index + 1)
|
|
1324
|
+
.some((later) => ['mouse_click', 'mouse_drag', 'scroll', 'type_text', 'key'].includes(later.type))))
|
|
1325
|
+
return {
|
|
1326
|
+
success: false,
|
|
1327
|
+
output: '',
|
|
1328
|
+
error: 'computer_use: a window-scoped batch cannot act after focus_window changes its target; focus first, then take a new window screenshot. Nothing was run.',
|
|
1329
|
+
};
|
|
1330
|
+
let postWindowId = actionFrame?.window?.window.id ??
|
|
1331
|
+
(!hostActionAvailable(caps, 'screenshot') ? uiSnapshotWindowId : undefined);
|
|
1149
1332
|
for (const [index, item] of items.entries()) {
|
|
1150
1333
|
try {
|
|
1151
1334
|
planned.push(plan(item, frameId));
|
|
@@ -1176,13 +1359,30 @@ export function createComputerUseTool(host, options = {}) {
|
|
|
1176
1359
|
}
|
|
1177
1360
|
try {
|
|
1178
1361
|
const keyboard = step.item.type === 'type_text' || step.item.type === 'key';
|
|
1179
|
-
if (keyboard && !frontChecked) {
|
|
1362
|
+
if (keyboard && !actionFrame?.window && !frontChecked) {
|
|
1180
1363
|
await refuseTerminalInFront();
|
|
1181
1364
|
frontChecked = true;
|
|
1182
1365
|
}
|
|
1183
1366
|
if (step.item.type !== 'type_text')
|
|
1184
1367
|
frontChecked = false;
|
|
1185
1368
|
const note = await step.run(signal);
|
|
1369
|
+
if (step.item.type === 'focus_window' && windowCaptureAvailable(host, caps))
|
|
1370
|
+
postWindowId = step.item.window_id;
|
|
1371
|
+
else if (step.item.type === 'ui_act' &&
|
|
1372
|
+
uiSnapshotWindowId &&
|
|
1373
|
+
!explicitDisplayFrame &&
|
|
1374
|
+
windowCaptureAvailable(host, caps))
|
|
1375
|
+
postWindowId = uiSnapshotWindowId;
|
|
1376
|
+
else if (step.item.type === 'wait' &&
|
|
1377
|
+
!changed &&
|
|
1378
|
+
uiSnapshotWindowId &&
|
|
1379
|
+
uiSnapshotFrame === frames.latest() &&
|
|
1380
|
+
frameId === undefined &&
|
|
1381
|
+
windowCaptureAvailable(host, caps))
|
|
1382
|
+
postWindowId = uiSnapshotWindowId;
|
|
1383
|
+
else if (actionFrame?.window &&
|
|
1384
|
+
['mouse_click', 'mouse_drag', 'scroll', 'type_text', 'key'].includes(step.item.type))
|
|
1385
|
+
postWindowId = actionFrame.window.window.id;
|
|
1186
1386
|
records.push({ label: step.label, status: 'done', note });
|
|
1187
1387
|
if (step.mutating)
|
|
1188
1388
|
changed = true;
|
|
@@ -1208,6 +1408,7 @@ export function createComputerUseTool(host, options = {}) {
|
|
|
1208
1408
|
// and after a wait, whose whole point is to look again.
|
|
1209
1409
|
const looks = settings.screenshotAfterActions &&
|
|
1210
1410
|
available.has('screenshot') &&
|
|
1411
|
+
(hostActionAvailable(caps, 'screenshot') || postWindowId !== undefined) &&
|
|
1211
1412
|
(changed || planned.some((step) => step.item.type === 'wait')) &&
|
|
1212
1413
|
!signal?.aborted;
|
|
1213
1414
|
let shot;
|
|
@@ -1215,7 +1416,7 @@ export function createComputerUseTool(host, options = {}) {
|
|
|
1215
1416
|
if (looks) {
|
|
1216
1417
|
try {
|
|
1217
1418
|
await sleep(settings.settleMs, signal);
|
|
1218
|
-
shot = await capture();
|
|
1419
|
+
shot = await capture(postWindowId);
|
|
1219
1420
|
}
|
|
1220
1421
|
catch (error) {
|
|
1221
1422
|
shotError = signal?.aborted ? 'cancelled' : errorText(error);
|
|
@@ -1298,7 +1499,7 @@ export function createComputerUseTool(host, options = {}) {
|
|
|
1298
1499
|
description: buildDescription(host, caps, settings),
|
|
1299
1500
|
inputSchema: actionSchema,
|
|
1300
1501
|
modelInputSchema: hostModelSchema(host, caps, settings),
|
|
1301
|
-
validationErrorHint: `Action requirements: ${ALL_ACTIONS.map((action) =>
|
|
1502
|
+
validationErrorHint: `Action requirements: ${ALL_ACTIONS.map((action) => actionRequirement(action, host, caps)).join('; ')}. A batch is {"type":"batch","actions":[...]} with at most ${settings.maxBatchActions} actions and no screenshot, zoom, ui_snapshot or batch inside.`,
|
|
1302
1503
|
category: 'custom',
|
|
1303
1504
|
permissions: [],
|
|
1304
1505
|
readOnly: (input) => isReadOnlyInput(input),
|
|
@@ -1324,7 +1525,7 @@ export function createComputerUseTool(host, options = {}) {
|
|
|
1324
1525
|
try {
|
|
1325
1526
|
switch (input.type) {
|
|
1326
1527
|
case 'screenshot':
|
|
1327
|
-
return screenshotResult(await capture());
|
|
1528
|
+
return screenshotResult(await capture(input.window_id));
|
|
1328
1529
|
case 'zoom':
|
|
1329
1530
|
return await zoom(input);
|
|
1330
1531
|
case 'ui_snapshot':
|