@namzu/sdk 61.2.1 → 61.4.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.
@@ -58,17 +58,105 @@ const DEFAULT_SETTLE_MS = 500
58
58
  const DEFAULT_MAX_BATCH_ACTIONS = 20
59
59
  const DEFAULT_MAX_WAIT_MS = 10_000
60
60
  const DEFAULT_WAIT_FOR_MS = 10_000
61
+
62
+ /**
63
+ * The wait between the last action and the screenshot that follows it. A host
64
+ * that can tell when the window stopped changing (see
65
+ * {@link ComputerUseHost.settle}) is asked first and ends the wait early; a
66
+ * host without it, one that cannot say, or one that fails keeps the fixed
67
+ * `settleMs` it always had. Cancellation is never swallowed.
68
+ */
69
+ async function settleAfterActions(
70
+ host: ComputerUseHost,
71
+ windowId: string | undefined,
72
+ settleMs: number,
73
+ signal: AbortSignal | undefined,
74
+ ): Promise<void> {
75
+ if (settleMs > 0 && typeof host.settle === 'function') {
76
+ try {
77
+ const settled = await host.settle(
78
+ { budgetMs: settleMs, ...(windowId !== undefined ? { windowId } : {}) },
79
+ signal,
80
+ )
81
+ if (settled !== undefined) return
82
+ } catch {
83
+ // Fall through to the fixed wait; a cancelled signal makes it reject at once.
84
+ }
85
+ }
86
+ await sleep(settleMs, signal)
87
+ }
61
88
  const MAX_LISTED_WINDOWS = 50
62
89
  /** The most of a ui_snapshot the model is shown, in characters (about 4 000 tokens). */
63
90
  const MAX_UI_SNAPSHOT_CHARS = 14_000
64
91
  /** The refs of earlier snapshots the tool still resolves for the host. */
65
92
  const MAX_HELD_UI_REFS = 4_000
66
93
 
94
+ /** One control an advisor may be asked to choose among. */
95
+ export interface ComputerUseAdvisorCandidate {
96
+ /** The ref the model was shown for it. */
97
+ readonly ref: string
98
+ /** The control as the model reads it, such as `Button "Save"`. */
99
+ readonly description: string
100
+ }
101
+
102
+ /** What an advisor is asked: which of these controls does this intent name? */
103
+ export interface ComputerUseAdvisorRequest {
104
+ /** What the model wants done, in its own words. */
105
+ readonly intent: string
106
+ readonly candidates: readonly ComputerUseAdvisorCandidate[]
107
+ /**
108
+ * The ref the model itself chose for this intent, when it chose one. Then the
109
+ * advisor is only watching: the answer is not acted on.
110
+ */
111
+ readonly chosen?: string
112
+ }
113
+
114
+ /** An advisor's answer: the candidates in order of fit, best first, each with how sure it is (0 to 1). */
115
+ export interface ComputerUseAdvice {
116
+ readonly ranked: readonly {
117
+ readonly ref: string
118
+ readonly confidence: number
119
+ }[]
120
+ }
121
+
122
+ /**
123
+ * A fast second opinion on which control an intent names, for hosts that run a
124
+ * small decision model beside the main one. The tool never depends on it: an
125
+ * advisor that fails, is slow or is unsure is the same as none.
126
+ */
127
+ export interface ComputerUseAdvisor {
128
+ /**
129
+ * Whether the tool may act on the answer. Then the model can name a control
130
+ * by words (`ui_do`) and the tool chooses; when false the advisor is asked
131
+ * about the controls the model chose by ref (`ui_act` with `intent`) and its
132
+ * answer is not used.
133
+ */
134
+ readonly acts: boolean
135
+ pick(
136
+ request: ComputerUseAdvisorRequest,
137
+ signal?: AbortSignal,
138
+ ): Promise<ComputerUseAdvice | undefined>
139
+ }
140
+
141
+ /** The least confidence in the best control for the tool to act on it. */
142
+ const ADVICE_MIN_CONFIDENCE = 0.85
143
+ /** How far the best control must lead the next one. */
144
+ const ADVICE_MIN_MARGIN = 0.4
145
+ /** The most controls an advisor is asked about at once. */
146
+ const MAX_ADVISED_CANDIDATES = 255
147
+
67
148
  /**
68
149
  * How `createComputerUseTool` sizes screenshots, paces actions and bounds a
69
150
  * batch. Every field is optional.
70
151
  */
