mcpField
J
jev-desktop

jev-desktop

github.com/lahfirAgent 工作流1.8k40 次安装今天更新
代码仓库

关于 jev-desktop

jev-desktop 是通过读取屏幕来操作桌面应用的技能,可逐步选择并执行 UI 操作,或持续运行直至达成目标。它面向需要验证操作、使用指定文本并按置信度设防的 Agent 流程;需克隆 agent-desktop 仓库并提供 TYPESAFE_API_KEY。

主要功能

  1. 01运行目标驱动或单步 UI 操作
  2. 02读取屏幕而不将完整树放入上下文
  3. 03依据元素声明的能力匹配操作
  4. 04文本输入必须提供文本
  5. 05采用置信度阈值并检查破坏性操作
  6. 06用 --no-values 控制字段值共享

适用场景

  1. 01打开文件夹并操作桌面应用
  2. 02将调用方提供的文本输入文档
  3. 03检查屏幕并执行单个确认操作
  4. 04限制发送到配置端点的字段值

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/lahfir/agent-desktop --skill jev-desktop

安装后在 Claude Code 中输入/jev-desktop确认已加载。

详细介绍

jev-desktop

此技能需要克隆 agent-desktop 仓库(脚本位于 scripts/jev/ 中,不会随任何已安装的软件包一同发布),并且需要 TYPESAFE_API_KEY。

两个入口读取同一屏幕。run.mjs 接收一个目标并持续操作,直到目标达成。act.mjs 只执行一个步骤,然后把计划交还给你。两者都不会将界面树放入上下文。

循环

node scripts/jev/run.mjs --app Finder "open the Applications folder"
node scripts/jev/run.mjs --app TextEdit --cursor --text "A line to write." "write the supplied sentence into the document"

每一轮都会读取屏幕,在同一个请求中询问一项操作及其目标,执行操作,然后再次读取。每轮会打印一行 JSON,停止时也会打印一行(此处显示的是精简内容)。

{"turn":{"step":1,"operation":"CLICK","target":"treeitem \"Applications\"",
         "confidence":1,"ok":true,"delivery":"delivered_verified","changed":true}}
{"stop":"done","confidence":0.96}

操作包括 CLICK、TYPE_TEXT、CHECK、UNCHECK、EXPAND、COLLAPSE、SCROLL、DRILL、WIDEN、WAIT、DONE 和 BLOCKED。只会提供屏幕上有对象可以接受的操作:对于报告还有更多内容的区域,才会提供 DRILL;只有执行 DRILL 后才会提供 WIDEN。SCROLL 始终向下滚动。WAIT 的 delivery 为 waited,DRILL 和 WIDEN 的 delivery 为 looked。

每项操作只询问一次目标。 click_target 从标记了 Click 的元素中选取,type_text_target 从标记了 SetValue 的元素中选取,其他目标也依此类推。只会读取与所选操作匹配的首个结果,因此目标绝不会与作用于它的操作不兼容。

查看也是一项操作。 首先使用 --skeleton 读取窗口,因此,首次查看密集文档的成本与查看小面板相同。被截断的区域会报告其中包含多少内容,而 DRILL 会将其固定为后续轮次的根区域。WIDEN 会恢复整个窗口。

使用 CHECK 和 UNCHECK,而非切换操作。 两者都是幂等操作:已处于目标状态的复选框会保持原样。

文本由你提供。 此处不会自行填写任何值,因此运行过程中绝不会在屏幕上输入你未选择的字符串。每个值传一次 --text,它们会按顺序使用;也可以将函数作为 text 选项传入,函数会针对每个字段及其描述进行询问。没有剩余值可用时,系统根本不会提供 TYPE_TEXT,因此运行会停止,而不会凭空编造内容。

文本会通过应用接受的方式输入。 直接写入值(set-value)是一次经过验证的调用。只有当应用拒绝该操作,且拒绝信息表明可以安全重试时,才会通过剪贴板输入值并使用 cmd+v 粘贴(仅限 macOS)。运行结束时会恢复剪贴板内容;如果剪贴板中包含非文本内容,则会清空剪贴板。不会逐字符按键输入:这种方式会丢失字符和大写字母。

