jev-desktop について
jev-desktop は、画面を読み取りながらデスクトップアプリを操作するスキルです。UI 操作を一つずつ選んで実行する方法と、目標まで実行する方法があり、検証や指定テキスト、確信度に基づく安全策を必要とするエージェント向けです。agent-desktop リポジトリのクローンと TYPESAFE_API_KEY が必要です。
主な機能
- 01目標駆動または単一ステップの UI 操作
- 02画面全体のツリーをコンテキストに入れずに読み取り
- 03要素が示す操作能力に基づくアクション選択
- 04テキスト入力時の値の指定必須
- 05確信度しきい値と破壊的操作の確認
- 06--no-values によるフィールド値共有の制御
活用シーン
- 01フォルダーを開いてデスクトップアプリを操作
- 02指定されたテキストを文書に入力
- 03画面を調べて単一の操作を実行
- 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 が必要です。
同じ画面読み取りを使う2つのエントリーポイントがあります。run.mjs は目標を受け取り、達成されるまで実行します。act.mjs は1ステップを実行し、計画をあなたに引き渡します。どちらもツリーをコンテキストに含めません。
ループ
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"
各ターンでは画面を読み取り、1つのリクエストで操作とその操作の対象を1つずつ尋ね、実行してから再び読み取ります。各ターンにつきJSONを1行出力し、停止時にも1行出力します(以下はトリミング済みです)。
{"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 はさらに内容があると報告された領域に対して、WIDEN は DRILL の後にのみ提示されます。SCROLL は常に下へスクロールします。delivery は WAIT の場合は waited、DRILL と WIDEN の場合は looked になります。
対象は操作ごとに1回だけ尋ねられます。 click_target は Click を提示する要素から、type_text_target は SetValue を提示する要素から選ばれ、同様に選択されます。選ばれた操作と一致する先頭の要素のみが読み取られるため、対象がその要素に適用する操作と合わないことはありません。
確認も操作です。 ウィンドウはまず --skeleton を使って読み取られるため、内容の密なドキュメントでも、小さなパネルと最初の読み取りコストは同じです。途中で切り取られた領域は、どれだけの内容を含むかを報告し、DRILL によって後続のターンのルートに固定されます。WIDEN を実行すると、ウィンドウ全体に戻ります。
切り替え操作ではなく、CHECK と UNCHECK を使います。 どちらも冪等です。すでに希望する状態になっているボックスは、その状態のままになります。
入力するテキストはあなたが指定します。 ここでは値を作成しないため、指定していない文字列が画面に入力されることはありません。値ごとに --text を指定すると順番に消費されます。または、text オプションに関数を渡すと、各フィールドについてその説明とともに尋ねられます。入力する値が残っていない場合は TYPE_TEXT が提示されないため、値を勝手に作るのではなく実行が停止します。
テキストはアプリケーションが受け付ける方法で入力されます。 値の直接書き込み(set-value)は、検証済みの1回の呼び出しです。アプリケーションがそれを拒否し、拒否の内容から再試行しても安全だと分かる場合に限り、値はクリップボード経由で入力され、cmd+v で貼り付けられます(macOS のみ)。実行の終了時にクリップボードは元に戻されますが、テキスト以外の内容が入っていた場合は空になります。1文字ずつキーを押す方法は使いません。この方法では文字が抜けたり、大文字が失われたりするためです。
マシンから送信される内容。 各ターンでは、画面の説明が判定エンドポイント(デフォルトでは TypeSafe の api.typesafe.ai、または TYPESAFE_BASE_URL で指定されたエンドポイント)に送信されます。送信内容は、各要素のロール、アクセシブルな名前または説明、保持している値の最大60文字、状態、ウィンドウタイトル、最近の操作です。指定されたテキストと詳細な実行証拠は含まれません。これは、プライベートなドキュメントの内容や入力済みフォームを、設定されたリモートサービスに送信するのに十分な情報です。セキュアなテキストフィールドは agent-desktop によってすでに除外されており、リクエストに含まれることはありません。他のすべてのフィールドの値も除外するには、いずれかのスクリプトに --no-values を渡してください。ただし、値が名前のない2つの行を見分ける唯一の手掛かりであることも多いため、対象の指定が難しくなります。デフォルトでは値を含めます。run.mjs は、操作のターンと、その後の最近の操作履歴から、明示的に指定した文字列を削除します。後続の対象名や実行証拠に同じ文字列が繰り返し含まれる場合も同様です。ライブ画面の説明は別に扱われます。アプリケーションにそのテキストが表示されている場合、上記の設定に応じて、アクセシブルな名前やフィールドの値がリモートサービスに送信されることがあります。act.mjs は、実行可能ファイルの argv をそのまま標準出力に出すため、このフィールドには --text で指定した値が含まれる可能性があります。標準出力にはアクセシブルな名前や要素のパスが含まれることもあります。これらの情報が公開される可能性を考慮し、エンドポイントと出力先を選んでください。
元に戻しにくい操作は、黙って実行されません。 通常の操作では対象の信頼度が 0.70 必要です。破壊的と評価される操作(0.50 以上)では 0.90 必要で、0.55 未満では何も実行されません。基準を満たさない場合、実行は停止し、操作対象だった候補を提示するので、システムに任せず自分で判断できます。act.mjs は 1 回のリクエストで評価を取得します。run.mjs は、対象の信頼度が 0.70 以上 0.90 未満の場合に限り、2 回目のモデル呼び出しでリスクを評価します(80 回の上限に含まれます)。そのため、デフォルトでは対象の信頼度が 0.90 以上の破壊的な操作は、評価なしで実行されます。この呼び出しが数値を返さない場合、その操作は破壊的な操作として扱われます。run.mjs または act.mjs に --confirm-destructive を渡すと、信頼度に関係なく、破壊的と評価された操作ごとに確認のため停止します。
--cursor を指定すると、実行の様子を確認できます。 セッションを開始し、操作が実行される前に各要素へ移動するカーソルを表示して、最後にオフにします。セッションを開始できない場合、カーソルなしで実行を続けます。カーソルは要素がある位置に表示されるため、アプリケーションをターミナルの前面に表示してください。そうしないと、カーソルがターミナルの背後に表示されます。
DONE、BLOCKED、リスクに対して信頼度が低すぎる場合、操作が 40 回に達した場合(WAIT、DRILL、WIDEN も数えます)、モデル呼び出しが 80 回に達した場合(リスク評価も数えます)、または何も変わらないターンが 3 回連続した場合に停止します(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> | agent-desktop list-windows --app <name> が報告する ID で、アプリのウィンドウを一つ読み取ります。表示されているウィンドウが複数あり、そのどれにもフォーカスされていない場合にのみ必要です。指定しないと 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 | 1 つの要素が明確に一致し、このアクションのリスクに対する基準を信頼度が上回っています。 | 何もする必要はありません。--execute を指定していれば、すでに実行されています。 |
confirm | 一致している可能性はありますが、基準を下回っています。 | ユーザーに確認するか、意図を言い換えて再度呼び出します。 |
abstain | Jev が none と回答したか、その要素はおそらくこの画面にないか、2 つの要素が同程度に当てはまります。 | 想定している画面ではありません。必要な画面を開いてから、再度呼び出します。 |
needs_text | コマンドにテキストが必要ですが、指定されていません。 | --text を指定して再度呼び出します。 |
why には、act を除く各 decision の理由が 1 文で示されます
(act の場合は null です)。runner_up には他に検討された候補が
示されます。通常、これを見るだけで、画面が違うのか意図が曖昧なのかが分かります。
スクリプトが決めること、Jev が決めないこと
- リスクに応じて基準が変わります。 回答は何をするかを示し、信頼度は実行するかどうかを示します。
通常のアクションに必要な信頼度は 0.70 です。Jev が
destructiveと評価するアクションは、 0.50 以上でも 0.90 が必要です。ファイルへの書き込み、削除、送信、警告の確認などが該当します。 0.55 未満では、何も実行されません。 - コマンドと要素の整合性を確認します。 2 つの質問は並行して回答され、互いの回答は参照しないため、
コードが選択された動詞と要素の提示するアクションを照合します。読み取り専用のコンボボックスには
clickを使い、set-valueは使いません。--textのペイロードにテキスト以外の動詞が指定されている場合は、 呼び出し元がテキストを指定したことを Jev が知らない証拠として、動詞を修正します。 - 開いているサーフェスが優先されます。 シート、アラート、メニュー、ポップオーバーが表示されている場合、
スクリプトはウィンドウを読み取ってからそのサーフェスを読み取り、ウィンドウではなく
サーフェスの要素を提示します。ウィンドウのツリーでは、これらの要素には
offscreenが付いており、すべて除外されてしまうためです。 - 無効、非表示、アクションのない要素は提示されません。 また、treeitem 内のセルも提示されません (隣接する行を操作します)。それ以外はすべて提示され、名前のない行も含まれます。 行は保持する値で識別でき、Choice には候補を絞るより完全なリストを渡すほうが適しています。
リクエストの構成
act.mjs は 5 つの質問を含む 1 回の呼び出しを送信します。質問は並行して
回答されるため、推測的な質問はトークンを消費しますが、待ち時間は増えません。
| 質問 | 種類 | 尋ねる内容 |
|---|---|---|
target | choice | 意図が指す要素(none を含む)はどれか。 |
command | choice | 16 種類の操作動詞のうち、どれを求めているか。 |
present | noul | その対象はこの画面に存在するか。 |
destructive | noul | 取り消すのが難しい操作か。 |
needs_text | noul | 呼び出し元からのテキストが必要か。 |
Choice には 255 個の選択肢を指定できるため、1 回の実行で最大 254 個の要素を渡せます
(両方のスクリプト)。最初の act.mjs の実行で信頼度が 0.70 を下回った場合、
上位 5 件について、より詳しい説明を付けて再度質問します。要素が 254 個を超える画面では、
その旨が notes に記載されます。--root @ref を使ってください。
OpenRouter 経由での判断
判断エンドポイントは TYPESAFE_BASE_URL です。デフォルトは TypeSafe のネイティブな
https://api.typesafe.ai/v1/systemone です。設定するエンドポイントには HTTPS が必要です。
HTTP が許可されるのは localhost またはループバック IP のリテラル値の場合に限られ、
ローカルのテスト用ルーターを利用できます。URL の認証情報は拒否され、リダイレクトは
安全側に失敗するため、ベアラキーが別のエンドポイントに転送されることはありません。
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> エイリアスとして送信されます。または、
typesafe/jev-1.13 のようなルーティング ID を固定して指定できます。typesafe/jev-latest は
設定しないでください。OpenRouter は 400 Model does not exist を返します。OpenRouter の
Decisions エンドポイントはアルファ版であり、仕様が変更される可能性があります。
既知の制限
act.mjsは呼び出しごとに画面を一度読み取ります。run.mjsは各アクションの前後に画面を読み取り、DRILLまたはWIDENの後は読み取りをスキップします。固定された領域がない場合、シート、アラート、メニュー、またはポップオーバーが開いていると、読み取りのたびに2回目のスナップショットを取得します。高密度なアプリでは読み取りに約3秒かかります。--rootを指定すると、はるかに高速です。act.mjsは再試行しません。429が発生すると呼び出しは終了します。run.mjsは429、503、529を、失敗する前に500ミリ秒後、続いて1000ミリ秒後の計2回再試行します。- 入力を押し続ける操作は使用できないため、キーの長押しや、押したままのドラッグはできません。
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ミリ秒間待機します。操作に失敗すると、エラーコードと配信状態を保持したまま、すぐに action_failed として実行を停止し、再実行しません。フォーカス、クリップボードへの書き込み、または貼り付けに失敗した場合も、同様に実行を終了します。失敗した set-value のエラー状態が報告されないバイナリでは、BINARY_TOO_OLD で失敗します。
run.mjs は実行が done で停止した場合にのみ0で終了します。それ以外の停止と、すべてのエラー({"ok":false,"error":…})では1で終了し、使用方法のエラーでは2で終了します。act.mjs は、実行したコマンドが失敗した場合、または実行されなかった場合(decision が act でないとき、executed.code は NOT_EXECUTED)を除き、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 が保持され、ネストされたアクションの証拠も保持されます。