71
152
  export interface ComputerUseToolOptions {
153
+ /**
154
+ * A second opinion on which control an intent names; see
155
+ * {@link ComputerUseAdvisor}. With one that `acts`, the tool offers `ui_do`
156
+ * (name a control by words); with any, `ui_act` accepts an `intent`. Absent,
157
+ * neither exists and the tool is exactly what it was.
158
+ */
159
+ readonly advisor?: ComputerUseAdvisor
72
160
  /**
73
161
  * What every screenshot and zoom image is fitted to before the model sees
74
162
  * it. Default {@link STANDARD_SCREENSHOT_LIMITS}, which every current vision
@@ -243,6 +331,16 @@ const uiActSchema = z.object({
243
331
  ref: z.string().min(1),
244
332
  action: z.enum(UI_ACTIONS),
245
333
  value: z.string().optional(),
334
+ /** What this is for, in a few words; only read when the tool has an advisor. */
335
+ intent: z.string().min(1).max(200).optional(),
336
+ })
337
+ const uiDoSchema = z.object({
338
+ type: z.literal('ui_do'),
339
+ /** The control, in words: "the Save button", "the name field". */
340
+ target: z.string().min(1).max(200),
341
+ action: z.enum(UI_ACTIONS),
342
+ value: z.string().optional(),
343
+ window_id: z.string().min(1).optional(),
246
344
  })
247
345
 
248
346
  /** What a batch may carry: everything but the image-returning actions and another batch. */
@@ -258,6 +356,7 @@ const batchItemSchema = z.discriminatedUnion('type', [
258
356
  listWindowsSchema,
259
357
  focusWindowSchema,
260
358
  uiActSchema,
359
+ uiDoSchema,
261
360
  ])
262
361
 
263
362
  const withFrame = { screenshot_id: screenshotIdSchema }