哪些内容会离开设备。 每一轮都会向决策端点发送屏幕描述(默认是 TypeSafe 的 api.typesafe.ai,也可以是 TYPESAFE_BASE_URL 指定的地址):每个元素的角色、其无障碍名称或描述、其值中最多六十个字符、其状态、窗口标题以及近期操作;不包括提供的文本和详细执行证据。这足以将私人文档或已填表单的内容发送到配置的远程服务。agent-desktop 会预先屏蔽安全文本字段,这些字段永远不会进入请求。对任一脚本传入 --no-values,也可以屏蔽其他所有字段中的值;但定位会更困难,因为值往往是区分两个无名称行的唯一依据。默认情况下会包含值。run.mjs 会在操作轮次和后续的近期操作记录中,隐藏明确提供的字符串,包括后续目标名称或执行证据中再次出现这些字符串的情况。实时屏幕描述则单独处理:如果应用显示了这些文本,根据上述设置,其无障碍名称或字段值仍可能发送到远程服务。act.mjs 会在标准输出中保留准确的可执行 argv,因此其中可能包含通过 --text 提供的值;标准输出也可能包含无障碍名称和元素路径。请谨慎选择端点和输出位置,并考虑这些信息的暴露风险。 难以撤销的步骤不会悄然执行。 普通步骤要求目标置信度达到 0.70;被评为具有破坏性的步骤(0.50 或更高)要求达到 0.90;低于 0.55 时则不会执行任何操作。未达到门槛时,本次运行会停止,并指出原本会操作的候选对象,让你来决定,而不是由它代劳。act.mjs 会在唯一一次请求中获取风险评级。run.mjs 会通过第二次模型调用评估风险(计入 80 次上限),但只针对目标置信度为 0.70 到 0.90 的目标,因此默认情况下,目标置信度达到 0.90 或更高的破坏性步骤不会进行评级。如果该调用没有返回数字,该步骤将被视为具有破坏性。向 run.mjs 或 act.mjs 传入 --confirm-destructive,即可在每个被评为具有破坏性的步骤前停止并请求确认,无论其置信度如何。

--cursor 让运行过程清晰可见。 它会启动会话,显示一个光标,在执行操作前移动到相应元素,并在结束时关闭光标。如果无法启动会话,运行过程会继续,但不显示光标。光标会绘制在元素所在的位置,因此请将应用置于终端前方,否则光标会出现在终端后面。

遇到 DONE、BLOCKED、置信度不足以承担相应风险、执行 40 次操作(WAIT、DRILL 和 WIDEN 也计入)、进行 80 次模型调用(风险评级也计入),或连续三轮都没有任何变化(WAIT 轮次不计)时,运行会停止。如果某项操作失败,也会以 action_failed 停止;如果屏幕上没有任何元素提供可执行的操作,则会以 nothing on this screen can be acted on 停止。

如果屏幕上可操作的元素多于一次选择所能承载的数量,请求和报告的轮次中都会注明(truncated),并会告知策略在某个区域内查找,而不是将目标判定为无法实现。

如果你已知道目标区域,可以使用 --root @ref 从该区域内部开始。

一次一步

目标、计划和记忆由你保留。你发送一句话,收到一个小对象。

node scripts/jev/act.mjs --app TextEdit --execute \
  --text "Morning over the dock." "type this text into the main writing area"
{
  "ok": true,
  "intent": "type this text into the main writing area",
  "app": "TextEdit", "window": "Untitled 3", "window_id": "w-4521", "surface": "sheet",
  "element": { "ref": "@s1:e139", "role": "textfield", "name": "Save As:",
               "where": "sheet \"save\" > group" },
  "command": "set-value",
  "argv": ["set-value", "@s1:e139", "poem.txt"],
  "decision": "act",
  "why": null,
  "confidence": { "target": 0.99, "command": 0.88 },
  "gates": { "present": 0.94, "destructive": 0.31, "needs_text": 0.87 },
  "runner_up": [{ "ref": "@s1:e137", "what": "textfield", "p": 0.02 }],
  "notes": [],
  "executed": { "ok": true, "delivery": "delivered_verified" }
}

调用方式

