@gui-agent/operator-nutjs 实战指南:基于 nut.js 的桌面 GUI Agent 操作器
2026/9/9 12:38:49 网站建设 项目流程

@gui-agent/operator-nutjs 实战指南:基于 nut.js 的桌面 GUI Agent 操作器

【免费下载链接】UI-TARS-desktopThe Open-Source Multimodal AI Agent Stack: Connecting Cutting-Edge AI Models and Agent Infra项目地址: https://gitcode.com/GitHub_Trending/ui/UI-TARS-desktop

NutJS Operator是 UI-TARS 多模态开源仓库multimodal/gui-agent/operator-nutjs目录下提供的一个「电脑操作器」(Computer Operator):它以 nut.js(Native UI Testing)风格的底层库为驱动,把鼠标移动/点击、键盘输入、滚轮、截屏、等待等桌面原语统一封装成 GUI Agent 标准动作。本文以该包的官方 README 为骨架,结合仓库源码(NutJSOperator.ts、共享动作类型 与 操作器抽象基类)展开讲解,读完你将掌握它的安装、初始化、坐标换算、动作执行模型与关键实现细节,并能把它直接接入你自己的 GUI Agent 任务循环。

一、NutJS Operator 是什么

在 GUI Agent 的典型工作流中,Agent 模型观察屏幕截图、规划动作,随后需要有一个「操作器」真正把动作落到真实的桌面环境中。@gui-agent/operator-nutjs扮演的正是这个角色,官方 README 对其定位的描述是:

  • 面向 GUI Agent 的桌面电脑操作器;
  • 提供一套与桌面环境交互的 API;
  • 覆盖截屏、鼠标操作、键盘操作等能力。

官方文档列出的核心特性如下:

  • 截屏(Screenshot):捕获屏幕画面,并对高 DPI 显示器做正确缩放处理;
  • 鼠标操作(Mouse Operations):移动、单击、双击、右键、拖拽等;
  • 键盘操作(Keyboard Operations):键入文本、按下快捷键等;
  • 滚动(Scroll):向上 / 向下滚动;
  • 等待(Wait):等待指定时间。

从包元信息看,当前仓库中该包的版本为0.3.0,构建产物经 rslib 输出为 ESM/CJS/类型声明三份(见 package.json),并声明了@computer-use/nut-js@agent-infra/logger@gui-agent/shared(workspace 内联依赖)、jimp(图像缩放)等依赖。需要说明的是:仓库中另有一份旧版同名思路的 SDK packages/ui-tars/operators/nut-js,属于另一条产品线(@ui-tars/operator-nut-js),本文讨论的是multimodal/gui-agent下的@gui-agent/operator-nutjs

二、安装与包结构

官方文档给出三种包管理器安装方式:

npm install @gui-agent/operator-nutjs
yarn add @gui-agent/operator-nutjs
pnpm add @gui-agent/operator-nutjs

包源码结构非常精简(对应仓库路径 multimodal/gui-agent/operator-nutjs):

路径作用
src/index.ts仅一行核心导出:export { NutJSOperator } from './NutJSOperator';
src/NutJSOperator.ts操作器完整实现
examples/test-runner.ts可运行示例:初始化、截屏并把动作逐一跑在 Google 首页上
package.json提供pnpm exampletsx运行示例)与pnpm test(vitest)脚本

需要注意一点:代码中实际 import 的是@computer-use/nut-js(而非 README 中提到的 nut-tree 原仓库命名空间),它向操作器暴露了screenmousekeyboardclipboardButtonKeyPointstraightTosleep等底层能力。

三、快速开始:最小可用示例

README 提供了一个可直接照抄的最小示例:创建 logger 与操作器实例,先截一张屏,再依次执行「点击屏幕中心」「键入文本」两个动作:

import { NutJSOperator } from '@gui-agent/operator-nutjs'; import { ConsoleLogger, LogLevel } from '@agent-infra/logger'; // Create a logger const logger = new ConsoleLogger(undefined, LogLevel.DEBUG); // Create an operator instance const operator = new NutJSOperator(logger); // Take a screenshot const screenshot = await operator.screenshot(); console.log('Screenshot taken:', screenshot.status); // Execute actions const result = await operator.execute({ actions: [ { type: 'click', inputs: { point: { normalized: { x: 0.5, y: 0.5 } // Click at the center of the screen } } }, { type: 'type', inputs: { content: 'Hello, World!' } } ] });