@@ -281,6 +380,7 @@ const actionSchema = z.discriminatedUnion('type', [
281
380
  uiSnapshotSchema,
282
381
  waitForSchema,
283
382
  uiActSchema.extend(withFrame),
383
+ uiDoSchema.extend(withFrame),
284
384
  z.object({
285
385
  type: z.literal('batch'),
286
386
  actions: z.array(batchItemSchema).min(1),
@@ -328,6 +428,7 @@ const ALL_ACTIONS: readonly ToolActionType[] = [
328
428
  'ui_snapshot',
329
429
  'wait_for',
330
430
  'ui_act',
431
+ 'ui_do',
331
432
  'batch',
332
433
  ]
333
434
 
@@ -349,6 +450,7 @@ const DESTRUCTIVE_ACTIONS = new Set<string>([
349
450
  'key',
350
451
  'scroll',
351
452
  'ui_act',
453
+ 'ui_do',
352
454
  ])
353
455
 
354
456
  /** Observations that show the model what is on the screen, on their own. */
@@ -427,6 +529,7 @@ function requiredCapability(type: string): keyof ComputerUseCapabilities | null
427
529
  case 'ui_snapshot':
428
530
  case 'wait_for':
429
531
  case 'ui_act':
532
+ case 'ui_do':
430
533
  return 'uiTree'
431
534
  default:
432
535
  return null
@@ -454,7 +557,11 @@ function windowCaptureAvailable(host: ComputerUseHost, caps: ComputerUseCapabili
454
557
  )
455
558
  }
456
559
 
457
- function availableActions(host: ComputerUseHost, caps: ComputerUseCapabilities): ToolActionType[] {
560
+ function availableActions(
561
+ host: ComputerUseHost,
562
+ caps: ComputerUseCapabilities,
563
+ advisor?: ComputerUseAdvisor,
564
+ ): ToolActionType[] {
458
565
  const displayScreenshot = hostActionAvailable(caps, 'screenshot')
459
566
  const window = windowCaptureAvailable(host, caps)
460
567
  const screenshot = displayScreenshot || window
@@ -489,6 +596,8 @@ function availableActions(host: ComputerUseHost, caps: ComputerUseCapabilities):
489
596
  case 'wait_for':
490
597
  case 'ui_act':
491
598
  return uiTree
599
+ case 'ui_do':
600
+ return uiTree && advisor?.acts === true
492
601
  case 'batch':
493
602
  return false
494
603
  default:
@@ -523,8 +632,9 @@ function buildDescription(
523
632
  host: ComputerUseHost,
524
633
  caps: ComputerUseCapabilities,
525
634
  settings: ResolvedSettings,
635
+ advisor?: ComputerUseAdvisor,
526
636
  ): string {
527
- const available = availableActions(host, caps)
637
+ const available = availableActions(host, caps, advisor)
528
638
  const unavailable = unavailableHostActions(host, caps)
529
639
  const intro = hostActionAvailable(caps, 'screenshot')
530
640
  ? `Controls the user's desktop on a ${caps.displayServer} host: screenshots, mouse and keyboard, for GUI tasks.`
@@ -570,6 +680,10 @@ function buildDescription(
570
680
  lines.push(
571
681
  'To start a Windows program, a shell command (Start-Process <program>, or cmd.exe /c start <program>) 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.',
572
682
  )
683
+ if (available.includes('ui_do'))
684
+ lines.push(
685
+ 'ui_do {target, action, value, window_id} acts on a control named in words ("the Save button") among those the latest ui_snapshot showed: a second model chooses it and the tool says which control it acted on. When it is not sure it acts on nothing and lists the closest controls: then use ui_act on one of their refs. Use ui_act with a ref whenever you already have one.',
686
+ )
573
687
  if (available.includes('list_windows'))
574
688
  lines.push(
575
689
  'list_windows names the open windows with their ids; focus_window brings one to the front.',
@@ -668,6 +782,7 @@ const ACTION_REQUIREMENTS: Readonly<Record<ToolActionType, string>> = {
668
782
  'ui_snapshot takes an optional window_id, root (a ref to open) and query (text to find)',
669
783
  wait_for: 'wait_for needs until and match; role, window_id and timeout_ms are optional',
670
784
  ui_act: 'ui_act needs ref and action, and value for set_value',
785
+ ui_do: 'ui_do needs target (the control, in words) and action, and value for set_value',
671
786
  batch: 'batch needs actions',
672
787
  }
673
788
 
@@ -685,10 +800,11 @@ function hostModelSchema(
685
800
  host: ComputerUseHost,
686
801
  caps: ComputerUseCapabilities,
687
802
  settings: ResolvedSettings,
803
+ advisor?: ComputerUseAdvisor,
688
804
  ): Record<string, unknown> {
689
805
  // An unavailable host remains a diagnostic tool. Avoid invalid empty enums
690
806
  // on provider wires; every execution is still refused before host access.
691
- const offered = availableActions(host, caps)
807
+ const offered = availableActions(host, caps, advisor)
692
808
  const actions = offered.length > 0 ? offered : ALL_ACTIONS.filter((a) => a !== 'batch')
693
809
  const items = actions.filter((action) => !OUTSIDE_BATCH.has(action))
694
810
  const buttons = new Set<string>()
@@ -814,6 +930,30 @@ function hostModelSchema(
814
930
  type: 'string',
815
931
  description: 'The text set_value puts in the control.',
816
932
  }
933
+ if (advisor)
934
+ fields.intent = {
935
+ type: 'string',
936
+ description:
937
+ 'For ui_act: what this is for, in a few words ("save the file"). Optional; it only helps check the choice.',
938
+ }
939
+ }
940
+ if (present.includes('ui_do')) {
941
+ fields.target = {
942
+ type: 'string',
943
+ description:
944
+ 'For ui_do: the control in words, such as "the Save button" or "the name field". The tool picks the control from the latest ui_snapshot.',
945
+ }
946
+ if (!present.includes('ui_act')) {
947
+ fields.action = {
948
+ type: 'string',
949
+ enum: [...UI_ACTIONS],
950
+ description: 'What ui_do does to the control.',
951
+ }
952
+ fields.value = {
953
+ type: 'string',
954
+ description: 'The text set_value puts in the control.',
955
+ }
956
+ }
817
957
  }
818
958
  if (!forItems && present.includes('zoom'))
819
959
  fields.region = {
@@ -936,6 +1076,33 @@ function uiActLabel(
936
1076
  }
937
1077
  }
938
1078
 
1079
+ /** What a ui_do does, over the words the model named the control with. */
1080
+ function uiDoLabel(input: {
1081
+ readonly target: string
1082
+ readonly action: UiElementAction
1083
+ readonly value?: string
1084
+ }): string {
1085
+ const target = quotedText(input.target)
1086
+ switch (input.action) {
1087
+ case 'invoke':
1088
+ return `Press the control for ${target}`
1089
+ case 'set_value':
1090
+ return `Set the control for ${target} to ${quotedText(input.value ?? '')}`
1091
+ case 'toggle':
1092
+ return `Toggle the control for ${target}`
1093
+ case 'select':
1094
+ return `Select the control for ${target}`
1095
+ case 'expand':
1096
+ return `Expand the control for ${target}`
1097
+ case 'collapse':
1098
+ return `Collapse the control for ${target}`
1099
+ case 'focus':
1100
+ return `Focus the control for ${target}`
1101
+ case 'scroll_into_view':
1102
+ return `Scroll to the control for ${target}`
1103
+ }
1104
+ }
1105
+
939
1106
  /**
940
1107
  * Human activity text for one action; the raw input remains the model-facing
941
1108
  * record. `describe` names a ui_act ref's control when the tool knows it.
@@ -981,6 +1148,8 @@ function itemLabel(
981
1148
  return `Wait until ${input.until.replace('_', ' ')} ${quotedText(input.match)}`
982
1149
  case 'ui_act':
983
1150
  return uiActLabel(input, describe)
1151
+ case 'ui_do':
1152
+ return uiDoLabel(input)
984
1153
  case 'batch':
985
1154
  return batchLabel(input.actions, describe)
986
1155
  }
@@ -1322,7 +1491,8 @@ export function createComputerUseTool(
1322
1491
  }
1323
1492
  : host.capabilities
1324
1493
  const frames = new ScreenshotFrames()
1325
- const available = new Set<string>(availableActions(host, caps))
1494
+ const available = new Set<string>(availableActions(host, caps, options.advisor))
1495
+ const advisor = options.advisor
1326
1496
 
1327
1497
  // The controls of the latest ui_snapshot, by the ref the model was shown.
1328
1498
  // A ref names a control, not one reading of it: the same host ref keeps the
@@ -1330,6 +1500,8 @@ export function createComputerUseTool(
1330
1500
  // control. Whether the control is still there is the host's answer.
1331
1501
  const uiRefs = new Map<string, UiRef>()
1332
1502
  const uiRefByHost = new Map<string, string>()
1503
+ // The refs the latest reading of each window showed, so an advisor is asked about what is there now.
1504
+ const shownRefs = new Map<string, Set<string>>()
1333
1505
  let uiSnapshots = 0
1334
1506
  let uiRefCount = 0
1335
1507
  let uiSnapshotWindowId: string | undefined
@@ -1344,6 +1516,30 @@ export function createComputerUseTool(
1344
1516
  }
1345
1517
  const label = (input: BatchItem | ActionInput): string => itemLabel(input, describeUiRef)
1346
1518
 
1519
+ /** The controls of a window's latest reading that offer an action (any, when none is named), as an advisor is shown them. */
1520
+ const advisedCandidates = (
1521
+ windowId: string | undefined,
1522
+ action?: UiElementAction,
1523
+ ): ComputerUseAdvisorCandidate[] => {
1524
+ const shown = shownRefs.get(windowId ?? '')
1525
+ const out: ComputerUseAdvisorCandidate[] = []
1526
+ if (!shown) return out
1527
+ for (const ref of shown) {
1528
+ const entry = uiRefs.get(ref)
1529
+ if (!entry) continue
1530
+ const offered = entry.element.actions ?? []
1531
+ if (offered.length === 0) continue
1532
+ if (action !== undefined && !offered.includes(action)) continue
1533
+ const text = uiElementText(entry.element)
1534
+ out.push({
1535
+ ref,
1536
+ description: text.length > 80 ? `${text.slice(0, 79)}…` : text,
1537
+ })
1538
+ if (out.length >= MAX_ADVISED_CANDIDATES) break
1539
+ }
1540
+ return out
1541
+ }
1542
+
1347
1543
  const refusal = (type: string): string | null => {
1348
1544
  if (available.has(type)) return null
1349
1545
  const required = requiredCapability(type)
@@ -1678,6 +1874,14 @@ export function createComputerUseTool(
1678
1874
  if (item.action === 'set_value' && item.value === undefined)
1679
1875
  throw new StepFailure('set_value needs value, the text to put in the control')
1680
1876
  return step(async () => {
1877
+ // A second opinion on the model's own choice: asked beside the action, never waited for.
1878
+ if (advisor && item.intent !== undefined) {
1879
+ const candidates = advisedCandidates(entry.windowId)
1880
+ if (candidates.length >= 2)
1881
+ void advisor
1882
+ .pick({ intent: item.intent, candidates, chosen: item.ref })
1883
+ .catch(() => undefined)
1884
+ }
1681
1885
  let outcome: UiActResult
1682
1886
  try {
1683
1887
  outcome = await (host.uiAct as NonNullable<ComputerUseHost['uiAct']>)(
@@ -1701,6 +1905,62 @@ export function createComputerUseTool(
1701
1905
  return deliveryOutcome(outcome)
1702
1906
  })
1703
1907
  }
1908
+ case 'ui_do': {
1909
+ if (!advisor?.acts)
1910
+ throw new StepFailure('ui_do is not available here; use ui_act with a ref')
1911
+ if (item.action === 'set_value' && item.value === undefined)
1912
+ throw new StepFailure('set_value needs value, the text to put in the control')
1913
+ if (uiSnapshots === 0)
1914
+ throw new StepFailure(
1915
+ 'no ui_snapshot has been taken yet; take one, because ui_do chooses among the controls it showed',
1916
+ )
1917
+ const windowId = item.window_id ?? uiSnapshotWindowId
1918
+ const candidates = advisedCandidates(windowId, item.action)
1919
+ if (candidates.length === 0)
1920
+ throw new StepFailure(
1921
+ `no control in the latest ui_snapshot${windowId ? ` of window ${windowId}` : ''} offers ${item.action}; take a ui_snapshot and act with ui_act on a ref`,
1922
+ )
1923
+ const target = item.target
1924
+ return step(async (signal) => {
1925
+ let advice: ComputerUseAdvice | undefined
1926
+ try {
1927
+ advice = await advisor.pick({ intent: target, candidates }, signal)
1928
+ } catch {
1929
+ if (signal?.aborted) throw new StepFailure('cancelled')
1930
+ }
1931
+ const [best, next] = advice?.ranked ?? []
1932
+ const sure =
1933
+ best !== undefined &&
1934
+ best.confidence >= ADVICE_MIN_CONFIDENCE &&
1935
+ best.confidence - (next?.confidence ?? 0) >= ADVICE_MIN_MARGIN &&
1936
+ uiRefs.has(best.ref)
1937
+ if (!best || !sure) {
1938
+ const close = (advice?.ranked ?? [])
1939
+ .filter((entry) => uiRefs.has(entry.ref))
1940
+ .slice(0, 3)
1941
+ .map((entry) => describeUiRef(entry.ref))
1942
+ throw deliveryFailure({
1943
+ delivery: 'not_delivered',
1944
+ retrySafety: 'safe',
1945
+ detail: `ui_do was not sure which control is ${quotedText(target)}, so it acted on nothing.${close.length > 0 ? ` Closest: ${close.join('; ')}.` : ''} Use ui_act on one of the refs, or ui_snapshot {query: ...} to find it.`,
1946
+ })
1947
+ }
1948
+ const chosen = describeUiRef(best.ref) ?? best.ref
1949
+ const ran = await plan(
1950
+ {
1951
+ type: 'ui_act',
1952
+ ref: best.ref,
1953
+ action: item.action,
1954
+ ...(item.value !== undefined ? { value: item.value } : {}),
1955
+ },
1956
+ undefined,
1957
+ ).run(signal)
1958
+ const prefix = `chose ${chosen} for ${quotedText(target)} (${Math.round(best.confidence * 100)}% sure)`
1959
+ return typeof ran === 'string'
1960
+ ? `${prefix}: ${ran}`
1961
+ : { ...ran, note: `${prefix}: ${ran.note}` }
1962
+ })
1963
+ }
1704
1964
  }
1705
1965
  }
1706
1966
 
@@ -1795,6 +2055,11 @@ export function createComputerUseTool(
1795
2055
  }
1796
2056
  }
1797
2057
  uiSnapshotWindowId = snapshot.windowId ?? requestedWindowId
2058
+ {
2059
+ const key = uiSnapshotWindowId ?? ''
2060
+ const now = new Set(refs.keys())
2061
+ shownRefs.set(key, snapshot.region ? new Set([...(shownRefs.get(key) ?? []), ...now]) : now)
2062
+ }
1798
2063
  if (uiSnapshotWindowId !== undefined) treeUsable.set(uiSnapshotWindowId, refs.size > 0)
1799
2064
  uiSnapshotFrame = frames.latest()
1800
2065
  const window =
@@ -2320,7 +2585,7 @@ export function createComputerUseTool(
2320
2585
  if (step.item.type === 'focus_window' && windowCaptureAvailable(host, caps))
2321
2586
  postWindowId = step.item.window_id
2322
2587
  else if (
2323
- step.item.type === 'ui_act' &&
2588
+ (step.item.type === 'ui_act' || step.item.type === 'ui_do') &&
2324
2589
  uiSnapshotWindowId &&
2325
2590
  !explicitDisplayFrame &&
2326
2591
  windowCaptureAvailable(host, caps)
@@ -2377,7 +2642,7 @@ export function createComputerUseTool(
2377
2642
  let shotError: string | undefined
2378
2643
  if (looks) {
2379
2644
  try {
2380
- await sleep(settings.settleMs, signal)
2645
+ await settleAfterActions(host, postWindowId, settings.settleMs, signal)
2381
2646
  shot = await capture(postWindowId)
2382
2647
  } catch (error) {
2383
2648
  shotError = signal?.aborted ? 'cancelled' : errorText(error)
@@ -2477,9 +2742,9 @@ export function createComputerUseTool(
2477
2742
 
2478
2743
  const tool = defineTool({
2479
2744
  name: COMPUTER_USE_TOOL_NAME,
2480
- description: buildDescription(host, caps, settings),
2745
+ description: buildDescription(host, caps, settings, options.advisor),
2481
2746
  inputSchema: actionSchema,
2482
- modelInputSchema: hostModelSchema(host, caps, settings),
2747
+ modelInputSchema: hostModelSchema(host, caps, settings, options.advisor),
2483
2748
  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.`,
2484
2749
  category: 'custom',
2485
2750
  permissions: [],
@@ -568,6 +568,40 @@ export interface ComputerUseHost {
568
568
  signal?: AbortSignal,
569
569
  ): Promise<UiActResult>
570
570
 
571
+ /**
572
+ * Wait until the window the last action went to has stopped changing,
573
+ * instead of the tool sleeping a fixed {@link ComputerUseSettleRequest.budgetMs}
574
+ * before it takes the screenshot it returns. A host that is told by the
575
+ * window itself (events, not polling) answers as soon as a change was
576
+ * seen and the window has been quiet for a moment, and after the whole
577
+ * budget when nothing changed at all, so it is never slower than the fixed
578
+ * wait.
579
+ *
580
+ * Resolve `undefined` (or reject) when the host cannot say; the tool then
581
+ * sleeps the fixed time as it always did. A host that omits this method
582
+ * keeps the fixed wait.
583
+ */
584
+ settle?(
585
+ request: ComputerUseSettleRequest,
586
+ signal?: AbortSignal,
587
+ ): Promise<ComputerUseSettleResult | undefined>
588
+
571
589
  initialize?(): Promise<void>
572
590
  dispose?(): Promise<void>
573
591
  }
592
+
593
+ /** What the tool asks of {@link ComputerUseHost.settle}. */
594
+ export interface ComputerUseSettleRequest {
595
+ /** The window the batch acted in, when it acted in one; absent for display input. */
596
+ readonly windowId?: string
597
+ /** The most the tool is willing to wait, in milliseconds (its `settleMs`). */
598
+ readonly budgetMs: number
599
+ }
600
+
601
+ /** What {@link ComputerUseHost.settle} reports. */
602
+ export interface ComputerUseSettleResult {
603
+ /** How long the host waited, in milliseconds. */
604
+ readonly waitedMs: number
605
+ /** Whether the window changed at all while it waited. */
606
+ readonly changed: boolean
607
+ }