标志含义
--app <name>必需。
--window-id <id>读取应用的一个窗口,窗口 ID 由 agent-desktop list-windows --app <name> 报告。仅当应用有多个可见窗口且没有窗口处于焦点状态时才需要,否则会失败并返回 AMBIGUOUS_TARGET。运行期间会固定在该窗口:运行过程中在其他位置打开的窗口不会被读取。如果操作后该窗口关闭,run.mjs 会报告该轮并以 window_closed 停止。--root 或打开窗口之外的界面会返回 root_outside_window 或 surface_outside_window(在 run.mjs 中会停止,在 act.mjs 中则是 error)。每次 run.mjs 停止时,都会在 screen.window_id 中报告所读取的窗口。run.mjs 也接受此参数。
--no-values不向决策请求提供可访问的字段值。两个脚本都支持此选项;默认会包含字段值。
--execute仅适用于 act.mjs。当 decision 为 act 时执行命令。不传入此选项则不会执行任何操作。
--text "…"意图所需的文本。Jev 返回选项,而不是字符串。run.mjs 可以重复接受此参数;act.mjs 只接受一个值。
--root @ref在单个容器内解析,而不是在整个窗口中解析。
--bin <path>仅适用于 act.mjs。agent-desktop 可执行文件。默认使用发布版本构建,然后使用 PATH。run.mjs 使用 AGENT_DESKTOP_BIN,默认值相同。

环境变量:必需设置 TYPESAFE_API_KEY(未设置时两个脚本都会失败,并显示 TYPESAFE_API_KEY unset)。TYPESAFE_BASE_URL 和 TYPESAFE_MODEL 见下文。JEV_VERIFY_MS 设置粘贴内容回读的截止时间,单位为毫秒(默认值为 2000,范围为 0 到 60000)。

argv 是确切的 agent-desktop 命令。double-click 和 hover 会携带 --headed;scroll 始终向下滚动。

将意图表述为操作,而不是元素。 "type this text into the main writing area" 会解析为 type。"the main writing area" 没有指定操作,可能会解析为 focus。请用人们通常会使用的方式描述目标,例如 "the field holding the name the file will be saved under",而不是照搬树中的名称。

读取 decision

decision含义你要做什么
act有一个元素明确匹配,且置信度达到此操作风险对应的门槛。无需操作。使用 --execute 时,操作已经执行。
confirm匹配看似合理,但置信度未达到门槛。询问用户,或重新表述意图后再次调用。
abstainJev 回答了 none,该元素可能不在当前屏幕上,或有两个元素同样符合。当前屏幕并非你所想的界面。打开你需要的界面,然后再次调用。
needs_text命令需要文本,但未提供文本。使用 --text 再次调用。

除 act(此时为 null)外,每个 decision 的 why 都会用一句话说明原因。runner_up 会显示它考虑过的其他选项,通常足以判断是屏幕不对,还是意图含糊。

脚本决定的内容,而非 Jev 决定的内容

  • 风险决定门槛。 回答说明要做什么;置信度说明是否执行。普通操作需要达到 0.70。对于 Jev 评为 destructive 且置信度达到 0.50 的操作,门槛为 0.90:写入文件、删除、发送、确认警告。低于 0.55 时,任何操作都不会执行。
  • 命令会与元素进行校验。 两个问题会并行回答,互不知晓对方的答案,因此代码会将选定的动词与元素声明支持的操作进行校验。只读组合框使用 click,绝不使用 set-value。如果动词不是文本操作,但提供了 --text 内容,则会修正动词,因为调用方提供了文本,这一点是 Jev 不知道的。
  • 打开的界面优先。 当工作表、警报、菜单或弹出框打开时,脚本会先读取窗口,再读取该界面,并提供界面中的元素,而非窗口中的元素。在窗口树中,这些元素带有 offscreen,会被全部丢弃。
  • 不会提供已禁用、隐藏或没有操作的元素,也不会提供树项目内的单元格(旁边的行才是操作对象)。其他所有元素都会提供,包括未命名的行:行会根据其所持有的值来区分,而 Choice 使用完整列表比使用精简列表效果更好。

请求的构成方式

act.mjs 会发送一个包含五个问题的调用。问题会并行回答,因此推测性问题只会消耗令牌,不会增加延迟。

问题类型询问内容
targetchoice意图指向哪个元素,以及 none。
commandchoice要求使用 16 种交互动词中的哪一种。
presentnoul该元素是否在当前屏幕上。
destructivenoul此操作是否难以撤销。
needs_textnoul是否需要调用方提供文本。

Choice 最多接受 255 个选项,因此一次最多可传入 254 个元素(两个脚本均如此)。当第一次运行 act.mjs 的结果低于 0.70 时,会使用更丰富的描述重新询问排名前五的选项。如果屏幕上的元素超过 254 个,notes 中会说明这一点;请使用 --root @ref。