需要特别指出的是,README 示例中的screenshot()execute()是操作器内部受保护/原始方法;在真实接入场景里更推荐使用抽象基类 operator.ts 暴露的三个带初始化保障的公共入口:

  • await operator.doInitialize():幂等初始化(内部_initialized/_initializing状态机保证只初始化一次、并发调用共享同一个 Promise);
  • await operator.doScreenshot():先确保初始化再截屏,异常时返回status: 'failed'而非抛出;
  • await operator.doExecute(params):先确保初始化再顺序执行动作列表,异常时同样折叠为status: 'failed'+errorMessage的返回结构。

官方示例 examples/test-runner.ts 就是这一写法的完整示范:operator.doInitialize()之后调用operator.doScreenshot()并把 base64 解码落盘为dumps/*.jpg,再对 Google 首页逐条执行move → click → type → hotkey(Enter) → wait → scroll → right_click → double_click → drag等动作,每一步结束后都会重新截屏留档。

四、类与 API 参考

4.1NutJSOperator

NutJSOperator继承自共享抽象基类Operator(见 operator.ts),必须实现四个抽象方法,这也构成了理解全包的索引:

抽象方法NutJS 实现位置职责
initialize(): Promise<void>NutJSOperator.ts#L39-L48抓取一帧屏幕并采集逻辑分辨率与像素密度,构建ScreenContext
supportedActions(): SupportedActionType[]NutJSOperator.ts#L50-L69声明本操作器支持的动作类型
screenContext(): ScreenContextNutJSOperator.ts#L71-L77返回屏幕上下文(未初始化时抛错)
screenshot(): Promise<ScreenshotOutput>/execute(params): Promise<ExecuteOutput>NutJSOperator.ts#L79-L125截屏与批量执行

4.2 构造函数

constructor(logger: ConsoleLogger = defaultLogger)
  • logger@agent-infra/loggerConsoleLogger实例。构造函数内部会用logger.spawn('[NutJSOperator]')派生带前缀的子 logger,所有动作日志统一携带[NutJSOperator]标记;
  • 默认值为模块级常量defaultLogger = new ConsoleLogger(undefined, LogLevel.DEBUG)

4.3screenshot()输出

返回ScreenshotOutput,字段如下(类型定义见 agents.ts):

  • base64:base64 编码的图片数据(保持物理像素尺寸);
  • contentType'image/jpeg'
  • status'success' | 'failed'
  • 失败时额外带errorMessage

4.4execute()输入输出

execute(params: ExecuteParams)接收{ actions: BaseAction[] }(可附带模型原始回复等字段,见 agents.ts),按数组顺序逐条执行动作,全部成功返回{ status: 'success' }。任何一条动作参数非法或类型不识别都会让整批失败——源码中singleActionExecutor对「必需坐标缺失」「拖拽缺起点/终点」「type 内容为空」「非法快捷键」「非法滚动方向」等场景都会直接throw

五、两种坐标系统与高 DPI 换算

NutJS Operator 的精髓之一是它屏蔽了「屏幕物理像素」与「Agent 视角归一化坐标」之间的换算,这也是 README 强调「截屏对高 DPI 显示器做正确缩放」的底层原因。

5.1Coordinates结构

共享类型 actions.ts 中定义了坐标结构:

export interface Coordinates { raw?: { x: number; y: number }; // 原始像素坐标 normalized?: { x: number; y: number }; // 归一化坐标(0–1) referenceBox?: { x1: number; y1: number; x2: number; y2: number }; referenceSystem?: 'screen' | 'window' | 'browserPage' | string; }

5.2 归一化坐标如何换算成真实坐标

源码 calculateRealCoords 实现换算规则:

  • 若提供normalizedrealX = normalized.x * screenContext.screenWidthrealY = normalized.y * screenContext.screenHeight
  • 若未提供normalized但提供raw:直接使用原始像素坐标;
  • 两者皆缺:抛出Invalid coordinates

screenWidth/screenHeight来自 initialize:截获一帧后读取pixelDensity.scaleX/scaleY,用物理分辨率除以缩放因子得到逻辑分辨率:

this._screenContext = { screenWidth: screenWithScale.width / screenWithScale.pixelDensity.scaleX, screenHeight: screenWithScale.height / screenWithScale.pixelDensity.scaleY, scaleX: screenWithScale.pixelDensity.scaleX, scaleY: screenWithScale.pixelDensity.scaleY, };

5.3 截屏的反向缩放

截屏路径则与之相反(物理像素 → 逻辑像素),见 screenshot:

  1. screen.grab()抓取原始帧;
  2. .toRGB()得到 RGB 位图与pixelDensity
  3. jimp按目标宽高(物理宽 / scaleX物理高 / scaleY)缩放;
  4. 编码为 JPEG 并 base64 输出。

最终效果是:模型看到的截图尺寸与归一化坐标使用的逻辑分辨率严格一致normalized: {x:0.5, y:0.5}永远指向截图正中心,无论 Windows/Linux 下的 125%/150% 缩放或 macOS Retina 屏如何设置。源码日志会打印screenshot: ${width}x${height}, scaleFactor: ${scaleFactor}便于核对。

六、Supported Actions 全量动作详解

6.1 声明 vs. 执行的差异

代码中有两层「动作清单」:

  1. 声明层:supportedActions() 返回 17 种:clickright_clickmiddle_clickdouble_clickmouse_downmouse_upmouse_movedragscrolltypehotkeypressreleasewaitcall_userfinished。这一层用于向 Agent 描述可用的动作空间;
  2. 执行层:singleActionExecutor 的 switch 分支。注意mouse_downmouse_upcall_user虽在声明列表中,但 switch 中并没有对应分支,落入default会抛Unsupported action——从源码结构可以推断,这类动作要么是留给上层 Agent 循环自行解释(如call_user表示"请求用户介入"),要么是尚待补全的执行能力,接入时需自行确认。

6.2 鼠标动作(README 别名表 + 源码实现)

README 给出的鼠标动作分组与别名如下,源码 switch 均予支持:

分组动作名(含别名)源码行为
移动movemove_tomouse_movehoverpoint;先做坐标换算再mouse.move(straightTo(...))
单击clickleft_clickleft_single左键单击
双击left_doubledouble_click左键双击
右键right_clickright_single右键单击
中键middle_click中键单击
拖拽left_click_dragdragselectstartend坐标

点击类统一走 handleClick:先换算并移动到目标点,sleep(100)稳定指针后执行mouse.click(button)mouse.doubleClick(button)。拖拽实现见 switch 中drag分支:移动到起点 → 停顿 100ms →mouse.drag(straightTo(new Point(endX, endY)))一气呵成,适合文本选中、拖动文件等场景。

6.3 键盘动作

  • type:键入文本。输入处理相当精细(NutJSOperator.ts#L186-L212):
    • trim(),再剥离末尾的\n或字面量\\n
    • 设置keyboard.config.autoDelayMs = 0提升键入速度;
    • Windows 平台回退方案:把文本写入系统剪贴板后模拟Ctrl+V粘贴(粘贴后恢复原剪贴板内容),以规避 nut.js 在 Windows 上的键入兼容问题;其他平台直接keyboard.type(content)
    • 若原内容以换行结尾,则补按一次Enter
    • 结束后将autoDelayMs恢复为 500ms。
  • hotkey:一次性「按下并松开」组合键;
  • press/release:仅按下或仅松开(用于组合出长按类操作)。

三者共用 getHotkeys 完成字符串到按键码的解析:

  • 按键串按空白或+分割(keyStr.split(/[\s+]/)),逐段小写后查表;
  • 内置别名表:return → Enterpage down/page up,Key.Comma、方向键arrowup/arrowdown/arrowleft/arrowright等;
  • 平台化修饰键:meta/win/command/cmd在 macOS 映射为LeftCmd、其他平台为LeftWinctrl在 macOS 映射为LeftCmd(代码如此实现,需要注意该约定)、其余平台为LeftControl
  • 别名表未命中的键会继续用 nut.jsKey枚举的 lowercase 字典兜底(所以enterescapea1等常规键名都能解析)。

因此{ type: 'hotkey', inputs: { key: 'ctrl+a' } }{ type: 'hotkey', inputs: { key: 'command+space' } }这类写法天然可用。

6.4 滚动、等待与收尾动作

  • scroll(NutJSOperator.ts#L232-L251):可选的point会先把鼠标移到该位置,再按direction(大小写不敏感)执行滚动——upmouse.scrollUp(500)downmouse.scrollDown(500),每次固定滚动 500 个刻度;left/right方向当前不被支持,会抛Unsupported scroll direction。注意:示例工程里传入的amount字段在此实现中并未被读取,属于预留字段;
  • wait:等待指定秒数(inputs.time,单位秒),未传时默认 5000ms。源码同时打印 warning「The operator should not process wait action」,暗示理想情况下等待调度应发生在 Agent 层而非操作器内;
  • finished:空操作,仅打日志,用于在动作序列结尾标识任务结束。

七、动作类型在共享层如何定义

前面表格中的动作并非只属于 NutJS Operator——它们定义在共享包@gui-agent/shared中(见 actions.ts)。NutJSOperator通过继承Operator、声明supportedActions()与整个 GUI Agent 体系对接。这种「动作 Schema 与具体执行器解耦」的设计,使得同一个动作描述可以被不同操作器解释:

  • 桌面端操作器(本文的 NutJS);
  • 移动端 ADB / 浏览器 / 浏览器底座等其他 operator 实现。

在共享动作类型中,每个动作被建模为统一三元组BaseAction { type, inputs, meta? },鼠标动作携带Coordinates点、键盘动作携带字符串内容或按键串,拖拽动作携带start/end两点。配套的ACTION_METADATA注册表(actions.ts)为每个动作标注了category(mouse/keyboard/navigation/mobile/system/wait)与语义描述,可用于自动生成系统提示词中的动作空间说明。

八、把它跑起来的完整链路(含验证建议)

综合 README、测试运行器示例 与源码,一条可自测的接入路径为:

# 在 monorepo 内安装依赖后,直接运行示例 pnpm example

示例脚本会依次完成:创建实例 →doInitialize()→ 截屏落盘dumps/screenshot-*.jpg→ 打开浏览器到 Google 首页 → 顺序执行 move/click/type/hotkey/wait/scroll/right_click/double_click/drag → 每步后截屏校验。运行环境要求操作系统当前桌面可被鼠标键盘控制(真实显示器或虚拟桌面),因为它执行的是真实的系统级输入

如果要写自己的验证脚本,建议沿用基类公共方法并做显式初始化:

const operator = new NutJSOperator(logger); await operator.doInitialize(); const ctx = await operator.getScreenContext(); console.log(ctx); // { screenWidth, screenHeight, scaleX, scaleY } const shot = await operator.doScreenshot(); // shot.base64 可直接喂给多模态大模型做下一步决策 const out = await operator.doExecute({ rawContent: 'click the search box', rawActionStrings: ['click'], actions: [{ type: 'click', inputs: { point: { normalized: { x: 0.5, y: 0.44 } } } }], });

doExecute对参数格式比较宽容(ExecuteParams本身是{ actions }ParsedGUIResponse可选字段的交叉并允许扩展字段),因此可以直接把模型解析结果原样透传。

九、总结

@gui-agent/operator-nutjs是 UI-TARS 多模态 Agent 技术栈中面向真实桌面操作系统的执行层组件:它用约 360 行实现覆盖了截屏、鼠标、键盘、滚动、等待等 GUI 原语,并通过归一化坐标与高 DPI 换算,让「模型看到什么坐标系,就点哪个点」这件事在不同缩放率的屏幕上保持正确。核心知识点可归纳为:

  • 继承Operator抽象基类,公共入口用doInitialize / doScreenshot / doExecute,自动处理幂等初始化与错误折叠;
  • Coordinates同时支持raw像素坐标与normalized0–1 归一化坐标;
  • 动作类型是共享层 Schema,动作执行支持 README 中的全部别名(如move_toleft_singleselect等);
  • type在 Windows 走剪贴板粘贴回退、hotkey/press/release通过别名表 +Key枚举解析按键串;
  • 当前实现的滚动仅支持up/downmouse_down/mouse_up/call_user处于「已声明、未落地执行」状态,接入时需留意。

如果你正在构建桌面端 GUI Agent(例如结合 apps/ui-tars 的界面能力),可以把该操作器作为动作执行后端,与@gui-agent/shared的动作解析、@agent-infra/logger的日志系统组合使用。项目采用 Apache-2.0 许可,源码路径:multimodal/gui-agent/operator-nutjs。

【免费下载链接】UI-TARS-desktopThe Open-Source Multimodal AI Agent Stack: Connecting Cutting-Edge AI Models and Agent Infra项目地址: https://gitcode.com/GitHub_Trending/ui/UI-TARS-desktop

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询