@namzu/sdk 61.3.0 → 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.
@@ -36,6 +36,12 @@ const MAX_LISTED_WINDOWS = 50;
36
36
  const MAX_UI_SNAPSHOT_CHARS = 14_000;
37
37
  /** The refs of earlier snapshots the tool still resolves for the host. */
38
38
  const MAX_HELD_UI_REFS = 4_000;
39
+ /** The least confidence in the best control for the tool to act on it. */
40
+ const ADVICE_MIN_CONFIDENCE = 0.85;
41
+ /** How far the best control must lead the next one. */
42
+ const ADVICE_MIN_MARGIN = 0.4;
43
+ /** The most controls an advisor is asked about at once. */
44
+ const MAX_ADVISED_CANDIDATES = 255;
39
45
  /**
40
46
  * Why `provider` cannot drive `computer_use`, or undefined when it can.
41
47
  *
@@ -169,6 +175,16 @@ const uiActSchema = z.object({
169
175
  ref: z.string().min(1),
170
176
  action: z.enum(UI_ACTIONS),
171
177
  value: z.string().optional(),
178
+ /** What this is for, in a few words; only read when the tool has an advisor. */
179
+ intent: z.string().min(1).max(200).optional(),
180
+ });
181
+ const uiDoSchema = z.object({
182
+ type: z.literal('ui_do'),
183
+ /** The control, in words: "the Save button", "the name field". */
184
+ target: z.string().min(1).max(200),
185
+ action: z.enum(UI_ACTIONS),
186
+ value: z.string().optional(),
187
+ window_id: z.string().min(1).optional(),
172
188
  });
173
189
  /** What a batch may carry: everything but the image-returning actions and another batch. */
