@namzu/sdk 48.1.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 +14 -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/tools/schedules/schedule-tool.d.ts.map +1 -1
- package/dist/tools/schedules/schedule-tool.js +12 -5
- package/dist/tools/schedules/schedule-tool.js.map +1 -1
- package/dist/tools/schedules/types.d.ts +6 -0
- package/dist/tools/schedules/types.d.ts.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/tools/schedules/schedule-tool.ts +16 -5
- package/src/tools/schedules/types.ts +6 -0
- package/src/types/computer-use/index.ts +65 -7
|
@@ -13,6 +13,7 @@ import type {
|
|
|
13
13
|
UiElementAction,
|
|
14
14
|
UiSnapshot,
|
|
15
15
|
WindowInfo,
|
|
16
|
+
WindowInputAction,
|
|
16
17
|
} from '../../types/computer-use/index.js'
|
|
17
18
|
import type { ToolResultBlock } from '../../types/message/index.js'
|
|
18
19
|
import type { LLMProvider } from '../../types/provider/index.js'
|
|
@@ -29,6 +30,7 @@ import {
|
|
|
29
30
|
toDisplayPoint,
|
|
30
31
|
toDisplayRect,
|
|
31
32
|
toImagePoint,
|
|
33
|
+
toWindowPoint,
|
|
32
34
|
} from './computer-use-coordinates.js'
|
|
33
35
|
import {
|
|
34
36
|
type FittedImage,
|
|
@@ -136,21 +138,36 @@ const mouseClickSchema = z.object({
|
|
|
136
138
|
type: z.literal('mouse_click'),
|
|
137
139
|
at: pointSchema,
|
|
138
140
|
button: mouseButtonSchema,
|
|
141
|
+
/** Accepted only to return an explicit refusal; pointer foreground is unsafe. */
|
|
142
|
+
delivery_mode: z.enum(['background', 'foreground']).optional(),
|
|
139
143
|
})
|
|
140
144
|
const mouseDragSchema = z.object({
|
|
141
145
|
type: z.literal('mouse_drag'),
|
|
142
146
|
from: pointSchema,
|
|
143
147
|
to: pointSchema,
|
|
144
148
|
button: mouseButtonSchema,
|
|
149
|
+
delivery_mode: z.enum(['background', 'foreground']).optional(),
|
|
145
150
|
})
|
|
146
151
|
const scrollSchema = z.object({
|
|
147
152
|
type: z.literal('scroll'),
|
|
148
153
|
at: pointSchema,
|
|
149
154
|
direction: z.enum(['up', 'down', 'left', 'right']),
|
|
150
155
|
amount: z.number().int().positive(),
|
|
156
|
+
delivery_mode: z.enum(['background', 'foreground']).optional(),
|
|
157
|
+
})
|
|
158
|
+
const typeTextSchema = z.object({
|
|
159
|
+
type: z.literal('type_text'),
|
|
160
|
+
text: z.string(),
|
|
161
|
+
/** Accepted only to refuse an unsafe atomic foreground focus click. */
|
|
162
|
+
at: pointSchema.optional(),
|
|
163
|
+
delivery_mode: z.enum(['background', 'foreground']).optional(),
|
|
164
|
+
})
|
|
165
|
+
const keySchema = z.object({
|
|
166
|
+
type: z.literal('key'),
|
|
167
|
+
keys: z.string(),
|
|
168
|
+
at: pointSchema.optional(),
|
|
169
|
+
delivery_mode: z.enum(['background', 'foreground']).optional(),
|
|
151
170
|
})
|
|
152
|
-
const typeTextSchema = z.object({ type: z.literal('type_text'), text: z.string() })
|
|
153
|
-
const keySchema = z.object({ type: z.literal('key'), keys: z.string() })
|
|
154
171
|
const waitSchema = z.object({ type: z.literal('wait'), ms: z.number().int().nonnegative() })
|
|
155
172
|
const listWindowsSchema = z.object({ type: z.literal('list_windows') })
|
|
156
173
|
const focusWindowSchema = z.object({
|
|
@@ -196,7 +213,7 @@ const batchItemSchema = z.discriminatedUnion('type', [
|
|
|
196
213
|
const withFrame = { screenshot_id: screenshotIdSchema }
|
|
197
214
|
|
|
198
215
|
const actionSchema = z.discriminatedUnion('type', [
|
|
199
|
-
z.object({ type: z.literal('screenshot') }),
|
|
216
|
+
z.object({ type: z.literal('screenshot'), window_id: z.string().min(1).optional() }),
|
|
200
217
|
z.object({ type: z.literal('zoom'), region: regionSchema, ...withFrame }),
|
|
201
218
|
cursorPositionSchema.extend(withFrame),
|
|
202
219
|
mouseMoveSchema.extend(withFrame),
|
|
@@ -365,8 +382,18 @@ function hostActionAvailable(
|
|
|
365
382
|
)
|
|
366
383
|
}
|
|
367
384
|
|
|
385
|
+
function windowCaptureAvailable(host: ComputerUseHost, caps: ComputerUseCapabilities): boolean {
|
|
386
|
+
return (
|
|
387
|
+
caps.windowCapture === true &&
|
|
388
|
+
typeof host.captureWindow === 'function' &&
|
|
389
|
+
typeof host.executeWindow === 'function'
|
|
390
|
+
)
|
|
391
|
+
}
|
|
392
|
+
|
|
368
393
|
function availableActions(host: ComputerUseHost, caps: ComputerUseCapabilities): ToolActionType[] {
|
|
369
|
-
const
|
|
394
|
+
const displayScreenshot = hostActionAvailable(caps, 'screenshot')
|
|
395
|
+
const window = windowCaptureAvailable(host, caps)
|
|
396
|
+
const screenshot = displayScreenshot || window
|
|
370
397
|
const windows =
|
|
371
398
|
caps.windows === true &&
|
|
372
399
|
typeof host.listWindows === 'function' &&
|
|
@@ -377,9 +404,20 @@ function availableActions(host: ComputerUseHost, caps: ComputerUseCapabilities):
|
|
|
377
404
|
typeof host.uiAct === 'function'
|
|
378
405
|
const available = ALL_ACTIONS.filter((action) => {
|
|
379
406
|
switch (action) {
|
|
407
|
+
case 'screenshot':
|
|
408
|
+
return screenshot
|
|
380
409
|
case 'zoom':
|
|
381
410
|
case 'wait':
|
|
382
411
|
return screenshot
|
|
412
|
+
case 'mouse_click':
|
|
413
|
+
return hostActionAvailable(caps, action) || (window && caps.mouseClickButtons?.length !== 0)
|
|
414
|
+
case 'mouse_drag':
|
|
415
|
+
return hostActionAvailable(caps, action) || (window && caps.mouseDragButtons?.length !== 0)
|
|
416
|
+
case 'type_text':
|
|
417
|
+
case 'key':
|
|
418
|
+
return hostActionAvailable(caps, action) || window
|
|
419
|
+
case 'scroll':
|
|
420
|
+
return hostActionAvailable(caps, action) || (window && caps.windowScroll !== false)
|
|
383
421
|
case 'list_windows':
|
|
384
422
|
case 'focus_window':
|
|
385
423
|
return windows
|
|
@@ -396,16 +434,17 @@ function availableActions(host: ComputerUseHost, caps: ComputerUseCapabilities):
|
|
|
396
434
|
return available
|
|
397
435
|
}
|
|
398
436
|
|
|
399
|
-
function unavailableHostActions(caps: ComputerUseCapabilities): string[] {
|
|
437
|
+
function unavailableHostActions(host: ComputerUseHost, caps: ComputerUseCapabilities): string[] {
|
|
400
438
|
const unavailable: string[] = []
|
|
401
|
-
|
|
439
|
+
const window = windowCaptureAvailable(host, caps)
|
|
440
|
+
if (!caps.screenshot) unavailable.push(window ? 'display screenshot' : 'screenshot')
|
|
402
441
|
if (!caps.cursorPosition) unavailable.push('cursor_position')
|
|
403
|
-
if (!caps.mouse) unavailable.push('mouse')
|
|
404
|
-
if (!caps.keyboard) unavailable.push('keyboard')
|
|
442
|
+
if (!caps.mouse) unavailable.push(window ? 'display mouse' : 'mouse')
|
|
443
|
+
if (!caps.keyboard) unavailable.push(window ? 'display keyboard' : 'keyboard')
|
|
405
444
|
if (caps.supportedActions) {
|
|
445
|
+
const available = new Set(availableActions(host, caps))
|
|
406
446
|
for (const action of HOST_ACTIONS) {
|
|
407
|
-
if (!
|
|
408
|
-
unavailable.push(action)
|
|
447
|
+
if (!available.has(action) && !unavailable.includes(action)) unavailable.push(action)
|
|
409
448
|
}
|
|
410
449
|
}
|
|
411
450
|
return unavailable
|
|
@@ -421,11 +460,15 @@ function buildDescription(
|
|
|
421
460
|
settings: ResolvedSettings,
|
|
422
461
|
): string {
|
|
423
462
|
const available = availableActions(host, caps)
|
|
424
|
-
const unavailable = unavailableHostActions(caps)
|
|
425
|
-
const
|
|
426
|
-
`Controls the user's desktop on a ${caps.displayServer} host: screenshots, mouse and keyboard, for GUI tasks
|
|
427
|
-
|
|
428
|
-
|
|
463
|
+
const unavailable = unavailableHostActions(host, caps)
|
|
464
|
+
const intro = hostActionAvailable(caps, 'screenshot')
|
|
465
|
+
? `Controls the user's desktop on a ${caps.displayServer} host: screenshots, mouse and keyboard, for GUI tasks.`
|
|
466
|
+
: windowCaptureAvailable(host, caps)
|
|
467
|
+
? `Controls named windows on a ${caps.displayServer} host: window screenshots, mouse and keyboard, for GUI tasks.`
|
|
468
|
+
: available.length > 0
|
|
469
|
+
? `Offers the available computer controls on a ${caps.displayServer} host.`
|
|
470
|
+
: `Computer control is unavailable on this ${caps.displayServer} host; this tool reports the limitation.`
|
|
471
|
+
const lines = [intro, `Available actions: ${available.join('; ') || 'none'}.`]
|
|
429
472
|
if (unavailable.length > 0) {
|
|
430
473
|
lines.push(
|
|
431
474
|
caps.unavailableReason
|
|
@@ -435,7 +478,7 @@ function buildDescription(
|
|
|
435
478
|
}
|
|
436
479
|
if (!available.includes('screenshot')) return finish(lines, caps, available)
|
|
437
480
|
lines.push(
|
|
438
|
-
'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.
|
|
481
|
+
'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.',
|
|
439
482
|
'Take a screenshot before your first click.',
|
|
440
483
|
)
|
|
441
484
|
if (settings.screenshotAfterActions)
|
|
@@ -462,12 +505,18 @@ function buildDescription(
|
|
|
462
505
|
lines.push(
|
|
463
506
|
'list_windows names the open windows with their ids; focus_window brings one to the front.',
|
|
464
507
|
)
|
|
508
|
+
if (windowCaptureAvailable(host, caps))
|
|
509
|
+
lines.push(
|
|
510
|
+
`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.`,
|
|
511
|
+
)
|
|
465
512
|
if (available.includes('ui_snapshot'))
|
|
466
513
|
lines.push(
|
|
467
514
|
`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.`,
|
|
468
515
|
)
|
|
469
516
|
lines.push(
|
|
470
|
-
|
|
517
|
+
windowCaptureAvailable(host, caps)
|
|
518
|
+
? '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.'
|
|
519
|
+
: '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.',
|
|
471
520
|
)
|
|
472
521
|
if (available.includes('list_windows'))
|
|
473
522
|
lines.push(
|
|
@@ -519,8 +568,8 @@ const ACTION_REQUIREMENTS: Readonly<Record<ToolActionType, string>> = {
|
|
|
519
568
|
mouse_click: 'mouse_click needs at (button defaults to left)',
|
|
520
569
|
mouse_drag: 'mouse_drag needs from and to (button defaults to left)',
|
|
521
570
|
scroll: 'scroll needs at, direction, and amount',
|
|
522
|
-
type_text: 'type_text needs text',
|
|
523
|
-
key: 'key needs keys',
|
|
571
|
+
type_text: 'type_text needs text; delivery_mode is optional on a window screenshot',
|
|
572
|
+
key: 'key needs keys; delivery_mode is optional on a window screenshot',
|
|
524
573
|
wait: 'wait needs ms',
|
|
525
574
|
list_windows: 'list_windows needs no other fields',
|
|
526
575
|
focus_window: 'focus_window needs window_id',
|
|
@@ -529,6 +578,16 @@ const ACTION_REQUIREMENTS: Readonly<Record<ToolActionType, string>> = {
|
|
|
529
578
|
batch: 'batch needs actions',
|
|
530
579
|
}
|
|
531
580
|
|
|
581
|
+
function actionRequirement(
|
|
582
|
+
action: ToolActionType,
|
|
583
|
+
host: ComputerUseHost,
|
|
584
|
+
caps: ComputerUseCapabilities,
|
|
585
|
+
): string {
|
|
586
|
+
return action === 'screenshot' && windowCaptureAvailable(host, caps)
|
|
587
|
+
? `screenshot ${hostActionAvailable(caps, 'screenshot') ? 'takes an optional' : 'requires'} window_id to capture pixels for that window`
|
|
588
|
+
: ACTION_REQUIREMENTS[action]
|
|
589
|
+
}
|
|
590
|
+
|
|
532
591
|
function hostModelSchema(
|
|
533
592
|
host: ComputerUseHost,
|
|
534
593
|
caps: ComputerUseCapabilities,
|
|
@@ -566,13 +625,31 @@ function hostModelSchema(
|
|
|
566
625
|
type: 'string',
|
|
567
626
|
description: 'Key or key chord to press, for example ENTER or CTRL+R.',
|
|
568
627
|
},
|
|
628
|
+
...(windowCaptureAvailable(host, caps)
|
|
629
|
+
? {
|
|
630
|
+
delivery_mode: {
|
|
631
|
+
type: 'string',
|
|
632
|
+
enum: ['background', 'foreground'],
|
|
633
|
+
description:
|
|
634
|
+
'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.',
|
|
635
|
+
},
|
|
636
|
+
}
|
|
637
|
+
: {}),
|
|
569
638
|
ms: {
|
|
570
639
|
type: 'integer',
|
|
571
640
|
description: `Milliseconds to wait, 0 to ${settings.maxWaitMs}.`,
|
|
572
641
|
},
|
|
573
642
|
}
|
|
574
|
-
if (
|
|
575
|
-
|
|
643
|
+
if (
|
|
644
|
+
present.includes('focus_window') ||
|
|
645
|
+
(!forItems &&
|
|
646
|
+
(present.includes('ui_snapshot') ||
|
|
647
|
+
(present.includes('screenshot') && windowCaptureAvailable(host, caps))))
|
|
648
|
+
)
|
|
649
|
+
fields.window_id = {
|
|
650
|
+
type: 'string',
|
|
651
|
+
description: 'A window id from list_windows or supplied by the host.',
|
|
652
|
+
}
|
|
576
653
|
if (present.includes('ui_act')) {
|
|
577
654
|
fields.ref = {
|
|
578
655
|
type: 'string',
|
|
@@ -605,7 +682,7 @@ function hostModelSchema(
|
|
|
605
682
|
type: {
|
|
606
683
|
type: 'string',
|
|
607
684
|
enum: actions,
|
|
608
|
-
description: `Desktop action. ${actions.map((action) =>
|
|
685
|
+
description: `Desktop action. ${actions.map((action) => actionRequirement(action, host, caps)).join('; ')}.`,
|
|
609
686
|
},
|
|
610
687
|
...fieldSchemas(false),
|
|
611
688
|
}
|
|
@@ -650,6 +727,10 @@ function pointLabel(point: { readonly x: number; readonly y: number }): string {
|
|
|
650
727
|
return `(${point.x}, ${point.y})`
|
|
651
728
|
}
|
|
652
729
|
|
|
730
|
+
function deliveryLabel(mode: 'background' | 'foreground' | undefined): string {
|
|
731
|
+
return mode === 'foreground' ? ' in foreground' : ''
|
|
732
|
+
}
|
|
733
|
+
|
|
653
734
|
function quotedText(value: string): string {
|
|
654
735
|
const oneLine = value.replace(/\s+/g, ' ')
|
|
655
736
|
const visible = oneLine.length > 64 ? `${oneLine.slice(0, 63)}…` : oneLine
|
|
@@ -696,7 +777,7 @@ function itemLabel(
|
|
|
696
777
|
): string {
|
|
697
778
|
switch (input.type) {
|
|
698
779
|
case 'screenshot':
|
|
699
|
-
return 'Capture screenshot'
|
|
780
|
+
return input.window_id ? `Capture window ${input.window_id}` : 'Capture screenshot'
|
|
700
781
|
case 'zoom':
|
|
701
782
|
return `Zoom into ${input.region.width}x${input.region.height} at ${pointLabel(input.region)}`
|
|
702
783
|
case 'cursor_position':
|
|
@@ -710,9 +791,9 @@ function itemLabel(
|
|
|
710
791
|
case 'scroll':
|
|
711
792
|
return `Scroll ${input.direction} ${input.amount} at ${pointLabel(input.at)}`
|
|
712
793
|
case 'type_text':
|
|
713
|
-
return `Type ${quotedText(input.text)}`
|
|
794
|
+
return `Type ${quotedText(input.text)}${deliveryLabel(input.delivery_mode)}`
|
|
714
795
|
case 'key':
|
|
715
|
-
return `Press ${input.keys}`
|
|
796
|
+
return `Press ${input.keys}${deliveryLabel(input.delivery_mode)}`
|
|
716
797
|
case 'wait':
|
|
717
798
|
return waitLabel(input.ms)
|
|
718
799
|
case 'list_windows':
|
|
@@ -806,6 +887,11 @@ function errorText(error: unknown): string {
|
|
|
806
887
|
return error instanceof Error ? error.message : String(error)
|
|
807
888
|
}
|
|
808
889
|
|
|
890
|
+
/** Driver IDs are normally short opaque tokens; never put arbitrary host text in tool instructions. */
|
|
891
|
+
function visibleWindowId(id: string): string {
|
|
892
|
+
return /^[A-Za-z0-9_.:-]{1,64}$/.test(id) ? id : '[window id]'
|
|
893
|
+
}
|
|
894
|
+
|
|
809
895
|
/** One planned step: validated, coordinates already mapped, ready to run. */
|
|
810
896
|
interface PlannedStep {
|
|
811
897
|
readonly item: BatchItem
|
|
@@ -903,7 +989,8 @@ function uiElementLine(
|
|
|
903
989
|
(UI_ACTIONS as readonly string[]).includes(action),
|
|
904
990
|
)
|
|
905
991
|
if (actions.length > 0) parts.push(`[${actions.join(', ')}]`)
|
|
906
|
-
const at =
|
|
992
|
+
const at =
|
|
993
|
+
frame && !frame.window && element.bounds ? centreOnImage(frame, element.bounds) : undefined
|
|
907
994
|
if (at) parts.push(`@${pointLabel(at)}`)
|
|
908
995
|
}
|
|
909
996
|
return neutralizeEnvelopeDelimiter(parts.join(' '))
|
|
@@ -956,6 +1043,7 @@ export function createComputerUseTool(
|
|
|
956
1043
|
windows: false,
|
|
957
1044
|
regionCapture: false,
|
|
958
1045
|
uiTree: false,
|
|
1046
|
+
windowCapture: false,
|
|
959
1047
|
unavailableReason: options.unavailableReason,
|
|
960
1048
|
}
|
|
961
1049
|
: host.capabilities
|
|
@@ -968,6 +1056,8 @@ export function createComputerUseTool(
|
|
|
968
1056
|
let uiRefs = new Map<string, UiRef>()
|
|
969
1057
|
let uiSnapshots = 0
|
|
970
1058
|
let uiRefCount = 0
|
|
1059
|
+
let uiSnapshotWindowId: string | undefined
|
|
1060
|
+
let uiSnapshotFrame: ScreenshotFrame | undefined
|
|
971
1061
|
const describeUiRef = (ref: string): string | undefined => {
|
|
972
1062
|
const entry = uiRefs.get(ref)
|
|
973
1063
|
return entry ? `${uiElementText(entry.element)} (${ref})` : undefined
|
|
@@ -975,6 +1065,7 @@ export function createComputerUseTool(
|
|
|
975
1065
|
const label = (input: BatchItem | ActionInput): string => itemLabel(input, describeUiRef)
|
|
976
1066
|
|
|
977
1067
|
const refusal = (type: string): string | null => {
|
|
1068
|
+
if (available.has(type)) return null
|
|
978
1069
|
const required = requiredCapability(type)
|
|
979
1070
|
const why = caps.unavailableReason
|
|
980
1071
|
? ` ${caps.unavailableReason} Do not retry; tell the user.`
|
|
@@ -986,8 +1077,27 @@ export function createComputerUseTool(
|
|
|
986
1077
|
return null
|
|
987
1078
|
}
|
|
988
1079
|
|
|
989
|
-
/** Capture the display, fit it, and number it. */
|
|
990
|
-
const capture = async (): Promise<Capture> => {
|
|
1080
|
+
/** Capture the display or one explicitly named window, fit it, and number it. */
|
|
1081
|
+
const capture = async (windowId?: string): Promise<Capture> => {
|
|
1082
|
+
if (windowId !== undefined) {
|
|
1083
|
+
if (!windowCaptureAvailable(host, caps))
|
|
1084
|
+
throw new StepFailure(
|
|
1085
|
+
'this host cannot capture a named window; no desktop screenshot was taken',
|
|
1086
|
+
)
|
|
1087
|
+
const shot = await (host.captureWindow as NonNullable<ComputerUseHost['captureWindow']>)(
|
|
1088
|
+
windowId,
|
|
1089
|
+
)
|
|
1090
|
+
const image = await fitPng(shot.data, settings.limits)
|
|
1091
|
+
if (image.sourceWidth !== shot.width || image.sourceHeight !== shot.height)
|
|
1092
|
+
throw new StepFailure(
|
|
1093
|
+
'the window PNG dimensions changed during capture; take a new screenshot',
|
|
1094
|
+
)
|
|
1095
|
+
return { frame: frames.recordWindow(image, shot), image }
|
|
1096
|
+
}
|
|
1097
|
+
if (!hostActionAvailable(caps, 'screenshot'))
|
|
1098
|
+
throw new StepFailure(
|
|
1099
|
+
'this host requires window_id for screenshots; no display screenshot was taken',
|
|
1100
|
+
)
|
|
991
1101
|
const result = await host.execute({ type: 'screenshot' })
|
|
992
1102
|
if (result.type !== 'screenshot')
|
|
993
1103
|
throw new Error(`computer_use: the host answered a screenshot with "${result.type}"`)
|
|
@@ -1004,6 +1114,10 @@ export function createComputerUseTool(
|
|
|
1004
1114
|
throw new StepFailure(
|
|
1005
1115
|
`there is no screenshot ${id} (the latest is ${frames.latest()?.id ?? 'none'}); take a screenshot and use its coordinates`,
|
|
1006
1116
|
)
|
|
1117
|
+
if (frame.window && frames.latest() !== frame)
|
|
1118
|
+
throw new StepFailure(
|
|
1119
|
+
`window screenshot ${id} is stale; take a new window screenshot before acting`,
|
|
1120
|
+
)
|
|
1007
1121
|
return frame
|
|
1008
1122
|
}
|
|
1009
1123
|
const latest = frames.latest()
|
|
@@ -1031,19 +1145,40 @@ export function createComputerUseTool(
|
|
|
1031
1145
|
throw new StepFailure(
|
|
1032
1146
|
`${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`,
|
|
1033
1147
|
)
|
|
1034
|
-
return toDisplayPoint(frame, point)
|
|
1148
|
+
return frame.window ? toWindowPoint(frame, point) : toDisplayPoint(frame, point)
|
|
1035
1149
|
}
|
|
1036
1150
|
|
|
1037
|
-
const runHost = async (
|
|
1151
|
+
const runHost = async (
|
|
1152
|
+
action: ComputerUseAction | WindowInputAction,
|
|
1153
|
+
frame?: ScreenshotFrame,
|
|
1154
|
+
): Promise<string> => {
|
|
1038
1155
|
try {
|
|
1156
|
+
if (frame?.window) {
|
|
1157
|
+
if (
|
|
1158
|
+
action.type === 'screenshot' ||
|
|
1159
|
+
action.type === 'cursor_position' ||
|
|
1160
|
+
action.type === 'mouse_move'
|
|
1161
|
+
)
|
|
1162
|
+
throw new StepFailure(
|
|
1163
|
+
`${action.type} has no window-scoped input path; use a display screenshot`,
|
|
1164
|
+
)
|
|
1165
|
+
if (frames.latest() !== frame)
|
|
1166
|
+
throw new StepFailure(`window screenshot ${frame.id} is stale; capture the window again`)
|
|
1167
|
+
await (host.executeWindow as NonNullable<ComputerUseHost['executeWindow']>)(
|
|
1168
|
+
frame.window.captureId,
|
|
1169
|
+
action as WindowInputAction,
|
|
1170
|
+
)
|
|
1171
|
+
return 'done'
|
|
1172
|
+
}
|
|
1039
1173
|
const result = await host.execute(action)
|
|
1040
1174
|
if (result.type === 'cursor_position') {
|
|
1041
|
-
const
|
|
1042
|
-
if (!
|
|
1043
|
-
return `at ${pointLabel(toImagePoint(
|
|
1175
|
+
const selected = frame ?? frames.latest()
|
|
1176
|
+
if (!selected) return `at display pixel ${pointLabel(result.point)}`
|
|
1177
|
+
return `at ${pointLabel(toImagePoint(selected, result.point))} in ${selected.id}`
|
|
1044
1178
|
}
|
|
1045
1179
|
return 'done'
|
|
1046
1180
|
} catch (error) {
|
|
1181
|
+
if (error instanceof StepFailure) throw error
|
|
1047
1182
|
if (isOutcomeUnknown(error, action.type)) throw new StepFailure(error.message, error)
|
|
1048
1183
|
throw new StepFailure(errorText(error))
|
|
1049
1184
|
}
|
|
@@ -1060,15 +1195,28 @@ export function createComputerUseTool(
|
|
|
1060
1195
|
run,
|
|
1061
1196
|
})
|
|
1062
1197
|
switch (item.type) {
|
|
1063
|
-
case 'cursor_position':
|
|
1064
|
-
frameFor(frameId)
|
|
1065
|
-
|
|
1198
|
+
case 'cursor_position': {
|
|
1199
|
+
const frame = frameFor(frameId)
|
|
1200
|
+
if (frame.window)
|
|
1201
|
+
throw new StepFailure(
|
|
1202
|
+
'cursor_position is display-relative; take a display screenshot first',
|
|
1203
|
+
)
|
|
1204
|
+
return step(() => runHost({ type: 'cursor_position' }, frame))
|
|
1205
|
+
}
|
|
1066
1206
|
case 'mouse_move': {
|
|
1067
1207
|
const frame = frameFor(frameId)
|
|
1208
|
+
if (frame.window)
|
|
1209
|
+
throw new StepFailure(
|
|
1210
|
+
'mouse_move cannot move the OS pointer in window scope; use a click or a display screenshot',
|
|
1211
|
+
)
|
|
1068
1212
|
const to = mapPoint(frame, item.to, 'to')
|
|
1069
1213
|
return step(() => runHost({ type: 'mouse_move', to }))
|
|
1070
1214
|
}
|
|
1071
1215
|
case 'mouse_click': {
|
|
1216
|
+
if (item.delivery_mode)
|
|
1217
|
+
throw new StepFailure(
|
|
1218
|
+
'window pointer delivery_mode is unavailable; foreground input cannot verify which window is under the cursor',
|
|
1219
|
+
)
|
|
1072
1220
|
const buttons = caps.mouseClickButtons
|
|
1073
1221
|
if (buttons && !buttons.includes(item.button))
|
|
1074
1222
|
throw new StepFailure(
|
|
@@ -1076,9 +1224,13 @@ export function createComputerUseTool(
|
|
|
1076
1224
|
)
|
|
1077
1225
|
const frame = frameFor(frameId)
|
|
1078
1226
|
const at = mapPoint(frame, item.at, 'at')
|
|
1079
|
-
return step(() => runHost({ type: 'mouse_click', at, button: item.button }))
|
|
1227
|
+
return step(() => runHost({ type: 'mouse_click', at, button: item.button }, frame))
|
|
1080
1228
|
}
|
|
1081
1229
|
case 'mouse_drag': {
|
|
1230
|
+
if (item.delivery_mode)
|
|
1231
|
+
throw new StepFailure(
|
|
1232
|
+
'window pointer delivery_mode is unavailable; foreground input cannot verify which window is under the cursor',
|
|
1233
|
+
)
|
|
1082
1234
|
const buttons = caps.mouseDragButtons
|
|
1083
1235
|
if (buttons && !buttons.includes(item.button))
|
|
1084
1236
|
throw new StepFailure(
|
|
@@ -1087,22 +1239,74 @@ export function createComputerUseTool(
|
|
|
1087
1239
|
const frame = frameFor(frameId)
|
|
1088
1240
|
const from = mapPoint(frame, item.from, 'from')
|
|
1089
1241
|
const to = mapPoint(frame, item.to, 'to')
|
|
1090
|
-
return step(() => runHost({ type: 'mouse_drag', from, to, button: item.button }))
|
|
1242
|
+
return step(() => runHost({ type: 'mouse_drag', from, to, button: item.button }, frame))
|
|
1091
1243
|
}
|
|
1092
1244
|
case 'scroll': {
|
|
1093
1245
|
const frame = frameFor(frameId)
|
|
1246
|
+
if (item.delivery_mode)
|
|
1247
|
+
throw new StepFailure(
|
|
1248
|
+
'window pointer delivery_mode is unavailable; foreground scrolling cannot verify which window receives the wheel',
|
|
1249
|
+
)
|
|
1250
|
+
if (frame.window && caps.windowScroll === false)
|
|
1251
|
+
throw new StepFailure(
|
|
1252
|
+
'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',
|
|
1253
|
+
)
|
|
1094
1254
|
const at = mapPoint(frame, item.at, 'at')
|
|
1095
1255
|
return step(() =>
|
|
1096
|
-
runHost({ type: 'scroll', at, direction: item.direction, amount: item.amount }),
|
|
1256
|
+
runHost({ type: 'scroll', at, direction: item.direction, amount: item.amount }, frame),
|
|
1097
1257
|
)
|
|
1098
1258
|
}
|
|
1099
|
-
case 'type_text':
|
|
1259
|
+
case 'type_text': {
|
|
1260
|
+
if (item.at)
|
|
1261
|
+
throw new StepFailure(
|
|
1262
|
+
'type_text at is unavailable: a foreground focus click cannot verify which window is under the point; click the field first, then type',
|
|
1263
|
+
)
|
|
1100
1264
|
requireLook()
|
|
1101
|
-
|
|
1102
|
-
|
|
1265
|
+
const frame = frameFor(frameId)
|
|
1266
|
+
if (item.delivery_mode && !frame.window)
|
|
1267
|
+
throw new StepFailure('type_text delivery_mode requires a window screenshot')
|
|
1268
|
+
if (frame.window && TERMINAL_APPS.test(frame.window.window.app))
|
|
1269
|
+
throw new StepFailure('computer_use never types into a terminal window')
|
|
1270
|
+
return step(() =>
|
|
1271
|
+
runHost(
|
|
1272
|
+
{
|
|
1273
|
+
type: 'type_text',
|
|
1274
|
+
text: item.text,
|
|
1275
|
+
...(item.delivery_mode ? { delivery_mode: item.delivery_mode } : {}),
|
|
1276
|
+
},
|
|
1277
|
+
frame,
|
|
1278
|
+
),
|
|
1279
|
+
)
|
|
1280
|
+
}
|
|
1281
|
+
case 'key': {
|
|
1282
|
+
if (item.at)
|
|
1283
|
+
throw new StepFailure(
|
|
1284
|
+
'key at is unavailable: a foreground focus click cannot verify which window is under the point; click the field first, then send the key',
|
|
1285
|
+
)
|
|
1103
1286
|
requireLook()
|
|
1104
|
-
|
|
1287
|
+
const frame = frameFor(frameId)
|
|
1288
|
+
if (item.delivery_mode && !frame.window)
|
|
1289
|
+
throw new StepFailure(
|
|
1290
|
+
'key delivery_mode requires a window screenshot; take one with screenshot {window_id}',
|
|
1291
|
+
)
|
|
1292
|
+
if (frame.window && TERMINAL_APPS.test(frame.window.window.app))
|
|
1293
|
+
throw new StepFailure('computer_use never sends keys to a terminal window')
|
|
1294
|
+
return step(() =>
|
|
1295
|
+
runHost(
|
|
1296
|
+
{
|
|
1297
|
+
type: 'key',
|
|
1298
|
+
keys: item.keys,
|
|
1299
|
+
...(item.delivery_mode ? { delivery_mode: item.delivery_mode } : {}),
|
|
1300
|
+
},
|
|
1301
|
+
frame,
|
|
1302
|
+
),
|
|
1303
|
+
)
|
|
1304
|
+
}
|
|
1105
1305
|
case 'wait':
|
|
1306
|
+
if (!hostActionAvailable(caps, 'screenshot') && !frames.latest() && !uiSnapshotWindowId)
|
|
1307
|
+
throw new StepFailure(
|
|
1308
|
+
'this host has no display screenshot; capture or inspect a named window before waiting',
|
|
1309
|
+
)
|
|
1106
1310
|
return step(async (signal) => {
|
|
1107
1311
|
await sleep(item.ms, signal)
|
|
1108
1312
|
return 'done'
|
|
@@ -1176,7 +1380,8 @@ export function createComputerUseTool(
|
|
|
1176
1380
|
uiSnapshots += 1
|
|
1177
1381
|
const id = `u${uiSnapshots}`
|
|
1178
1382
|
const refs = new Map<string, UiRef>()
|
|
1179
|
-
const
|
|
1383
|
+
const latest = frames.latest()
|
|
1384
|
+
const frame = latest?.window ? undefined : latest
|
|
1180
1385
|
const lines: string[] = []
|
|
1181
1386
|
let chars = 0
|
|
1182
1387
|
let shown = 0
|
|
@@ -1208,11 +1413,13 @@ export function createComputerUseTool(
|
|
|
1208
1413
|
}
|
|
1209
1414
|
visit(snapshot.root, 0)
|
|
1210
1415
|
uiRefs = refs
|
|
1416
|
+
uiSnapshotWindowId = snapshot.windowId ?? input.window_id
|
|
1417
|
+
uiSnapshotFrame = frames.latest()
|
|
1211
1418
|
const window =
|
|
1212
1419
|
snapshot.windowId && /^[\w.:-]{1,64}$/.test(snapshot.windowId) ? snapshot.windowId : undefined
|
|
1213
1420
|
const header = [
|
|
1214
1421
|
`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.`,
|
|
1215
|
-
...(frame
|
|
1422
|
+
...(frame && !frame.window
|
|
1216
1423
|
? [
|
|
1217
1424
|
`@(x, y) is a control's centre on screenshot ${frame.id}, for a click when ui_act cannot reach it.`,
|
|
1218
1425
|
]
|
|
@@ -1298,7 +1505,8 @@ export function createComputerUseTool(
|
|
|
1298
1505
|
throw new StepFailure(errorText(error))
|
|
1299
1506
|
}
|
|
1300
1507
|
if (windows.length === 0) return 'no windows are open'
|
|
1301
|
-
const
|
|
1508
|
+
const latest = frames.latest()
|
|
1509
|
+
const frame = latest?.window ? undefined : latest
|
|
1302
1510
|
const lines = windows.slice(0, MAX_LISTED_WINDOWS).map((window) => {
|
|
1303
1511
|
const where = frame
|
|
1304
1512
|
? (() => {
|
|
@@ -1337,6 +1545,17 @@ export function createComputerUseTool(
|
|
|
1337
1545
|
}
|
|
1338
1546
|
|
|
1339
1547
|
const describeFrame = (frame: ScreenshotFrame): string => {
|
|
1548
|
+
if (frame.window) {
|
|
1549
|
+
const { window } = frame.window
|
|
1550
|
+
const focusWarning = window.focused
|
|
1551
|
+
? ''
|
|
1552
|
+
: ' 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.'
|
|
1553
|
+
const coverage =
|
|
1554
|
+
frame.window.captureCoverage === 'not_observable_in_window_scope'
|
|
1555
|
+
? ' Browser permission bubbles outside the window may be absent.'
|
|
1556
|
+
: ''
|
|
1557
|
+
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}`
|
|
1558
|
+
}
|
|
1340
1559
|
const { display } = frame
|
|
1341
1560
|
const scaled = frame.imageWidth !== display.width || frame.imageHeight !== display.height
|
|
1342
1561
|
return scaled
|
|
@@ -1348,7 +1567,16 @@ export function createComputerUseTool(
|
|
|
1348
1567
|
id: frame.id,
|
|
1349
1568
|
width: frame.imageWidth,
|
|
1350
1569
|
height: frame.imageHeight,
|
|
1351
|
-
|
|
1570
|
+
...(frame.window
|
|
1571
|
+
? {
|
|
1572
|
+
window: {
|
|
1573
|
+
id: visibleWindowId(frame.window.window.id),
|
|
1574
|
+
pid: frame.window.window.pid,
|
|
1575
|
+
bounds: frame.window.window.bounds,
|
|
1576
|
+
},
|
|
1577
|
+
captureCoverage: frame.window.captureCoverage,
|
|
1578
|
+
}
|
|
1579
|
+
: { display: frame.display }),
|
|
1352
1580
|
mimeType: 'image/png' as const,
|
|
1353
1581
|
encoding: 'base64' as const,
|
|
1354
1582
|
})
|
|
@@ -1356,7 +1584,7 @@ export function createComputerUseTool(
|
|
|
1356
1584
|
const pin = (frame: ScreenshotFrame): NonNullable<ToolResult['workingState']> => [
|
|
1357
1585
|
{
|
|
1358
1586
|
key: 'computer_use.screenshot',
|
|
1359
|
-
text: `computer_use coordinates are pixels of screenshot ${frame.id} (${frame.imageWidth}x${frame.imageHeight}), origin top-left.`,
|
|
1587
|
+
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)}` : ''}.`,
|
|
1360
1588
|
},
|
|
1361
1589
|
]
|
|
1362
1590
|
|
|
@@ -1378,7 +1606,9 @@ export function createComputerUseTool(
|
|
|
1378
1606
|
|
|
1379
1607
|
const screenshotResult = (shot: Capture): ToolResult => ({
|
|
1380
1608
|
success: true,
|
|
1381
|
-
output:
|
|
1609
|
+
output: shot.frame.window
|
|
1610
|
+
? `Screenshot ${shot.frame.id} captured (${shot.frame.imageWidth}x${shot.frame.imageHeight} of window ${visibleWindowId(shot.frame.window.window.id)}).`
|
|
1611
|
+
: `Screenshot ${shot.frame.id} captured (${shot.frame.imageWidth}x${shot.frame.imageHeight} of the ${shot.frame.display.width}x${shot.frame.display.height} display).`,
|
|
1382
1612
|
content: [
|
|
1383
1613
|
{ type: 'text', text: [describeFrame(shot.frame), ...firstLookHint(shot.frame)].join('\n') },
|
|
1384
1614
|
imageBlock(shot.image),
|
|
@@ -1402,7 +1632,11 @@ export function createComputerUseTool(
|
|
|
1402
1632
|
)
|
|
1403
1633
|
const rect = toDisplayRect(frame, onImage) as Rect
|
|
1404
1634
|
let fitted: FittedImage
|
|
1405
|
-
if (
|
|
1635
|
+
if (frame.window) {
|
|
1636
|
+
if (!frame.window.data)
|
|
1637
|
+
throw new StepFailure(`window screenshot ${frame.id} is stale; capture the window again`)
|
|
1638
|
+
fitted = await cropAndFitPng(frame.window.data, rect, settings.limits)
|
|
1639
|
+
} else if (caps.regionCapture === true && typeof host.captureRegion === 'function') {
|
|
1406
1640
|
const piece = await host.captureRegion(rect)
|
|
1407
1641
|
fitted = await fitPng(piece.data, settings.limits)
|
|
1408
1642
|
} else {
|
|
@@ -1477,6 +1711,30 @@ export function createComputerUseTool(
|
|
|
1477
1711
|
// the screenshot in step 5 should not arrive after steps 1–4 changed
|
|
1478
1712
|
// the desktop.
|
|
1479
1713
|
const planned: PlannedStep[] = []
|
|
1714
|
+
const actionFrame = frameId ? frames.get(frameId) : frames.latest()
|
|
1715
|
+
const explicitDisplayFrame =
|
|
1716
|
+
frameId !== undefined && actionFrame !== undefined && !actionFrame.window
|
|
1717
|
+
if (
|
|
1718
|
+
actionFrame?.window &&
|
|
1719
|
+
items.some(
|
|
1720
|
+
(item, index) =>
|
|
1721
|
+
item.type === 'focus_window' &&
|
|
1722
|
+
items
|
|
1723
|
+
.slice(index + 1)
|
|
1724
|
+
.some((later) =>
|
|
1725
|
+
['mouse_click', 'mouse_drag', 'scroll', 'type_text', 'key'].includes(later.type),
|
|
1726
|
+
),
|
|
1727
|
+
)
|
|
1728
|
+
)
|
|
1729
|
+
return {
|
|
1730
|
+
success: false,
|
|
1731
|
+
output: '',
|
|
1732
|
+
error:
|
|
1733
|
+
'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.',
|
|
1734
|
+
}
|
|
1735
|
+
let postWindowId =
|
|
1736
|
+
actionFrame?.window?.window.id ??
|
|
1737
|
+
(!hostActionAvailable(caps, 'screenshot') ? uiSnapshotWindowId : undefined)
|
|
1480
1738
|
for (const [index, item] of items.entries()) {
|
|
1481
1739
|
try {
|
|
1482
1740
|
planned.push(plan(item, frameId))
|
|
@@ -1507,12 +1765,35 @@ export function createComputerUseTool(
|
|
|
1507
1765
|
}
|
|
1508
1766
|
try {
|
|
1509
1767
|
const keyboard = step.item.type === 'type_text' || step.item.type === 'key'
|
|
1510
|
-
if (keyboard && !frontChecked) {
|
|
1768
|
+
if (keyboard && !actionFrame?.window && !frontChecked) {
|
|
1511
1769
|
await refuseTerminalInFront()
|
|
1512
1770
|
frontChecked = true
|
|
1513
1771
|
}
|
|
1514
1772
|
if (step.item.type !== 'type_text') frontChecked = false
|
|
1515
1773
|
const note = await step.run(signal)
|
|
1774
|
+
if (step.item.type === 'focus_window' && windowCaptureAvailable(host, caps))
|
|
1775
|
+
postWindowId = step.item.window_id
|
|
1776
|
+
else if (
|
|
1777
|
+
step.item.type === 'ui_act' &&
|
|
1778
|
+
uiSnapshotWindowId &&
|
|
1779
|
+
!explicitDisplayFrame &&
|
|
1780
|
+
windowCaptureAvailable(host, caps)
|
|
1781
|
+
)
|
|
1782
|
+
postWindowId = uiSnapshotWindowId
|
|
1783
|
+
else if (
|
|
1784
|
+
step.item.type === 'wait' &&
|
|
1785
|
+
!changed &&
|
|
1786
|
+
uiSnapshotWindowId &&
|
|
1787
|
+
uiSnapshotFrame === frames.latest() &&
|
|
1788
|
+
frameId === undefined &&
|
|
1789
|
+
windowCaptureAvailable(host, caps)
|
|
1790
|
+
)
|
|
1791
|
+
postWindowId = uiSnapshotWindowId
|
|
1792
|
+
else if (
|
|
1793
|
+
actionFrame?.window &&
|
|
1794
|
+
['mouse_click', 'mouse_drag', 'scroll', 'type_text', 'key'].includes(step.item.type)
|
|
1795
|
+
)
|
|
1796
|
+
postWindowId = actionFrame.window.window.id
|
|
1516
1797
|
records.push({ label: step.label, status: 'done', note })
|
|
1517
1798
|
if (step.mutating) changed = true
|
|
1518
1799
|
} catch (error) {
|
|
@@ -1537,6 +1818,7 @@ export function createComputerUseTool(
|
|
|
1537
1818
|
const looks =
|
|
1538
1819
|
settings.screenshotAfterActions &&
|
|
1539
1820
|
available.has('screenshot') &&
|
|
1821
|
+
(hostActionAvailable(caps, 'screenshot') || postWindowId !== undefined) &&
|
|
1540
1822
|
(changed || planned.some((step) => step.item.type === 'wait')) &&
|
|
1541
1823
|
!signal?.aborted
|
|
1542
1824
|
let shot: Capture | undefined
|
|
@@ -1544,7 +1826,7 @@ export function createComputerUseTool(
|
|
|
1544
1826
|
if (looks) {
|
|
1545
1827
|
try {
|
|
1546
1828
|
await sleep(settings.settleMs, signal)
|
|
1547
|
-
shot = await capture()
|
|
1829
|
+
shot = await capture(postWindowId)
|
|
1548
1830
|
} catch (error) {
|
|
1549
1831
|
shotError = signal?.aborted ? 'cancelled' : errorText(error)
|
|
1550
1832
|
}
|
|
@@ -1632,7 +1914,7 @@ export function createComputerUseTool(
|
|
|
1632
1914
|
description: buildDescription(host, caps, settings),
|
|
1633
1915
|
inputSchema: actionSchema,
|
|
1634
1916
|
modelInputSchema: hostModelSchema(host, caps, settings),
|
|
1635
|
-
validationErrorHint: `Action requirements: ${ALL_ACTIONS.map((action) =>
|
|
1917
|
+
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.`,
|
|
1636
1918
|
category: 'custom',
|
|
1637
1919
|
permissions: [],
|
|
1638
1920
|
readOnly: (input: ActionInput) => isReadOnlyInput(input),
|
|
@@ -1659,7 +1941,7 @@ export function createComputerUseTool(
|
|
|
1659
1941
|
try {
|
|
1660
1942
|
switch (input.type) {
|
|
1661
1943
|
case 'screenshot':
|
|
1662
|
-
return screenshotResult(await capture())
|
|
1944
|
+
return screenshotResult(await capture(input.window_id))
|
|
1663
1945
|
case 'zoom':
|
|
1664
1946
|
return await zoom(input)
|
|
1665
1947
|
case 'ui_snapshot':
|