通过 OpenRouter 做出决策

决策端点为 TYPESAFE_BASE_URL,默认使用 TypeSafe 原生端点 https://api.typesafe.ai/v1/systemone。配置的端点必须使用 HTTPS;只有 localhost 或字面形式的回环 IP 才允许使用 HTTP,这样本地测试路由器仍可使用。URL 凭据会被拒绝,重定向也会被安全地拦截,以免将 bearer 密钥转发到其他端点。将端点指向 OpenRouter 的 Decisions 路由,即可使用 OpenRouter 密钥作为 TYPESAFE_API_KEY:

TYPESAFE_BASE_URL=https://openrouter.ai/api/alpha/decisions \
TYPESAFE_MODEL=typesafe/jev-1.13 \
TYPESAFE_API_KEY=sk-or-... \
node scripts/jev/run.mjs --app Finder "open the Applications folder"

TYPESAFE_MODEL 可以保持默认值 jev-latest:通过 OpenRouter 调用时,不带前缀的名称会作为已注册的 ~typesafe/<name> 别名发送。也可以固定使用某个路由 ID,例如 typesafe/jev-1.13。不要设置为 typesafe/jev-latest:OpenRouter 会返回 400 Model does not exist。OpenRouter 的 Decisions 端点目前处于 alpha 阶段,格式可能会发生变化。

已知限制

  • act.mjs 每次调用时只读取一次屏幕。run.mjs 会在每项操作前后读取屏幕,但 DRILL 或 WIDEN 后会跳过读取。未固定区域时,如果有工作表、警报、菜单或弹出层处于打开状态,每次读取都会额外拍摄一次快照。在内容密集的应用中,一次读取约需 3 s;使用 --root 则快得多。
  • act.mjs 不会重试:遇到 429 时调用即告结束。run.mjs 会对 429、503 和 529 重试两次,间隔分别为 500 ms 和 1000 ms,然后才报错。
  • 不支持保持输入状态,因此无法持续按键,也无法按住拖动。请参阅 crates/core/src/commands/input_hold_policy.rs。
  • 此处不会解析 press。它不需要元素,因此请直接发送。

文件

文件内容
scripts/jev/policy.mjs操作、屏幕描述方式以及何时停止。此处不会接触任何应用。
scripts/jev/screen.mjs命令可以访问哪些元素、哪个界面处于打开状态,以及如何描述元素。两个入口共用此文件。
scripts/jev/desktop.mjs唯一与 agent-desktop 通信的代码。
scripts/jev/run.mjs循环及其命令行。
scripts/jev/act.mjs单步解析器。

检查

run.mjs 中的 WAIT 会暂停 250 ms,然后重新读取屏幕。操作失败时会立即以 action_failed 状态停止运行,并保留错误代码和投递状态,且绝不会重复执行。焦点设置、剪贴板写入或粘贴失败时,也会以相同方式结束运行。如果某个二进制文件的 set-value 失败且未报告错误处置结果,则会报错 BINARY_TOO_OLD。

只有当运行在 done 状态停止时,run.mjs 才会以 0 退出;其他所有停止情况,以及任何错误({"ok":false,"error":…}),都会以 1 退出;用法错误则以 2 退出。除非执行的命令失败或未执行(当 decision 不是 act 时,executed.code 为 NOT_EXECUTED),否则 act.mjs 会以 0 退出;错误以 1 退出,用法错误以 2 退出。

粘贴后会回读字段。如果字段中没有请求的文本,运行会以 TEXT_VERIFICATION_FAILED 停止,且不会再次输入。回读会比较完全相同的字符串,因此即使文本已写入,只要字段会规范化文本(删除首尾空格、更改大小写或换行符),也会报告失败。回读会重试,直到达到 JEV_VERIFY_MS 截止时间;之后若仍未通过,则报错。

node scripts/jev/act.test.mjs
node scripts/jev/run.test.mjs
node scripts/jev/run.integration.test.mjs
node scripts/jev/evidence.test.mjs

调用方可通过轮次保留原生的 steps、post_state 和 details;这些内容不会纳入模型历史记录。经验证的 AlreadyInState 步骤若 outcome: "skipped" 且 delivery: "not_delivered",表示无需输入。后置条件失败时会保留 ok: false 和任何嵌套的操作证据。

评论

支持 Markdown