174
190
  const batchItemSchema = z.discriminatedUnion('type', [
@@ -183,6 +199,7 @@ const batchItemSchema = z.discriminatedUnion('type', [
183
199
  listWindowsSchema,
184
200
  focusWindowSchema,
185
201
  uiActSchema,
202
+ uiDoSchema,
186
203
  ]);
187
204
  const withFrame = { screenshot_id: screenshotIdSchema };
188
205
  const actionSchema = z.discriminatedUnion('type', [
@@ -204,6 +221,7 @@ const actionSchema = z.discriminatedUnion('type', [
204
221
  uiSnapshotSchema,
205
222
  waitForSchema,
206
223
  uiActSchema.extend(withFrame),
224
+ uiDoSchema.extend(withFrame),
207
225
  z.object({
208
226
  type: z.literal('batch'),
209
227
  actions: z.array(batchItemSchema).min(1),
@@ -236,6 +254,7 @@ const ALL_ACTIONS = [
236
254
  'ui_snapshot',
237
255
  'wait_for',
238
256
  'ui_act',
257
+ 'ui_do',
239
258
  'batch',
240
259
  ];
241
260
  /** Observations: they change nothing on the desktop. */
@@ -255,6 +274,7 @@ const DESTRUCTIVE_ACTIONS = new Set([
255
274
  'key',
256
275
  'scroll',
257
276
  'ui_act',
277
+ 'ui_do',
258
278
  ]);
259
279
  /** Observations that show the model what is on the screen, on their own. */
260
280
  const SCREEN_OBSERVATIONS = new Set([
@@ -320,6 +340,7 @@ function requiredCapability(type) {
320
340
  case 'ui_snapshot':
321
341
  case 'wait_for':
322
342
  case 'ui_act':
343
+ case 'ui_do':
323
344
  return 'uiTree';
324
345
  default:
325
346
  return null;
@@ -337,7 +358,7 @@ function windowCaptureAvailable(host, caps) {
337
358
  typeof host.captureWindow === 'function' &&
338
359
  typeof host.executeWindow === 'function');
339
360
  }
340
- function availableActions(host, caps) {
361
+ function availableActions(host, caps, advisor) {
341
362
  const displayScreenshot = hostActionAvailable(caps, 'screenshot');
342
363
  const window = windowCaptureAvailable(host, caps);
343
364
  const screenshot = displayScreenshot || window;
@@ -370,6 +391,8 @@ function availableActions(host, caps) {
370
391
  case 'wait_for':
371
392
  case 'ui_act':
372
393
  return uiTree;
394
+ case 'ui_do':
395
+ return uiTree && advisor?.acts === true;
373
396
  case 'batch':
374
397
  return false;
375
398
  default:
@@ -403,8 +426,8 @@ function unavailableHostActions(host, caps) {
403
426
  // ---------------------------------------------------------------------------
404
427
  // Model-facing description and schema
405
428
  // ---------------------------------------------------------------------------
406
- function buildDescription(host, caps, settings) {
407
- const available = availableActions(host, caps);
429
+ function buildDescription(host, caps, settings, advisor) {
430
+ const available = availableActions(host, caps, advisor);
408
431
  const unavailable = unavailableHostActions(host, caps);
409
432
  const intro = hostActionAvailable(caps, 'screenshot')
410
433
  ? `Controls the user's desktop on a ${caps.displayServer} host: screenshots, mouse and keyboard, for GUI tasks.`
@@ -433,6 +456,8 @@ function buildDescription(host, caps, settings) {
433
456
  available.includes('key') &&
434
457
  available.includes('type_text'))
435
458
  lines.push('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.');
459
+ if (available.includes('ui_do'))
460
+ lines.push('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.');
436
461
  if (available.includes('list_windows'))
437
462
  lines.push('list_windows names the open windows with their ids; focus_window brings one to the front.');
438
463
  if (windowCaptureAvailable(host, caps))
@@ -507,6 +532,7 @@ const ACTION_REQUIREMENTS = {
507
532
  ui_snapshot: 'ui_snapshot takes an optional window_id, root (a ref to open) and query (text to find)',
508
533
  wait_for: 'wait_for needs until and match; role, window_id and timeout_ms are optional',
509
534
  ui_act: 'ui_act needs ref and action, and value for set_value',
535
+ ui_do: 'ui_do needs target (the control, in words) and action, and value for set_value',
510
536
  batch: 'batch needs actions',
511
537
  };
512
538
  function actionRequirement(action, host, caps) {
@@ -514,10 +540,10 @@ function actionRequirement(action, host, caps) {
514
540
  ? `screenshot ${hostActionAvailable(caps, 'screenshot') ? 'takes an optional' : 'requires'} window_id to capture pixels for that window`
515
541
  : ACTION_REQUIREMENTS[action];
516
542
  }
517
- function hostModelSchema(host, caps, settings) {
543
+ function hostModelSchema(host, caps, settings, advisor) {
518
544
  // An unavailable host remains a diagnostic tool. Avoid invalid empty enums
519
545
  // on provider wires; every execution is still refused before host access.
520
- const offered = availableActions(host, caps);
546
+ const offered = availableActions(host, caps, advisor);
521
547
  const actions = offered.length > 0 ? offered : ALL_ACTIONS.filter((a) => a !== 'batch');
522
548
  const items = actions.filter((action) => !OUTSIDE_BATCH.has(action));
523
549
  const buttons = new Set();
@@ -633,6 +659,28 @@ function hostModelSchema(host, caps, settings) {
633
659
  type: 'string',
634
660
  description: 'The text set_value puts in the control.',
635
661
  };
662
+ if (advisor)
663
+ fields.intent = {
664
+ type: 'string',
665
+ description: 'For ui_act: what this is for, in a few words ("save the file"). Optional; it only helps check the choice.',
666
+ };
667
+ }
668
+ if (present.includes('ui_do')) {
669
+ fields.target = {
670
+ type: 'string',
671
+ description: '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.',
672
+ };
673
+ if (!present.includes('ui_act')) {
674
+ fields.action = {
675
+ type: 'string',
676
+ enum: [...UI_ACTIONS],
677
+ description: 'What ui_do does to the control.',
678
+ };
679
+ fields.value = {
680
+ type: 'string',
681
+ description: 'The text set_value puts in the control.',
682
+ };
683
+ }
636
684
  }
637
685
  if (!forItems && present.includes('zoom'))
638
686
  fields.region = {
@@ -739,6 +787,28 @@ function uiActLabel(input, describe) {
739
787
  return `Scroll to ${target}`;
740
788
  }
741
789
  }
790
+ /** What a ui_do does, over the words the model named the control with. */
791
+ function uiDoLabel(input) {
792
+ const target = quotedText(input.target);
793
+ switch (input.action) {
794
+ case 'invoke':
795
+ return `Press the control for ${target}`;
796
+ case 'set_value':
797
+ return `Set the control for ${target} to ${quotedText(input.value ?? '')}`;
798
+ case 'toggle':
799
+ return `Toggle the control for ${target}`;
800
+ case 'select':
801
+ return `Select the control for ${target}`;
802
+ case 'expand':
803
+ return `Expand the control for ${target}`;
804
+ case 'collapse':
805
+ return `Collapse the control for ${target}`;
806
+ case 'focus':
807
+ return `Focus the control for ${target}`;
808
+ case 'scroll_into_view':
809
+ return `Scroll to the control for ${target}`;
810
+ }
811
+ }
742
812
  /**
743
813
  * Human activity text for one action; the raw input remains the model-facing
744
814
  * record. `describe` names a ui_act ref's control when the tool knows it.
@@ -779,6 +849,8 @@ function itemLabel(input, describe) {
779
849
  return `Wait until ${input.until.replace('_', ' ')} ${quotedText(input.match)}`;
780
850
  case 'ui_act':
781
851
  return uiActLabel(input, describe);
852
+ case 'ui_do':
853
+ return uiDoLabel(input);
782
854
  case 'batch':
783
855
  return batchLabel(input.actions, describe);
784
856
  }
@@ -993,13 +1065,16 @@ export function createComputerUseTool(host, options = {}) {
993
1065
  }
994
1066
  : host.capabilities;
995
1067
  const frames = new ScreenshotFrames();
996
- const available = new Set(availableActions(host, caps));
1068
+ const available = new Set(availableActions(host, caps, options.advisor));
1069
+ const advisor = options.advisor;
997
1070
  // The controls of the latest ui_snapshot, by the ref the model was shown.
998
1071
  // A ref names a control, not one reading of it: the same host ref keeps the
999
1072
  // same number in every snapshot, and a number is never reused for another
1000
1073
  // control. Whether the control is still there is the host's answer.
1001
1074
  const uiRefs = new Map();
1002
1075
  const uiRefByHost = new Map();
1076
+ // The refs the latest reading of each window showed, so an advisor is asked about what is there now.
1077
+ const shownRefs = new Map();
1003
1078
  let uiSnapshots = 0;
1004
1079
  let uiRefCount = 0;
1005
1080
  let uiSnapshotWindowId;
@@ -1013,6 +1088,31 @@ export function createComputerUseTool(host, options = {}) {
1013
1088
  return entry ? `${uiElementText(entry.element)} (${ref})` : undefined;
1014
1089
  };
1015
1090
  const label = (input) => itemLabel(input, describeUiRef);
1091
+ /** The controls of a window's latest reading that offer an action (any, when none is named), as an advisor is shown them. */
1092
+ const advisedCandidates = (windowId, action) => {
1093
+ const shown = shownRefs.get(windowId ?? '');
1094
+ const out = [];
1095
+ if (!shown)
1096
+ return out;
1097
+ for (const ref of shown) {
1098
+ const entry = uiRefs.get(ref);
1099
+ if (!entry)
1100
+ continue;
1101
+ const offered = entry.element.actions ?? [];
1102
+ if (offered.length === 0)
1103
+ continue;
1104
+ if (action !== undefined && !offered.includes(action))
1105
+ continue;
1106
+ const text = uiElementText(entry.element);
1107
+ out.push({
1108
+ ref,
1109
+ description: text.length > 80 ? `${text.slice(0, 79)}…` : text,
1110
+ });
1111
+ if (out.length >= MAX_ADVISED_CANDIDATES)
1112
+ break;
1113
+ }
1114
+ return out;
1115
+ };
1016
1116
  const refusal = (type) => {
1017
1117
  if (available.has(type))
1018
1118
  return null;
@@ -1262,6 +1362,14 @@ export function createComputerUseTool(host, options = {}) {
1262
1362
  if (item.action === 'set_value' && item.value === undefined)
1263
1363
  throw new StepFailure('set_value needs value, the text to put in the control');
1264
1364
  return step(async () => {
1365
+ // A second opinion on the model's own choice: asked beside the action, never waited for.
1366
+ if (advisor && item.intent !== undefined) {
1367
+ const candidates = advisedCandidates(entry.windowId);
1368
+ if (candidates.length >= 2)
1369
+ void advisor
1370
+ .pick({ intent: item.intent, candidates, chosen: item.ref })
1371
+ .catch(() => undefined);
1372
+ }
1265
1373
  let outcome;
1266
1374
  try {
1267
1375
  outcome = await host.uiAct(entry.hostRef, item.action, item.value, callSignal);
@@ -1280,6 +1388,56 @@ export function createComputerUseTool(host, options = {}) {
1280
1388
  return deliveryOutcome(outcome);
1281
1389
  });
1282
1390
  }
1391
+ case 'ui_do': {
1392
+ if (!advisor?.acts)
1393
+ throw new StepFailure('ui_do is not available here; use ui_act with a ref');
1394
+ if (item.action === 'set_value' && item.value === undefined)
1395
+ throw new StepFailure('set_value needs value, the text to put in the control');
1396
+ if (uiSnapshots === 0)
1397
+ throw new StepFailure('no ui_snapshot has been taken yet; take one, because ui_do chooses among the controls it showed');
1398
+ const windowId = item.window_id ?? uiSnapshotWindowId;
1399
+ const candidates = advisedCandidates(windowId, item.action);
1400
+ if (candidates.length === 0)
1401
+ throw new StepFailure(`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`);
1402
+ const target = item.target;
1403
+ return step(async (signal) => {
1404
+ let advice;
1405
+ try {
1406
+ advice = await advisor.pick({ intent: target, candidates }, signal);
1407
+ }
1408
+ catch {
1409
+ if (signal?.aborted)
1410
+ throw new StepFailure('cancelled');
1411
+ }
1412
+ const [best, next] = advice?.ranked ?? [];
1413
+ const sure = best !== undefined &&
1414
+ best.confidence >= ADVICE_MIN_CONFIDENCE &&
1415
+ best.confidence - (next?.confidence ?? 0) >= ADVICE_MIN_MARGIN &&
1416
+ uiRefs.has(best.ref);
1417
+ if (!best || !sure) {
1418
+ const close = (advice?.ranked ?? [])
1419
+ .filter((entry) => uiRefs.has(entry.ref))
1420
+ .slice(0, 3)
1421
+ .map((entry) => describeUiRef(entry.ref));
1422
+ throw deliveryFailure({
1423
+ delivery: 'not_delivered',
1424
+ retrySafety: 'safe',
1425
+ 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.`,
1426
+ });
1427
+ }
1428
+ const chosen = describeUiRef(best.ref) ?? best.ref;
1429
+ const ran = await plan({
1430
+ type: 'ui_act',
1431
+ ref: best.ref,
1432
+ action: item.action,
1433
+ ...(item.value !== undefined ? { value: item.value } : {}),
1434
+ }, undefined).run(signal);
1435
+ const prefix = `chose ${chosen} for ${quotedText(target)} (${Math.round(best.confidence * 100)}% sure)`;
1436
+ return typeof ran === 'string'
1437
+ ? `${prefix}: ${ran}`
1438
+ : { ...ran, note: `${prefix}: ${ran.note}` };
1439
+ });
1440
+ }
1283
1441
  }
1284
1442
  };
1285
1443
  /** Read one window's controls, number the ones the model can act on, and show them. */
@@ -1364,6 +1522,11 @@ export function createComputerUseTool(host, options = {}) {
1364
1522
  }
1365
1523
  }
1366
1524
  uiSnapshotWindowId = snapshot.windowId ?? requestedWindowId;
1525
+ {
1526
+ const key = uiSnapshotWindowId ?? '';
1527
+ const now = new Set(refs.keys());
1528
+ shownRefs.set(key, snapshot.region ? new Set([...(shownRefs.get(key) ?? []), ...now]) : now);
1529
+ }
1367
1530
  if (uiSnapshotWindowId !== undefined)
1368
1531
  treeUsable.set(uiSnapshotWindowId, refs.size > 0);
1369
1532
  uiSnapshotFrame = frames.latest();
@@ -1835,7 +1998,7 @@ export function createComputerUseTool(host, options = {}) {
1835
1998
  const outcome = typeof ran === 'string' ? { note: ran } : ran;
1836
1999
  if (step.item.type === 'focus_window' && windowCaptureAvailable(host, caps))
1837
2000
  postWindowId = step.item.window_id;
1838
- else if (step.item.type === 'ui_act' &&
2001
+ else if ((step.item.type === 'ui_act' || step.item.type === 'ui_do') &&
1839
2002
  uiSnapshotWindowId &&
1840
2003
  !explicitDisplayFrame &&
1841
2004
  windowCaptureAvailable(host, caps))
@@ -1984,9 +2147,9 @@ export function createComputerUseTool(host, options = {}) {
1984
2147
  const capturesScreen = (input) => observes && capturesScreenInput(input, settings.screenshotAfterActions);
1985
2148
  const tool = defineTool({
1986
2149
  name: COMPUTER_USE_TOOL_NAME,
1987
- description: buildDescription(host, caps, settings),
2150
+ description: buildDescription(host, caps, settings, options.advisor),
1988
2151
  inputSchema: actionSchema,
1989
- modelInputSchema: hostModelSchema(host, caps, settings),
2152
+ modelInputSchema: hostModelSchema(host, caps, settings, options.advisor),
1990
2153
  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.`,
1991
2154
  category: 'custom',
1992
2155
  permissions: [],