@reason-machines/cli-linux-arm64-musl 0.2.276 → 0.2.278
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.
- package/bin/ara +1 -1
- package/bin/vm-hands/connect-cli-qphdm1f1.js +2 -0
- package/bin/vm-hands/custom-endpoint-device-92914wyp.js +2 -0
- package/bin/vm-hands/desktop-account-vsrtgkk9.js +2 -0
- package/bin/vm-hands/desktop-wallpaper-0pjhgyfk.jpg +0 -0
- package/bin/vm-hands/device-workspace-zk5xwd10.js +2 -0
- package/bin/vm-hands/dock-browser-afp005mt.png +0 -0
- package/bin/vm-hands/dock-files-rywdm96b.png +0 -0
- package/bin/vm-hands/dock-terminal-s41cj1jp.png +0 -0
- package/bin/vm-hands/hands-worker-source-da93x0qx.js +2 -0
- package/bin/vm-hands/index-w9nq2f0p.js +12818 -0
- package/bin/vm-hands/local-automation-tvf2dbfq.js +2 -0
- package/bin/vm-hands/local-capabilities-p579j21r.js +2 -0
- package/bin/vm-hands/local-desktop-users-3j79jbyp.js +2 -0
- package/bin/vm-hands/local-display-mac-j1v3mcgx.js +2 -0
- package/bin/vm-hands/local-display-session-jwdq3jb4.js +2 -0
- package/bin/vm-hands/network-contract-41qanjhp.js +2 -0
- package/bin/vm-hands/package-rx9y4bwe.js +2 -0
- package/bin/vm-hands/permissions-2s0qyrg0.js +2 -0
- package/bin/vm-hands/reason-hands-protocol-51gs8j7v.js +2 -0
- package/bin/vm-hands/status-2d21kwsn.js +96 -0
- package/bin/vm-hands/version-1qgvgegp.js +2 -0
- package/bin/vm-hands/vm-hands-entry-lite-1bvbhwbb.js +3 -0
- package/bin/vm-hands/vm-hands-entry-lite-1hwdab4e.js +3 -0
- package/bin/vm-hands/vm-hands-entry-lite-2xas56hm.js +43 -0
- package/bin/vm-hands/vm-hands-entry-lite-51hcyapr.js +529 -0
- package/bin/vm-hands/vm-hands-entry-lite-5pxbx31d.js +179 -0
- package/bin/vm-hands/vm-hands-entry-lite-65pbrs1x.js +3 -0
- package/bin/vm-hands/vm-hands-entry-lite-7gh1rbn7.js +3 -0
- package/bin/vm-hands/vm-hands-entry-lite-84aq8d12.js +3 -0
- package/bin/vm-hands/vm-hands-entry-lite-8wyfep1z.js +343 -0
- package/bin/vm-hands/vm-hands-entry-lite-9pmag2qk.js +3 -0
- package/bin/vm-hands/vm-hands-entry-lite-anaydk9m.js +4 -0
- package/bin/vm-hands/vm-hands-entry-lite-bfwf82rw.js +5 -0
- package/bin/vm-hands/vm-hands-entry-lite-bwt3y6wz.js +3 -0
- package/bin/vm-hands/vm-hands-entry-lite-c2nrar90.js +4046 -0
- package/bin/vm-hands/vm-hands-entry-lite-ffg7rf02.js +3 -0
- package/bin/vm-hands/vm-hands-entry-lite-fraghb8k.js +5 -0
- package/bin/vm-hands/vm-hands-entry-lite-gqncr2j0.js +42 -0
- package/bin/vm-hands/vm-hands-entry-lite-htn1genx.js +459 -0
- package/bin/vm-hands/vm-hands-entry-lite-jwqqfjba.js +759 -0
- package/bin/vm-hands/vm-hands-entry-lite-k4c504n7.js +3 -0
- package/bin/vm-hands/vm-hands-entry-lite-n07kxrd1.js +3 -0
- package/bin/vm-hands/vm-hands-entry-lite-ptmqg51y.js +4 -0
- package/bin/vm-hands/vm-hands-entry-lite-qvrnx0vk.js +4 -0
- package/bin/vm-hands/vm-hands-entry-lite-s2xydt0n.js +112 -0
- package/bin/vm-hands/vm-hands-entry-lite-s5qn1k80.js +4 -0
- package/bin/vm-hands/vm-hands-entry-lite-sv6xwe0h.js +66 -0
- package/bin/vm-hands/vm-hands-entry-lite-y4sjgfqz.js +3 -0
- package/bin/vm-hands/vm-hands-entry-lite.js +2 -0
- package/bin/vm-hands/vm-hands-executor-v2qjnrb5.js +9 -0
- package/bin/vm-hands/vm-hands-op-runner-ej1c20a6.js +2 -0
- package/bin/vm-hands/vm-hands-runtime-yyy4ad4z.js +4 -0
- package/bin/vm-hands/worker-sandbox-nnce49w0.js +2 -0
- package/package.json +1 -1
|
@@ -0,0 +1,759 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
import{Dd as me}from"./vm-hands-entry-lite-gqncr2j0.js";import{ee as X,fe as ee,he as lt,ie as pe,le as xt,me as bt,ne as $,we as L,xe as Ge}from"./vm-hands-entry-lite-5pxbx31d.js";import{Be as v,Ce as z,Ke as gt,ze as E}from"./vm-hands-entry-lite-bfwf82rw.js";import{eg as ht,hg as wt,kg as D,mg as Lt,ng as oe}from"./vm-hands-entry-lite-2xas56hm.js";import{Mg as Se,Ng as re}from"./vm-hands-entry-lite-s2xydt0n.js";import{Vg as be}from"./vm-hands-entry-lite-s5qn1k80.js";import{Xg as T}from"./vm-hands-entry-lite-9pmag2qk.js";import{Fh as xe,zh as we}from"./vm-hands-entry-lite-anaydk9m.js";import{Gh as _}from"./vm-hands-entry-lite-ffg7rf02.js";import{spawn as I}from"child_process";import{randomUUID as ye}from"crypto";import{closeSync as Vr,copyFileSync as ie,existsSync as _t,fstatSync as Er,mkdirSync as ce,openSync as vr,readdirSync as K,readFileSync as Ae,readlinkSync as to,renameSync as eo,rmSync as ro,statSync as oo}from"fs";import{connect as so}from"net";import{userInfo as ge}from"os";import{basename as ne,dirname as ut,join as b}from"path";var yt=`import { spawn, execFileSync } from "node:child_process";
|
|
3
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
4
|
+
import { readFileSync, readlinkSync, lstatSync, realpathSync, statSync, mkdtempSync, mkdirSync, chmodSync, writeFileSync, readdirSync, rmSync, openSync, readSync, closeSync, renameSync } from "node:fs";
|
|
5
|
+
import { tmpdir } from "node:os";
|
|
6
|
+
import { basename, join } from "node:path";
|
|
7
|
+
|
|
8
|
+
const fail = code => { throw new Error(code); };
|
|
9
|
+
const record = value => value && typeof value === "object" && !Array.isArray(value);
|
|
10
|
+
const text = result => Array.isArray(result?.content) ? result.content.filter(item => item.type === "text").map(item => item.text).join("\\n") : "";
|
|
11
|
+
export const CLICKABLE_ROLES = new Set(["button", "push button", "toggle button", "check box", "checkbox", "radio button", "radio", "link", "menu item", "check menu item", "tab", "text field", "entry", "combo box", "list item", "tree item"]);
|
|
12
|
+
const ACCESSIBLE_CLICK_ACTIONS = /^(?:click|press|activate|invoke|toggle|select)$/i;
|
|
13
|
+
function stableJson(value) {
|
|
14
|
+
if (Array.isArray(value)) return value.map(stableJson);
|
|
15
|
+
if (record(value)) return Object.fromEntries(Object.keys(value).sort().map(key => [key, stableJson(value[key])]));
|
|
16
|
+
return value;
|
|
17
|
+
}
|
|
18
|
+
export function accessibilityState(result, platform = "linux") {
|
|
19
|
+
const structured = record(result?.structuredContent) ? result.structuredContent : null;
|
|
20
|
+
const rawElements = Array.isArray(structured?.elements) ? structured.elements : [];
|
|
21
|
+
const elements = rawElements.flatMap(item => {
|
|
22
|
+
if (!record(item) || !Number.isSafeInteger(item.index) || item.index < 0 || item.index > 9999) return [];
|
|
23
|
+
const roleValue = item.localizedControlType || item.controlType || item.role;
|
|
24
|
+
if (typeof roleValue !== "string" || !roleValue.trim()) return [];
|
|
25
|
+
const actions = Array.isArray(item.actions) ? item.actions.filter(action => typeof action === "string") : [];
|
|
26
|
+
return [{ index: item.index, role: roleValue.trim().replace(/\\s+/g, " ").toLowerCase(), actions }];
|
|
27
|
+
});
|
|
28
|
+
const nativeText = text(result).replace(/\\r\\n?/g, "\\n");
|
|
29
|
+
const treeLines = Array.isArray(structured?.treeLines) ? structured.treeLines.filter(line => typeof line === "string")
|
|
30
|
+
: nativeText.split("\\n").filter(line => (platform === "darwin" ? /^\\t*\\d+ / : /^\\t+\\d+ /).test(line));
|
|
31
|
+
// OCU 0.3.5 Linux emits its AT-SPI tree as text only. Preserve the exact
|
|
32
|
+
// native indices/roles/actions; never guess indices from labels or pixels.
|
|
33
|
+
if (!structured) {
|
|
34
|
+
const roles = [...CLICKABLE_ROLES].sort((a,b) => b.length-a.length);
|
|
35
|
+
const seen = new Set();
|
|
36
|
+
for (const line of treeLines) {
|
|
37
|
+
if (platform === "darwin") {
|
|
38
|
+
// Stock OCU 0.3.5 macOS hides primary AXPress from Secondary Actions
|
|
39
|
+
// and omits frames for ordinary controls. Keep its native index/role;
|
|
40
|
+
// OCU resolves that exact element and performs the accessibility click.
|
|
41
|
+
// Coordinate actions retain their separate screenshot bounds checks.
|
|
42
|
+
const native = /^\\t*(\\d+) (.+)$/.exec(line);
|
|
43
|
+
if (!native) continue;
|
|
44
|
+
const index = Number(native[1]);
|
|
45
|
+
if (seen.has(index)) fail("cloud_computer_accessibility_tree_invalid");
|
|
46
|
+
seen.add(index);
|
|
47
|
+
const role = roles.find(value => native[2] === value || native[2].startsWith(value + " "));
|
|
48
|
+
if (!role || index > 9999 || !Number.isSafeInteger(index)) continue;
|
|
49
|
+
const traits = /^\\s*\\(([^)]*)\\)/.exec(native[2].slice(role.length));
|
|
50
|
+
if (traits && traits[1].split(",").some(value => value.trim() === "disabled")) continue;
|
|
51
|
+
const actions = /(?:,)? Secondary Actions: (.+)$/.exec(native[2]);
|
|
52
|
+
elements.push({ index, role, actions: actions ? actions[1].split(", ") : [], platform: "darwin" });
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
const match = /^\\t+(\\d+) (.+?) Frame: \\{x: (-?\\d+), y: (-?\\d+), width: (\\d+), height: (\\d+)\\}$/.exec(line);
|
|
56
|
+
if (!match) continue;
|
|
57
|
+
const index = Number(match[1]);
|
|
58
|
+
if (seen.has(index)) fail("cloud_computer_accessibility_tree_invalid");
|
|
59
|
+
seen.add(index);
|
|
60
|
+
const role = roles.find(value => match[2].startsWith(value + " "));
|
|
61
|
+
const actionMatch = / Secondary Actions: ([a-z -]+(?:, [a-z -]+)*)$/.exec(match[2]);
|
|
62
|
+
if (!role || !actionMatch || index > 9999 || Number(match[3]) < 0 || Number(match[4]) < 0
|
|
63
|
+
|| Number(match[5]) < 1 || Number(match[6]) < 1) continue;
|
|
64
|
+
elements.push({ index, role, actions: actionMatch[1].split(", ") });
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
const fingerprint = structured
|
|
68
|
+
? JSON.stringify(stableJson(structured))
|
|
69
|
+
: treeLines.length ? treeLines.join("\\n") : nativeText;
|
|
70
|
+
return {
|
|
71
|
+
hash: createHash("sha256").update(fingerprint).digest("hex"),
|
|
72
|
+
elements,
|
|
73
|
+
treeLines,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
export function assertAccessibilityTreeMatches(expected, current) {
|
|
77
|
+
if (typeof expected?.accessibilityTreeHash !== "string" || !/^[a-f0-9]{64}$/.test(expected.accessibilityTreeHash)
|
|
78
|
+
|| expected.accessibilityTreeHash !== current?.accessibilityTreeHash) fail("cloud_computer_accessibility_tree_changed");
|
|
79
|
+
}
|
|
80
|
+
export function assertSafeComputerTarget(target, action) {
|
|
81
|
+
if (!target || !Number.isSafeInteger(target.pid) || target.pid <= 0 || !target.executable || !target.processStart) fail("cloud_computer_window_unavailable");
|
|
82
|
+
const name = basename(target.executable).toLowerCase();
|
|
83
|
+
if (/(?:chrome|chromium|firefox|msedge|brave|safari)/i.test(name + " " + target.app)) fail("cloud_computer_use_browser_use");
|
|
84
|
+
if (/^(?:xterm|uxterm|gnome-terminal(?:-server)?|konsole|xfce4-terminal|mate-terminal|qterminal|lxterminal|tilix|kitty|alacritty|terminal|iterm2?|bash|sh|zsh|fish|mc|pcmanfm|thunar|nautilus|dolphin|nemo|finder|openbox|tint2|dock|spotlight|systemuiserver|system settings|system preferences|code|code-oss|electron|gedit|kate|leafpad|mousepad|textedit|automator|script editor|shortcuts|emacs|gvim|vim|nvim|python[0-9.]*|node|ruby|perl|wish[0-9.]*|tclsh[0-9.]*)$/i.test(name)
|
|
85
|
+
|| /(?:devtools|developer tools|javascript console)/i.test(target.title)
|
|
86
|
+
|| /(?:com\\.apple\\.(?:Terminal|Finder|Automator|ScriptEditor2|Shortcuts|systempreferences|dock|Spotlight)|com\\.googlecode\\.iterm2)/i.test(target.app)) fail("cloud_computer_use_bash");
|
|
87
|
+
if (action?.kind === "key_press") {
|
|
88
|
+
const parts = action.key.toLowerCase().split("+"); const key = parts.pop(); const mods = new Set(parts);
|
|
89
|
+
if (key === "f12" || (mods.has("ctrl") && mods.has("shift") && ["i","j","c","k"].includes(key))
|
|
90
|
+
|| (mods.has("alt") && ["f2","f4","tab"].includes(key)) || mods.has("super") || mods.has("meta") || mods.has("cmd")
|
|
91
|
+
|| (mods.has("ctrl") && mods.has("alt"))) fail("cloud_computer_shortcut_restricted");
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
export function sameComputerTarget(a, b) {
|
|
95
|
+
return ["app","pid","title","width","height","executable","processStart"].every(key => a?.[key] === b?.[key]);
|
|
96
|
+
}
|
|
97
|
+
function provider(command, args) {
|
|
98
|
+
const child = spawn(command, args, { stdio: ["pipe", "pipe", "ignore"], env: process.env });
|
|
99
|
+
let counter = 0, buffer = "", closedCode = null; const pending = new Map();
|
|
100
|
+
const exited = new Promise(resolve => {
|
|
101
|
+
child.once("exit", resolve);
|
|
102
|
+
// Spawn failure means no child exists. Errors after a PID was assigned
|
|
103
|
+
// (including a failed kill) still require an actual exit receipt.
|
|
104
|
+
child.once("error", () => { if (child.pid === undefined) resolve(); });
|
|
105
|
+
});
|
|
106
|
+
child.stdout.setEncoding("utf8"); child.stdout.on("data", chunk => {
|
|
107
|
+
buffer += chunk; if (buffer.length > 16 * 1024 * 1024) { child.kill(); return; }
|
|
108
|
+
let index; while ((index = buffer.indexOf("\\n")) >= 0) {
|
|
109
|
+
const line = buffer.slice(0,index); buffer = buffer.slice(index+1); let message;
|
|
110
|
+
try { message = JSON.parse(line); } catch { continue; }
|
|
111
|
+
const wait = pending.get(message.id); if (!wait) continue; pending.delete(message.id); clearTimeout(wait.timer);
|
|
112
|
+
if (message.error) wait.reject(new Error("cloud_automation_provider_error")); else wait.resolve(message.result);
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
const closed = code => { closedCode = code; for (const wait of pending.values()) { clearTimeout(wait.timer); wait.reject(new Error(code)); } pending.clear(); };
|
|
116
|
+
child.stdin.on("error", () => closed("cloud_automation_provider_closed"));
|
|
117
|
+
child.on("error", () => closed("cloud_automation_provider_unavailable"));
|
|
118
|
+
child.on("exit", () => closed("cloud_automation_provider_closed"));
|
|
119
|
+
const rpc = (method, params) => new Promise((resolve,reject) => {
|
|
120
|
+
if (closedCode) { reject(new Error(closedCode)); return; }
|
|
121
|
+
const id = ++counter; const timer = setTimeout(() => { pending.delete(id); reject(new Error("cloud_automation_provider_timeout")); child.kill(); }, 45000);
|
|
122
|
+
pending.set(id,{resolve,reject,timer}); child.stdin.write(JSON.stringify({jsonrpc:"2.0",id,method,params})+"\\n");
|
|
123
|
+
});
|
|
124
|
+
return { rpc, exited, forceClose() { child.kill("SIGKILL"); }, notify(method) { child.stdin.write(JSON.stringify({jsonrpc:"2.0",method})+"\\n"); }, close() { child.stdin.end(); child.kill(); closed("cloud_automation_provider_closed"); } };
|
|
125
|
+
}
|
|
126
|
+
export function checkedProviderResult(result, { computer = false, platform = process.platform } = {}) {
|
|
127
|
+
if (!record(result) || result.isError === true || result.structuredContent?.error) {
|
|
128
|
+
// OCU 0.3.5 emits this fixed error text, without a structured permission code.
|
|
129
|
+
// Scope the translation to its macOS Computer result, never browser content.
|
|
130
|
+
if (computer && platform === "darwin" && result?.isError === true
|
|
131
|
+
&& text(result).startsWith("Accessibility permission is required. Run \`open-computer-use doctor\` and grant access to Open Computer Use.")) {
|
|
132
|
+
fail("cloud_computer_accessibility_permission_required");
|
|
133
|
+
}
|
|
134
|
+
// Stock OBU MCP exposes its Go Unix-socket read deadline as one error text.
|
|
135
|
+
// Match the transport envelope, never ordinary page/tool content or OCU.
|
|
136
|
+
if (!computer && result?.isError === true && Array.isArray(result.content)
|
|
137
|
+
&& result.content.length === 1 && result.content[0]?.type === "text"
|
|
138
|
+
&& typeof result.content[0].text === "string"
|
|
139
|
+
&& /^read unix [^\\r\\n]{1,1000}: i\\/o timeout$/.test(result.content[0].text)) fail("cloud_automation_provider_timeout");
|
|
140
|
+
if (result?.structuredContent?.error?.code === -32000
|
|
141
|
+
&& /^Timed out after \\d+ms waiting for CDP command\\.$/.test(result?.structuredContent?.error?.message ?? "")) fail("cloud_automation_browser_capture_timeout");
|
|
142
|
+
fail("cloud_automation_provider_refused");
|
|
143
|
+
}
|
|
144
|
+
return result;
|
|
145
|
+
}
|
|
146
|
+
export function parseMacPermissionStatus(output) {
|
|
147
|
+
const match = /^Permissions: accessibility=(granted|missing), screenRecording=(granted|missing)$/m.exec(output.trim());
|
|
148
|
+
if (!match) return null;
|
|
149
|
+
return { accessibility: match[1], screenRecording: match[2] };
|
|
150
|
+
}
|
|
151
|
+
export function computerSnapshotImage(result) {
|
|
152
|
+
const images = Array.isArray(result?.content) ? result.content.filter(block => block?.type === "image") : [];
|
|
153
|
+
// OCU may omit a screenshot when ScreenCaptureKit fails. This alone does not
|
|
154
|
+
// establish a permission denial; it can also mean a missing window or timeout.
|
|
155
|
+
if (images.length === 0) fail("cloud_computer_screen_capture_unavailable");
|
|
156
|
+
const image = images.find(block => block.mimeType === "image/png");
|
|
157
|
+
if (!image || typeof image.data !== "string" || image.data.length > 12 * 1024 * 1024) fail("cloud_computer_display_image_invalid");
|
|
158
|
+
return image;
|
|
159
|
+
}
|
|
160
|
+
// Linux OCU ends each line with the window title, which is page text and may
|
|
161
|
+
// itself contain brackets ("Issue [WIP] - Google Chrome"). Matching the fixed
|
|
162
|
+
// marker prefix keeps such a window listed; macOS markers never contain "]".
|
|
163
|
+
const LINUX_RUNNING_APP_LINE = /^(.*?) -- (.*?) \\[(running), pid=\\d+, window=.*\\]$/;
|
|
164
|
+
export function parseRunningApps(result) {
|
|
165
|
+
return text(result).split("\\n").flatMap(line => {
|
|
166
|
+
const match = LINUX_RUNNING_APP_LINE.exec(line) ?? /^(.*?) (?:\u2014|--) (.*?) \\[([^\\]]*)\\]$/.exec(line);
|
|
167
|
+
if (!match || !/(?:^|,\\s*)running(?:,|$)/.test(match[3]) || match[2].length > 256 || /[\\x00-\\x1f/]/.test(match[2])) return [];
|
|
168
|
+
return [{name:match[1].slice(0,256),app:match[2]}];
|
|
169
|
+
}).slice(0,100);
|
|
170
|
+
}
|
|
171
|
+
function targetFor(result, expectedApp) {
|
|
172
|
+
const lines = text(result); const identity = /^App=(.+) \\(pid (\\d+)\\)\\nWindow: ("(?:[^"\\\\]|\\\\.)*"), App: /m.exec(lines);
|
|
173
|
+
if (!identity || identity[1] !== expectedApp) fail("cloud_computer_window_changed");
|
|
174
|
+
const image = computerSnapshotImage(result);
|
|
175
|
+
const png = Buffer.from(image.data,"base64");
|
|
176
|
+
if (png.length < 24 || png.subarray(0,8).toString("hex") !== "89504e470d0a1a0a") fail("cloud_computer_display_image_invalid");
|
|
177
|
+
const width=png.readUInt32BE(16),height=png.readUInt32BE(20),pid=Number(identity[2]);
|
|
178
|
+
if (!Number.isSafeInteger(pid) || pid <= 0 || width<1 || height<1 || width>8192 || height>8192 || width*height>25000000) fail("cloud_computer_window_unavailable");
|
|
179
|
+
let executable,processStart;
|
|
180
|
+
if (process.platform === "linux") {
|
|
181
|
+
executable=readlinkSync(\`/proc/\${pid}/exe\`); const stat=readFileSync(\`/proc/\${pid}/stat\`,"utf8"); processStart=stat.slice(stat.lastIndexOf(")")+2).split(/\\s+/)[19];
|
|
182
|
+
} else {
|
|
183
|
+
executable=execFileSync("/bin/ps",["-p",String(pid),"-o","comm="],{encoding:"utf8",timeout:3000}).trim();
|
|
184
|
+
processStart=execFileSync("/bin/ps",["-p",String(pid),"-o","lstart="],{encoding:"utf8",timeout:3000}).trim();
|
|
185
|
+
}
|
|
186
|
+
if (!executable || !processStart) fail("cloud_computer_window_unavailable");
|
|
187
|
+
const tree = accessibilityState(result, process.platform);
|
|
188
|
+
return {app:identity[1],pid,title:JSON.parse(identity[3]),width,height,executable,processStart,accessibilityTreeHash:tree.hash};
|
|
189
|
+
}
|
|
190
|
+
export function computerSnapshotOptions(request, platform = process.platform) {
|
|
191
|
+
// Stock 0.3.5 action refresh uses its 1200/64 defaults. Revalidate that
|
|
192
|
+
// receipt with identical bounds so an unchanged tree hashes identically.
|
|
193
|
+
const stockActionTree = platform === "darwin" && request.operation === "act"
|
|
194
|
+
&& request.expected?.stockActionTree === true;
|
|
195
|
+
return { app: request.app, text_limit: 500,
|
|
196
|
+
max_tree_nodes: stockActionTree ? 1200 : 300,
|
|
197
|
+
max_tree_depth: stockActionTree ? 64 : 30 };
|
|
198
|
+
}
|
|
199
|
+
// Stock macOS OCU refreshes the screenshot/tree after input. Failure to
|
|
200
|
+
// validate that observation must never turn completed input into a retry.
|
|
201
|
+
export function macComputerActionObservation(result, before) {
|
|
202
|
+
try {
|
|
203
|
+
if (result?.isError || !Array.isArray(result?.content)
|
|
204
|
+
|| result.content.filter(item => item?.type === "image").length !== 1)
|
|
205
|
+
fail("cloud_computer_display_image_invalid");
|
|
206
|
+
const target = targetFor(result, before.app);
|
|
207
|
+
if (!["app", "pid", "executable", "processStart"].every(key => target[key] === before[key]))
|
|
208
|
+
fail("cloud_computer_window_changed");
|
|
209
|
+
return { actionCompleted: true, afterSnapshot: { target: { ...target, stockActionTree: true }, content: result.content } };
|
|
210
|
+
} catch {
|
|
211
|
+
return { actionCompleted: true, afterSnapshotError: "invalid_post_action_snapshot" };
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
export function actionTool(action, app, target, elements = []) {
|
|
215
|
+
if (!record(action)) fail("cloud_computer_action_invalid");
|
|
216
|
+
const keys={click_pixel:["kind","x","y"],click_element:["kind","element_index","role"],type_text:["kind","text"],key_press:["kind","key"],drag:["kind","from_x","from_y","to_x","to_y","duration_ms","steps"]}[action.kind];
|
|
217
|
+
if (!keys || Object.keys(action).some(key=>!keys.includes(key))) fail("cloud_computer_action_invalid");
|
|
218
|
+
const point = (x,y) => { if (!Number.isSafeInteger(x)||!Number.isSafeInteger(y)||x<0||y<0||x>=target.width||y>=target.height) fail("cloud_computer_action_out_of_bounds"); };
|
|
219
|
+
if (action.kind === "click_pixel") { point(action.x,action.y); return ["click",{app,x:action.x,y:action.y,click_count:1,mouse_button:"left"}]; }
|
|
220
|
+
if (action.kind === "click_element") {
|
|
221
|
+
if (!Number.isSafeInteger(action.element_index) || action.element_index < 0 || action.element_index > 9999
|
|
222
|
+
|| typeof action.role !== "string" || action.role.length > 128) fail("cloud_computer_action_invalid");
|
|
223
|
+
const role = action.role.trim().replace(/\\s+/g, " ").toLowerCase();
|
|
224
|
+
const element = elements.find(candidate => candidate.index === action.element_index);
|
|
225
|
+
if (!element || element.role !== role || !CLICKABLE_ROLES.has(role)
|
|
226
|
+
|| (element.platform !== "darwin" && element.actions.length > 0 && !element.actions.some(candidate => ACCESSIBLE_CLICK_ACTIONS.test(candidate)))) {
|
|
227
|
+
fail("cloud_computer_accessibility_target_invalid");
|
|
228
|
+
}
|
|
229
|
+
return ["click",{app,element_index:String(action.element_index),click_method:"accessibility"}];
|
|
230
|
+
}
|
|
231
|
+
if (action.kind === "type_text" && typeof action.text === "string" && action.text.length <= 8192) return ["type_text",{app,text:action.text}];
|
|
232
|
+
if (action.kind === "key_press" && typeof action.key === "string" && /^(?:(?:ctrl|alt|shift|super)\\+){0,4}(?:[a-zA-Z0-9*/=.-]|Return|Tab|Escape|BackSpace|Delete|space|Left|Right|Up|Down|Home|End|Page_Up|Page_Down|F(?:[1-9]|1[0-2]))$/.test(action.key)) return ["press_key",{app,key:action.key}];
|
|
233
|
+
if (action.kind === "drag") { point(action.from_x,action.from_y);point(action.to_x,action.to_y);if(!Number.isSafeInteger(action.duration_ms)||action.duration_ms<100||action.duration_ms>2000||!Number.isSafeInteger(action.steps)||action.steps<1||action.steps>120)fail("cloud_computer_action_invalid");return ["drag",{app,from_x:action.from_x,from_y:action.from_y,to_x:action.to_x,to_y:action.to_y}]; }
|
|
234
|
+
fail("cloud_computer_action_unsupported");
|
|
235
|
+
}
|
|
236
|
+
const explicitTabTools = new Set(["navigate", "wait_load", "page_info", "cdp", "move_mouse", "wait_file_chooser"]);
|
|
237
|
+
export function validateBrowserTab(request) {
|
|
238
|
+
if (explicitTabTools.has(request.tool) && (!record(request.arguments) || !Number.isSafeInteger(request.arguments.tab_id) || request.arguments.tab_id <= 0)) fail("cloud_automation_browser_tab_id_required");
|
|
239
|
+
if (request.tool === "call" && request.arguments?.method === "waitForFileChooser"
|
|
240
|
+
&& (!record(request.arguments.params) || !Number.isSafeInteger(request.arguments.params.tabId) || request.arguments.params.tabId <= 0)) fail("cloud_automation_browser_tab_id_required");
|
|
241
|
+
}
|
|
242
|
+
const browserNetworkReasons = new Map([
|
|
243
|
+
["ERR_CONNECTION_REFUSED", "connection_refused"], ["ERR_CONNECTION_RESET", "connection_reset"],
|
|
244
|
+
["ERR_NAME_NOT_RESOLVED", "name_not_resolved"], ["ERR_INTERNET_DISCONNECTED", "internet_disconnected"],
|
|
245
|
+
["ERR_TIMED_OUT", "timed_out"], ["ERR_CONNECTION_TIMED_OUT", "timed_out"], ["ERR_ABORTED", "aborted"],
|
|
246
|
+
// The machine's egress proxy refused the host (CONNECT 403): a network policy, not the site.
|
|
247
|
+
["ERR_TUNNEL_CONNECTION_FAILED", "egress_blocked"],
|
|
248
|
+
]);
|
|
249
|
+
function browserRefusalReason(value) {
|
|
250
|
+
if (typeof value !== "string") return "provider_refused";
|
|
251
|
+
const code = /\\bnet::(ERR_[A-Z_]+)\\b/.exec(value.slice(0, 8192))?.[1];
|
|
252
|
+
return browserNetworkReasons.get(code) ?? "provider_refused";
|
|
253
|
+
}
|
|
254
|
+
class BrowserProviderFailure extends Error {
|
|
255
|
+
constructor(error, stage, reason) {
|
|
256
|
+
super(error instanceof Error && /^cloud_automation_[a-z0-9_]+$/.test(error.message) ? error.message : "cloud_automation_failed");
|
|
257
|
+
this.stage = stage; this.reason = reason;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
class ComputerProviderFailure extends Error {
|
|
261
|
+
constructor(error, stage, reason) {
|
|
262
|
+
super(error instanceof Error && /^cloud_(?:automation|computer)_[a-z0-9_]+$/.test(error.message) ? error.message : "cloud_automation_failed");
|
|
263
|
+
this.stage = stage; this.reason = reason;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
export async function callComputerTool(call, name, args, platform = process.platform) {
|
|
267
|
+
const stage = name === "list_apps" ? "list_apps" : name === "get_app_state" ? "get_app_state" : "action";
|
|
268
|
+
let result;
|
|
269
|
+
try {
|
|
270
|
+
result = await call(name, args);
|
|
271
|
+
return checkedProviderResult(result, { computer: true, platform });
|
|
272
|
+
} catch (error) {
|
|
273
|
+
// OCU 0.3.5's fixed error when the app has no window on screen (hidden, minimized
|
|
274
|
+
// or on another Space) and its own recovery could not bring one up: the window
|
|
275
|
+
// is unavailable to Computer use, not a provider refusal.
|
|
276
|
+
if (platform === "darwin" && name === "get_app_state" && result?.isError === true
|
|
277
|
+
&& Array.isArray(result.content) && result.content.length === 1 && result.content[0]?.type === "text"
|
|
278
|
+
&& result.content[0].text === "Apple event error -10005: cgWindowNotFound") fail("cloud_computer_window_unavailable");
|
|
279
|
+
// Match only the fixed stock OCU refusal, never echo provider text or input.
|
|
280
|
+
const editableRequired = platform === "darwin" && name === "type_text" && result?.isError === true
|
|
281
|
+
&& Array.isArray(result.content) && result.content.length === 1 && result.content[0]?.type === "text"
|
|
282
|
+
&& result.content[0].text === "type_text requires a focused editable text element. Click a text entry area first, or use set_value on a settable text element.";
|
|
283
|
+
const reason = editableRequired ? "focused_editable_required"
|
|
284
|
+
: error?.message === "cloud_computer_accessibility_permission_required" ? "accessibility_permission_required"
|
|
285
|
+
: error?.message === "cloud_automation_provider_timeout" ? "timed_out"
|
|
286
|
+
: error?.message === "cloud_automation_provider_closed" ? "provider_closed"
|
|
287
|
+
: error?.message === "cloud_automation_provider_unavailable" ? "provider_unavailable"
|
|
288
|
+
: error?.message === "cloud_automation_provider_error" ? "provider_error" : "provider_refused";
|
|
289
|
+
throw new ComputerProviderFailure(error, stage, reason);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
export function cloudAutomationFailure(error) {
|
|
293
|
+
const errorCode = error instanceof Error && /^cloud_(?:automation|computer)_[a-z0-9_]+$/.test(error.message) ? error.message : "cloud_automation_failed";
|
|
294
|
+
return { ok: false, errorCode, ...(error instanceof BrowserProviderFailure ? { browserFailure: { stage: error.stage, reason: error.reason } } : {}), ...(error instanceof ComputerProviderFailure ? { computerFailure: { stage: error.stage, reason: error.reason } } : {}) };
|
|
295
|
+
}
|
|
296
|
+
async function browserStage(stage, action) {
|
|
297
|
+
try { return await action(); }
|
|
298
|
+
catch (error) {
|
|
299
|
+
if (error instanceof BrowserProviderFailure) throw error;
|
|
300
|
+
const reason = error?.message === "cloud_automation_browser_capture_timeout" || error?.message === "cloud_automation_provider_timeout" ? "timed_out"
|
|
301
|
+
: error?.message === "cloud_automation_browser_target_invalid" ? "invalid_target" : "provider_refused";
|
|
302
|
+
throw new BrowserProviderFailure(error, stage, reason);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
function checkedBrowserResult(result, stage, fileInput = false) {
|
|
306
|
+
try { return checkedProviderResult(result); }
|
|
307
|
+
catch (error) {
|
|
308
|
+
const raw = result?.structuredContent?.error?.message ?? text(result);
|
|
309
|
+
const fileInputDenied = fileInput && result?.structuredContent?.error?.code === -32000
|
|
310
|
+
&& result.structuredContent.error.message === "Not allowed";
|
|
311
|
+
const reason = fileInputDenied ? "file_input_not_allowed"
|
|
312
|
+
: error?.message === "cloud_automation_browser_capture_timeout" || error?.message === "cloud_automation_provider_timeout"
|
|
313
|
+
? "timed_out" : browserRefusalReason(raw);
|
|
314
|
+
throw new BrowserProviderFailure(error, stage, reason);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
export async function callDiscoveredBrowserTool(rpc, request) {
|
|
318
|
+
validateBrowserTab(request);
|
|
319
|
+
const startedAt = performance.now();
|
|
320
|
+
const discovery = await rpc("tools/list", {});
|
|
321
|
+
const discoveryMs = performance.now() - startedAt;
|
|
322
|
+
if (!Array.isArray(discovery?.tools)) fail("cloud_automation_response_invalid");
|
|
323
|
+
if (request.tool === "run_action_plan" || !discovery.tools.some(tool => tool?.name === request.tool))
|
|
324
|
+
fail("cloud_automation_browser_tool_unavailable");
|
|
325
|
+
const callStartedAt = performance.now();
|
|
326
|
+
const result = await callBrowserTool((name, args) => rpc("tools/call", { name, arguments: args }), request);
|
|
327
|
+
return { result, browserTimings: { discoveryMs, callMs: performance.now() - callStartedAt } };
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// A cloud Session's browser or computer screenshot is also kept as a file on
|
|
331
|
+
// its machine (request.saveScreenshot), so the agent can show it to the user by
|
|
332
|
+
// path. The newest ten stay. Saving never fails the capture: no path is
|
|
333
|
+
// returned instead.
|
|
334
|
+
export const BROWSER_SCREENSHOT_DIRECTORY = "/tmp/reason-screenshots";
|
|
335
|
+
export function saveBrowserScreenshot(result, options) {
|
|
336
|
+
return saveScreenshotPng(result?.structuredContent?.result?.data, options);
|
|
337
|
+
}
|
|
338
|
+
/** The one PNG of a computer snapshot's content (Open Computer Use returns the window as an image block). */
|
|
339
|
+
export function saveComputerScreenshot(content, options) {
|
|
340
|
+
const images = Array.isArray(content) ? content.filter(item => item?.type === "image") : [];
|
|
341
|
+
return images.length === 1 && images[0].mimeType === "image/png" ? saveScreenshotPng(images[0].data, options) : undefined;
|
|
342
|
+
}
|
|
343
|
+
function saveScreenshotPng(data, { directory = BROWSER_SCREENSHOT_DIRECTORY, keep = 10 } = {}) {
|
|
344
|
+
try {
|
|
345
|
+
if (typeof data !== "string") return undefined;
|
|
346
|
+
const bytes = Buffer.from(data, "base64");
|
|
347
|
+
if (bytes.length < 24 || bytes.subarray(0, 8).toString("hex") !== "89504e470d0a1a0a") return undefined;
|
|
348
|
+
mkdirSync(directory, { recursive: true, mode: 0o755 });
|
|
349
|
+
const owner = lstatSync(directory);
|
|
350
|
+
if (!owner.isDirectory() || owner.isSymbolicLink() || owner.uid !== process.getuid?.()) return undefined;
|
|
351
|
+
chmodSync(directory, 0o755);
|
|
352
|
+
const path = join(directory, \`\${randomUUID()}.png\`);
|
|
353
|
+
writeFileSync(path, bytes, { mode: 0o644, flag: "wx" });
|
|
354
|
+
chmodSync(path, 0o644);
|
|
355
|
+
const saved = readdirSync(directory).filter(name => /^[0-9a-f-]{36}\\.png$/.test(name))
|
|
356
|
+
.map(name => ({ name, info: lstatSync(join(directory, name)) }))
|
|
357
|
+
.filter(file => file.info.isFile() && file.info.uid === process.getuid?.())
|
|
358
|
+
.sort((a, b) => b.info.mtimeMs - a.info.mtimeMs);
|
|
359
|
+
for (const file of saved.filter(file => join(directory, file.name) !== path).slice(keep - 1)) rmSync(join(directory, file.name), { force: true });
|
|
360
|
+
return path;
|
|
361
|
+
} catch { return undefined; }
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// A cloud Session's screen recording (request.operation record_start, then
|
|
365
|
+
// record_stop). Explicit and bounded: nothing records unless the agent asks,
|
|
366
|
+
// ffmpeg ends by itself after \`seconds\` or at the size limit, and one recording
|
|
367
|
+
// runs at a time. Each request is its own short process, so ffmpeg runs
|
|
368
|
+
// detached and a small state file names it for the stop request. The newest
|
|
369
|
+
// five recordings stay.
|
|
370
|
+
export const BROWSER_RECORDING_DIRECTORY = "/tmp/reason-recordings";
|
|
371
|
+
export const BROWSER_RECORDING_MAX_SECONDS = 30;
|
|
372
|
+
const RECORDING_MAX_BYTES = 10 * 1024 * 1024;
|
|
373
|
+
const RECORDING_STATE = "active.json";
|
|
374
|
+
const RECORDING_PROGRESS = "progress";
|
|
375
|
+
const pause = ms => new Promise(resolve => setTimeout(resolve, ms));
|
|
376
|
+
function recordingDirectory(directory) {
|
|
377
|
+
mkdirSync(directory, { recursive: true, mode: 0o755 });
|
|
378
|
+
const owner = lstatSync(directory);
|
|
379
|
+
if (!owner.isDirectory() || owner.isSymbolicLink() || owner.uid !== process.getuid?.()) fail("cloud_automation_recording_unavailable");
|
|
380
|
+
chmodSync(directory, 0o755);
|
|
381
|
+
}
|
|
382
|
+
/** True while \`pid\` is still the recorder writing \`path\`, so a reused process id is never signalled. */
|
|
383
|
+
function recorderRunning(pid, path) {
|
|
384
|
+
try {
|
|
385
|
+
// A finished recorder not yet reaped is a zombie, with no command line: not running.
|
|
386
|
+
if (process.platform === "linux") return readFileSync(\`/proc/\${pid}/cmdline\`, "utf8").split("\\0").includes(path);
|
|
387
|
+
const row = execFileSync("/bin/ps", ["-p", String(pid), "-o", "stat=,args="], { encoding: "utf8", timeout: 5000, maxBuffer: 65536, stdio: ["ignore", "pipe", "ignore"] }).trim();
|
|
388
|
+
return !row.startsWith("Z") && row.includes(path);
|
|
389
|
+
} catch { return false; }
|
|
390
|
+
}
|
|
391
|
+
function activeRecording(directory) {
|
|
392
|
+
try {
|
|
393
|
+
const state = JSON.parse(readFileSync(join(directory, RECORDING_STATE), "utf8"));
|
|
394
|
+
if (!record(state) || !Number.isSafeInteger(state.pid) || state.pid <= 1 || typeof state.path !== "string"
|
|
395
|
+
|| !state.path.startsWith(\`\${directory}/\`) || !/^[0-9a-f-]{36}\\.webm$/.test(state.path.slice(directory.length + 1))
|
|
396
|
+
|| !Number.isSafeInteger(state.seconds) || !Number.isFinite(state.startedAt)) return undefined;
|
|
397
|
+
return state;
|
|
398
|
+
} catch { return undefined; }
|
|
399
|
+
}
|
|
400
|
+
export async function startBrowserRecording(seconds, { directory = BROWSER_RECORDING_DIRECTORY, ffmpeg = "ffmpeg", display = process.env.DISPLAY || ":99", keep = 5 } = {}) {
|
|
401
|
+
if (!Number.isSafeInteger(seconds) || seconds < 1 || seconds > BROWSER_RECORDING_MAX_SECONDS) fail("cloud_automation_recording_seconds_invalid");
|
|
402
|
+
recordingDirectory(directory);
|
|
403
|
+
const running = activeRecording(directory);
|
|
404
|
+
if (running && recorderRunning(running.pid, running.path)) fail("cloud_automation_recording_active");
|
|
405
|
+
const path = join(directory, \`\${randomUUID()}.webm\`);
|
|
406
|
+
const saved = readdirSync(directory).filter(name => /^[0-9a-f-]{36}\\.webm$/.test(name))
|
|
407
|
+
.map(name => ({ name, info: lstatSync(join(directory, name)) }))
|
|
408
|
+
.filter(file => file.info.isFile() && file.info.uid === process.getuid?.())
|
|
409
|
+
.sort((a, b) => b.info.mtimeMs - a.info.mtimeMs);
|
|
410
|
+
for (const file of saved.slice(keep - 1)) rmSync(join(directory, file.name), { force: true });
|
|
411
|
+
const progress = join(directory, RECORDING_PROGRESS);
|
|
412
|
+
rmSync(progress, { force: true });
|
|
413
|
+
const child = spawn(ffmpeg, ["-nostdin", "-hide_banner", "-loglevel", "error", "-y",
|
|
414
|
+
"-f", "x11grab", "-framerate", "12", "-i", display, "-t", String(seconds),
|
|
415
|
+
"-vf", "scale=1280:-2", "-c:v", "libvpx", "-deadline", "realtime", "-b:v", "700k",
|
|
416
|
+
"-an", "-fs", String(RECORDING_MAX_BYTES), "-progress", progress, path], { stdio: "ignore", detached: true });
|
|
417
|
+
let exited = false;
|
|
418
|
+
child.once("exit", () => { exited = true; });
|
|
419
|
+
try { await new Promise((resolve, reject) => { child.once("spawn", resolve); child.once("error", reject); }); }
|
|
420
|
+
catch { fail("cloud_automation_recording_unavailable"); }
|
|
421
|
+
// Started means ffmpeg reports a captured frame (the file itself stays empty
|
|
422
|
+
// for seconds while the muxer buffers); a missing display or encoder ends it
|
|
423
|
+
// within this wait instead.
|
|
424
|
+
const startedAt = Date.now();
|
|
425
|
+
let begun = false;
|
|
426
|
+
while (!exited && !begun && Date.now() - startedAt < 6000) {
|
|
427
|
+
await pause(100);
|
|
428
|
+
try { begun = /^frame=\\s*[1-9]/m.test(readFileSync(progress, "utf8")); } catch {}
|
|
429
|
+
}
|
|
430
|
+
if (exited || !begun) {
|
|
431
|
+
if (!exited) { try { process.kill(child.pid, "SIGKILL"); } catch {} }
|
|
432
|
+
rmSync(path, { force: true });
|
|
433
|
+
rmSync(progress, { force: true });
|
|
434
|
+
fail("cloud_automation_recording_unavailable");
|
|
435
|
+
}
|
|
436
|
+
const state = join(directory, RECORDING_STATE);
|
|
437
|
+
writeFileSync(\`\${state}.\${process.pid}\`, JSON.stringify({ pid: child.pid, path, seconds, startedAt }), { mode: 0o600 });
|
|
438
|
+
renameSync(\`\${state}.\${process.pid}\`, state);
|
|
439
|
+
child.unref();
|
|
440
|
+
return { ok: true, recording: { seconds } };
|
|
441
|
+
}
|
|
442
|
+
export async function stopBrowserRecording({ directory = BROWSER_RECORDING_DIRECTORY } = {}) {
|
|
443
|
+
const state = activeRecording(directory);
|
|
444
|
+
if (!state) fail("cloud_automation_recording_not_started");
|
|
445
|
+
// SIGINT lets ffmpeg finish the file; it has already ended by itself when
|
|
446
|
+
// the requested seconds ran out.
|
|
447
|
+
if (recorderRunning(state.pid, state.path)) {
|
|
448
|
+
try { process.kill(state.pid, "SIGINT"); } catch {}
|
|
449
|
+
for (let waited = 0; waited < 8000 && recorderRunning(state.pid, state.path); waited += 150) await pause(150);
|
|
450
|
+
if (recorderRunning(state.pid, state.path)) { try { process.kill(state.pid, "SIGKILL"); } catch {} await pause(300); }
|
|
451
|
+
}
|
|
452
|
+
rmSync(join(directory, RECORDING_STATE), { force: true });
|
|
453
|
+
rmSync(join(directory, RECORDING_PROGRESS), { force: true });
|
|
454
|
+
let fd;
|
|
455
|
+
try {
|
|
456
|
+
const file = lstatSync(state.path);
|
|
457
|
+
if (!file.isFile() || file.uid !== process.getuid?.() || file.size < 128 || file.size > RECORDING_MAX_BYTES + 65536) throw new Error();
|
|
458
|
+
fd = openSync(state.path, "r");
|
|
459
|
+
const head = Buffer.alloc(4);
|
|
460
|
+
if (readSync(fd, head, 0, 4, 0) !== 4 || head.toString("hex") !== "1a45dfa3") throw new Error();
|
|
461
|
+
chmodSync(state.path, 0o644);
|
|
462
|
+
return { ok: true, recordingPath: state.path, recordingBytes: file.size,
|
|
463
|
+
recordingSeconds: Math.max(1, Math.min(state.seconds, Math.round((Date.now() - state.startedAt) / 1000))) };
|
|
464
|
+
} catch {
|
|
465
|
+
rmSync(state.path, { force: true });
|
|
466
|
+
fail("cloud_automation_recording_failed");
|
|
467
|
+
} finally { if (fd !== undefined) closeSync(fd); }
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
export async function callBrowserTool(call, request) {
|
|
471
|
+
validateBrowserTab(request);
|
|
472
|
+
const args = request.arguments ?? {};
|
|
473
|
+
const rpcCdp = request.tool === "call" && args.method === "executeCdp";
|
|
474
|
+
const cdpMethod = request.tool === "cdp" ? args.method : rpcCdp ? args.params?.method : undefined;
|
|
475
|
+
const cdpTabId = request.tool === "cdp" ? args.tab_id : rpcCdp ? args.params?.target?.tabId : undefined;
|
|
476
|
+
// A person's own Chrome (cli/local-automation.ts) keeps agent tabs in the
|
|
477
|
+
// background: bringing one to the front would replace the tab they are
|
|
478
|
+
// looking at. Screenshots of a background tab still work, and OBU's tab-scoped
|
|
479
|
+
// CDP still refuses a tab outside this session.
|
|
480
|
+
const background = process.env.REASON_AUTOMATION_BACKGROUND_TABS === "1";
|
|
481
|
+
const foreground = async tabId => browserStage("foreground", async () => {
|
|
482
|
+
if (!Number.isSafeInteger(tabId) || tabId <= 0) fail("cloud_automation_browser_target_invalid");
|
|
483
|
+
if (background) return;
|
|
484
|
+
// This CDP call revalidates membership in the same pinned OBU session.
|
|
485
|
+
// Never pick an ambient Chrome tab or trust OBU's logical \`active\` flag.
|
|
486
|
+
checkedBrowserResult(await call("cdp", { tab_id: tabId, method: "Page.bringToFront", params: {} }), "foreground");
|
|
487
|
+
});
|
|
488
|
+
const fileChooserTab = request.tool === "wait_file_chooser" ? args.tab_id
|
|
489
|
+
: request.tool === "call" && args.method === "waitForFileChooser" ? args.params.tabId : undefined;
|
|
490
|
+
if (fileChooserTab !== undefined) {
|
|
491
|
+
// OBU 0.1.42 enables interception but does not enable Page events on a fresh
|
|
492
|
+
// debugger attachment. Use its scoped CDP tool before the unchanged wait.
|
|
493
|
+
await browserStage("provider_call", async () => checkedBrowserResult(
|
|
494
|
+
await call("cdp", { tab_id: fileChooserTab, method: "Page.enable", params: {} }), "provider_call"));
|
|
495
|
+
}
|
|
496
|
+
// Capture must observe the presented target, including the raw RPC spelling.
|
|
497
|
+
if (cdpMethod === "Page.captureScreenshot") await foreground(cdpTabId);
|
|
498
|
+
const result = await browserStage("provider_call", async () => checkedBrowserResult(await call(request.tool, args), "provider_call", cdpMethod === "DOM.setFileInputFiles"));
|
|
499
|
+
const opened = request.tool === "open_tab";
|
|
500
|
+
const created = request.tool === "call" && args.method === "createTab";
|
|
501
|
+
if (opened || created) {
|
|
502
|
+
// Stock OBU 0.1.42 creates background tabs. Its active:true means logical
|
|
503
|
+
// session selection, not the visible browser tab. Preserve the exact result
|
|
504
|
+
// while presenting only the successfully created, returned tab.
|
|
505
|
+
const payload = result.structuredContent?.result;
|
|
506
|
+
if (opened && payload?.navigate?.errorText) throw new BrowserProviderFailure(new Error("cloud_automation_provider_refused"), "navigation", browserRefusalReason(payload.navigate.errorText));
|
|
507
|
+
await foreground(opened ? payload?.tab?.id : payload?.id);
|
|
508
|
+
} else if (request.tool === "navigate" || request.tool === "page_info" || cdpMethod === "Page.navigate") {
|
|
509
|
+
if (result.structuredContent?.result?.errorText) throw new BrowserProviderFailure(new Error("cloud_automation_provider_refused"), "navigation", browserRefusalReason(result.structuredContent.result.errorText));
|
|
510
|
+
await foreground(cdpMethod === "Page.navigate" ? cdpTabId : args.tab_id);
|
|
511
|
+
}
|
|
512
|
+
return result;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
const SIMULATOR_APP = "com.apple.iphonesimulator";
|
|
516
|
+
// Installed Apple Simulator only. Never resolve a caller-supplied path or use
|
|
517
|
+
// LaunchServices' possibly different registered Xcode as launch authority.
|
|
518
|
+
export function installedMacSimulator({ execute = execFileSync, realpath = realpathSync, stat = statSync } = {}) {
|
|
519
|
+
const options = { encoding: "utf8", timeout: 5000, maxBuffer: 4096, stdio: ["ignore", "pipe", "ignore"] };
|
|
520
|
+
let developer, bundle, executable;
|
|
521
|
+
try {
|
|
522
|
+
developer = realpath(execute("/usr/bin/xcode-select", ["-p"], options).trim());
|
|
523
|
+
if (!/^\\/Applications\\/Xcode[^/]*\\.app\\/Contents\\/Developer$/.test(developer)) fail("cloud_computer_simulator_unavailable");
|
|
524
|
+
bundle = realpath(join(developer, "Applications/Simulator.app"));
|
|
525
|
+
executable = realpath(join(bundle, "Contents/MacOS/Simulator"));
|
|
526
|
+
if (bundle !== join(developer, "Applications/Simulator.app") || executable !== join(bundle, "Contents/MacOS/Simulator")) fail("cloud_computer_simulator_unavailable");
|
|
527
|
+
// A writable parent could swap an otherwise signed bundle after validation.
|
|
528
|
+
for (let path = executable; path !== "/"; path = path.slice(0, path.lastIndexOf("/")) || "/") {
|
|
529
|
+
const info = stat(path);
|
|
530
|
+
if (info.uid !== 0 || (info.mode & 0o022) !== 0) fail("cloud_computer_simulator_unavailable");
|
|
531
|
+
}
|
|
532
|
+
const identifier = execute("/usr/bin/plutil", ["-extract", "CFBundleIdentifier", "raw", "-o", "-", join(bundle, "Contents/Info.plist")], options).trim();
|
|
533
|
+
const binary = execute("/usr/bin/plutil", ["-extract", "CFBundleExecutable", "raw", "-o", "-", join(bundle, "Contents/Info.plist")], options).trim();
|
|
534
|
+
if (identifier !== SIMULATOR_APP || binary !== "Simulator") fail("cloud_computer_simulator_unavailable");
|
|
535
|
+
execute("/usr/bin/codesign", ["--verify", "--strict", "-R", "anchor apple", bundle], options);
|
|
536
|
+
} catch { fail("cloud_computer_simulator_unavailable"); }
|
|
537
|
+
return { app: SIMULATOR_APP, bundle, executable };
|
|
538
|
+
}
|
|
539
|
+
export function assertInstalledSimulatorTarget(target, installed) {
|
|
540
|
+
assertSafeComputerTarget(target);
|
|
541
|
+
if (target.app !== installed.app || target.executable !== installed.executable) fail("cloud_computer_simulator_identity_changed");
|
|
542
|
+
}
|
|
543
|
+
export function observeInstalledSimulator(installed, execute = execFileSync) {
|
|
544
|
+
const options = { encoding: "utf8", timeout: 5000, maxBuffer: 1024 * 1024, stdio: ["ignore", "pipe", "ignore"] };
|
|
545
|
+
let rows;
|
|
546
|
+
try { rows = execute("/bin/ps", ["-axo", "pid=,comm="], options).split("\\n"); }
|
|
547
|
+
catch { fail("cloud_computer_simulator_launch_uncertain"); }
|
|
548
|
+
const pids = rows.flatMap(row => {
|
|
549
|
+
const match = /^\\s*(\\d+)\\s+(.+)$/.exec(row);
|
|
550
|
+
return match && match[2] === installed.executable ? [Number(match[1])] : [];
|
|
551
|
+
});
|
|
552
|
+
if (pids.length !== 1 || !Number.isSafeInteger(pids[0]) || pids[0] <= 0) fail("cloud_computer_simulator_launch_uncertain");
|
|
553
|
+
try {
|
|
554
|
+
const processStart = execute("/bin/ps", ["-p", String(pids[0]), "-o", "lstart="], options).trim();
|
|
555
|
+
const executable = execute("/bin/ps", ["-p", String(pids[0]), "-o", "comm="], options).trim();
|
|
556
|
+
const target = { app: installed.app, pid: pids[0], executable, processStart };
|
|
557
|
+
assertInstalledSimulatorTarget(target, installed);
|
|
558
|
+
return target;
|
|
559
|
+
} catch { fail("cloud_computer_simulator_launch_uncertain"); }
|
|
560
|
+
}
|
|
561
|
+
export async function launchInstalledSimulator(call, { platform = process.platform, resolve = installedMacSimulator, execute = execFileSync, observe = observeInstalledSimulator } = {}) {
|
|
562
|
+
if (platform !== "darwin") fail("cloud_computer_simulator_unavailable");
|
|
563
|
+
const installed = resolve();
|
|
564
|
+
const running = parseRunningApps(await call("list_apps", {})).some(app => app.app === installed.app);
|
|
565
|
+
if (!running) {
|
|
566
|
+
// No -n: a repeated observation cannot request a second instance. A timeout
|
|
567
|
+
// is uncertain; never retry open here or kill a potentially pre-existing app.
|
|
568
|
+
try { execute("/usr/bin/open", ["-a", installed.bundle], { timeout: 5000, stdio: "ignore" }); }
|
|
569
|
+
catch { fail("cloud_computer_simulator_launch_uncertain"); }
|
|
570
|
+
}
|
|
571
|
+
// Do not call stock get_app_state here: if a just-listed process exits, its
|
|
572
|
+
// implicit launch uses LaunchServices registration rather than this exact path.
|
|
573
|
+
const target = observe(installed);
|
|
574
|
+
assertInstalledSimulatorTarget(target, installed);
|
|
575
|
+
return { ok: true, launched: "Simulator", target };
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
// A headless Device runs this same file with its installed desktop components
|
|
579
|
+
// (cli/local-automation.ts); cloud machines leave both variables unset.
|
|
580
|
+
export function automationCommand(kind, env = process.env, platform = process.platform) {
|
|
581
|
+
const override = kind === "computer" ? env.REASON_AUTOMATION_OCU : env.REASON_AUTOMATION_OBU;
|
|
582
|
+
if (override) return override;
|
|
583
|
+
return join(platform === "linux" ? "/usr/local/bin" : join(env.HOME,".local/bin"),kind === "computer"?"open-computer-use":"obu");
|
|
584
|
+
}
|
|
585
|
+
// A macOS worker's OCU is its own CLI (\`reason __computer-use mcp|doctor\`), which
|
|
586
|
+
// runs OCU as Reason and answers doctor from Reason's grants without prompting.
|
|
587
|
+
export function computerArgs(args, env = process.env) {
|
|
588
|
+
return env.REASON_AUTOMATION_OCU_SUBCOMMAND ? [env.REASON_AUTOMATION_OCU_SUBCOMMAND, ...args] : args;
|
|
589
|
+
}
|
|
590
|
+
export async function runCloudAutomation(request) {
|
|
591
|
+
if (!record(request) || request.version !== 1 || !["computer","browser"].includes(request.kind)) fail("cloud_automation_request_invalid");
|
|
592
|
+
const home = process.env.HOME; if (!home?.startsWith("/")) fail("cloud_automation_home_unavailable");
|
|
593
|
+
const computer = request.kind === "computer";
|
|
594
|
+
if (!computer && (typeof request.scope !== "string" || !/^reason-cloud-[a-f0-9]{64}$/.test(request.scope))) fail("cloud_automation_scope_invalid");
|
|
595
|
+
const command = automationCommand(computer ? "computer" : "browser");
|
|
596
|
+
if (computer && request.operation === "permission_status") {
|
|
597
|
+
if (process.platform !== "darwin") fail("cloud_computer_permission_status_unavailable");
|
|
598
|
+
let output;
|
|
599
|
+
try {
|
|
600
|
+
output = execFileSync(command, computerArgs(["doctor"]), { encoding: "utf8", timeout: 10000, maxBuffer: 4096 });
|
|
601
|
+
} catch {
|
|
602
|
+
fail("cloud_computer_permission_status_unavailable");
|
|
603
|
+
}
|
|
604
|
+
const permissions = parseMacPermissionStatus(output);
|
|
605
|
+
if (!permissions) fail("cloud_computer_permission_status_unavailable");
|
|
606
|
+
return { ok: true, permissions };
|
|
607
|
+
}
|
|
608
|
+
// Recording is the machine's own ffmpeg, not a browser provider call.
|
|
609
|
+
if (!computer && request.operation === "record_start") return await startBrowserRecording(request.seconds);
|
|
610
|
+
if (!computer && request.operation === "record_stop") return await stopBrowserRecording();
|
|
611
|
+
if (!computer && request.operation !== "discover") validateBrowserTab(request);
|
|
612
|
+
// A headless macOS Device pins the OBU instance of its private browser profile,
|
|
613
|
+
// which OBU's fixed Chrome profile roots never include (cli/local-automation.ts).
|
|
614
|
+
const instance = process.env.REASON_AUTOMATION_OBU_INSTANCE;
|
|
615
|
+
const transport = provider(command,computer?computerArgs(["mcp"]):["mcp","--session-id",request.scope,...(instance?["--profile",instance]:["--browser","chrome","--profile","Default"])]);
|
|
616
|
+
try {
|
|
617
|
+
await transport.rpc("initialize",{protocolVersion:"2025-03-26",capabilities:{},clientInfo:{name:"reason-cloud",version:"1"}});
|
|
618
|
+
transport.notify("notifications/initialized");
|
|
619
|
+
const call = (name,args) => callComputerTool((tool, arguments_) => transport.rpc("tools/call", { name: tool, arguments: arguments_ }), name, args);
|
|
620
|
+
if (!computer) {
|
|
621
|
+
if (request.operation === "discover") return { ok: true, result: await transport.rpc("tools/list", {}) };
|
|
622
|
+
const called = await callDiscoveredBrowserTool((method, params) => transport.rpc(method, params), request);
|
|
623
|
+
const screenshotPath = request.saveScreenshot === true ? saveBrowserScreenshot(called.result) : undefined;
|
|
624
|
+
return { ok: true, ...called, ...(screenshotPath ? { screenshotPath } : {}) };
|
|
625
|
+
}
|
|
626
|
+
if (request.operation === "launch_simulator") return await launchInstalledSimulator(call);
|
|
627
|
+
if (request.operation === "launch_calculator") {
|
|
628
|
+
if (process.platform === "linux") { const child=spawn("/usr/bin/galculator",[],{stdio:"ignore",detached:true,env:process.env}); await new Promise((resolve,reject)=>{child.once("spawn",resolve);child.once("error",reject);});child.unref(); }
|
|
629
|
+
else execFileSync("/usr/bin/open",["-b","com.apple.calculator"],{timeout:5000,stdio:"ignore"});
|
|
630
|
+
return {ok:true,launched:"Calculator"};
|
|
631
|
+
}
|
|
632
|
+
if (request.operation === "list") return {ok:true,apps:parseRunningApps(await call("list_apps",{}))};
|
|
633
|
+
if (typeof request.app !== "string" || request.app.length>256 || /[\\x00-\\x1f/]/.test(request.app)) fail("cloud_computer_window_invalid");
|
|
634
|
+
// Only already-running apps can be selected; never turn a caller string into
|
|
635
|
+
// OCU's implicit application launch behavior.
|
|
636
|
+
const apps=parseRunningApps(await call("list_apps",{})); if (!apps.some(app=>app.app===request.app)) fail("cloud_computer_window_unavailable");
|
|
637
|
+
const installedSimulator = process.platform === "darwin" && request.app === SIMULATOR_APP ? installedMacSimulator() : null;
|
|
638
|
+
const snapshot=await call("get_app_state",computerSnapshotOptions(request));
|
|
639
|
+
const accessibility=accessibilityState(snapshot, process.platform);
|
|
640
|
+
const target=targetFor(snapshot,request.app);
|
|
641
|
+
if (installedSimulator) assertInstalledSimulatorTarget(target, installedSimulator);
|
|
642
|
+
if(request.operation === "snapshot") {
|
|
643
|
+
const screenshotPath = request.saveScreenshot === true ? saveComputerScreenshot(snapshot.content) : undefined;
|
|
644
|
+
return {ok:true,target,content:snapshot.content,...(screenshotPath ? {screenshotPath} : {})};
|
|
645
|
+
}
|
|
646
|
+
if(request.operation !== "act" || !sameComputerTarget(target,request.expected)) fail("cloud_computer_window_changed");
|
|
647
|
+
if (request.action?.kind === "click_element") assertAccessibilityTreeMatches(request.expected, target);
|
|
648
|
+
assertSafeComputerTarget(target,request.action);
|
|
649
|
+
const [tool,args]=actionTool(request.action,request.app,target,accessibility.elements); const result=await call(tool,args);
|
|
650
|
+
return process.platform === "darwin"
|
|
651
|
+
? {ok:true,...macComputerActionObservation(result,target)}
|
|
652
|
+
: {ok:true,target,result};
|
|
653
|
+
} finally { transport.close(); }
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
// Blaxel's process API answers a command from its log file, and while commands
|
|
657
|
+
// run it punches out the head of any log file over a share of a guest-wide
|
|
658
|
+
// budget (floor 256 KiB). A single-line screenshot result above that share comes
|
|
659
|
+
// back headless and unparseable. Results above the inline limit are therefore
|
|
660
|
+
// spooled to a private file that the controller reads back in chunks below that
|
|
661
|
+
// floor (CLOUD_AUTOMATION_INLINE_RESULT_MAX in cloud-automation.ts must match).
|
|
662
|
+
export const CLOUD_AUTOMATION_INLINE_RESULT_MAX = 192 * 1024;
|
|
663
|
+
const SPOOL_PREFIX = "reason-automation-";
|
|
664
|
+
const SPOOL_STALE_MS = 5 * 60_000;
|
|
665
|
+
export function cloudAutomationOutputLine(result, { directory = tmpdir(), now = Date.now() } = {}) {
|
|
666
|
+
// ASCII-only JSON, so the controller's byte-range chunks never split a character.
|
|
667
|
+
const line = JSON.stringify(result).replace(/[\\u007f-\\uffff]/g, char => "\\\\u" + char.charCodeAt(0).toString(16).padStart(4, "0"));
|
|
668
|
+
// A Device worker (REASON_AUTOMATION_INLINE_RESULT=1) chunks stdout itself and has no spool reader.
|
|
669
|
+
if (line.length <= CLOUD_AUTOMATION_INLINE_RESULT_MAX || process.env.REASON_AUTOMATION_INLINE_RESULT === "1") return line;
|
|
670
|
+
// A controller that never finished reading leaves its spool behind; nothing
|
|
671
|
+
// else removes it, so each run clears this user's stale ones first.
|
|
672
|
+
try {
|
|
673
|
+
for (const entry of readdirSync(directory)) {
|
|
674
|
+
if (!entry.startsWith(SPOOL_PREFIX)) continue;
|
|
675
|
+
const path = join(directory, entry);
|
|
676
|
+
try { const info = lstatSync(path); if (info.isDirectory() && info.uid === process.getuid?.() && now - info.mtimeMs > SPOOL_STALE_MS) rmSync(path, { recursive: true, force: true }); } catch {}
|
|
677
|
+
}
|
|
678
|
+
} catch {}
|
|
679
|
+
const spoolDirectory = mkdtempSync(join(directory, SPOOL_PREFIX));
|
|
680
|
+
const path = join(spoolDirectory, "result.json");
|
|
681
|
+
writeFileSync(path, line, { mode: 0o600, flag: "wx" });
|
|
682
|
+
return JSON.stringify({ ok: true, spool: { path, bytes: line.length, sha256: createHash("sha256").update(line).digest("hex") },
|
|
683
|
+
...(result?.actionCompleted === true ? { actionCompleted: true } : {}) });
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
if(process.env.REASON_CLOUD_GATEWAY_EXEC === "1") {
|
|
687
|
+
try {
|
|
688
|
+
let source="";for await(const part of process.stdin){source+=part;if(source.length>65536)fail("cloud_automation_request_invalid");}
|
|
689
|
+
const result=await runCloudAutomation(JSON.parse(source));process.stdout.write(cloudAutomationOutputLine(result)+"\\n");
|
|
690
|
+
} catch(error) {
|
|
691
|
+
process.stdout.write(JSON.stringify(cloudAutomationFailure(error))+"\\n");process.exitCode=1;
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
`;import{spawn as Le}from"child_process";import{basename as Je}from"path";var ze="__desktop-call",je="/usr/bin/setpriv",De=27262976,Me=8192,Pe=2000;function V(t){return Boolean(t)&&typeof t==="object"&&!Array.isArray(t)}function Ze(t){try{return JSON.parse(t)}catch{return null}}function ke(){return[process.execPath,...Je(process.execPath).startsWith("bun")&&process.argv[1]?[process.argv[1]]:[],ze]}function Qe(t,e,r,o={}){r.throwIfAborted();let s=o.command??ke();return new Promise((a,c)=>{let i=(o.spawnProcess??Le)(je,[`--reuid=${t.uid}`,`--regid=${t.gid}`,"--clear-groups","--no-new-privs","--",...s],{cwd:t.home,detached:!0,stdio:["pipe","pipe","pipe"],env:{HOME:t.home,USER:t.username,LOGNAME:t.username,PATH:"/usr/local/bin:/usr/bin:/bin"}}),n=[],f=0,d="",l=null,m=!1,u,S=(x)=>{try{process.kill(-i.pid,x)}catch{i.kill(x)}},C=(x)=>{l??=x,S("SIGTERM"),u??=setTimeout(()=>S("SIGKILL"),Pe)},O=()=>C(new _("cancelled","Reason cancelled the desktop operation."));r.addEventListener("abort",O,{once:!0});let w=(x)=>{if(m)return;m=!0,clearTimeout(u),r.removeEventListener("abort",O),x()};if(i.stdout.on("data",(x)=>{if(f+=x.byteLength,f>De)C(new _("desktop_delegate_failed","Reason's desktop reply is too large."));else n.push(x)}),i.stderr.setEncoding("utf8").on("data",(x)=>{d=(d+x).slice(-Me)}),i.stdin.on("error",()=>{}),i.once("error",()=>w(()=>c(l??new _("desktop_delegate_unavailable","Reason could not start its desktop as the desktop account.")))),i.once("close",(x,Ue)=>w(()=>{if(l)return c(l);let y=Ze(Buffer.concat(n).toString("utf8"));if(V(y)&&y.ok===!0&&V(y.result))return a(y.result);let F=V(y)&&y.ok===!1&&typeof y.code==="string"&&typeof y.message==="string"?new _(y.code.slice(0,120),y.message.slice(0,2000)):null,St=`exited ${x??Ue}${F?"":" with no reply"}`,pt=d.trim().split(`
|
|
695
|
+
`).at(-1)?.slice(-300)??"";if(!F||F.code==="desktop_delegate_failed")process.stderr.write(`Reason worker: desktop call ${St}${d?`: ${d.trim().slice(-500)}`:""}
|
|
696
|
+
`);if(F)return c(F);c(new _("desktop_delegate_failed",`Reason's desktop operation failed (${St}${pt?`: ${pt}`:""}).`))})),r.aborted)O();i.stdin.end(JSON.stringify(e))})}async function At(t,e,r,o={}){let s=await Qe(t,{op:"automation",call:e},r,o);if(!Number.isSafeInteger(s.code)||typeof s.stdout!=="string"||typeof s.stderr!=="string")throw new _("desktop_delegate_failed","Reason's desktop reply is malformed.");return{code:s.code,stdout:Buffer.from(s.stdout,"base64"),stderr:s.stderr}}import{execFile as Jr,spawn as zr}from"child_process";import{closeSync as jr,mkdirSync as Dr,openSync as Mr}from"fs";import{dirname as k,join as Pr}from"path";import{promisify as Zr}from"util";import{spawnSync as j}from"child_process";import{createHash as br,randomUUID as wr}from"crypto";import{chmodSync as xr,existsSync as st,mkdirSync as Sr,readdirSync as es,readFileSync as M,renameSync as pr,rmSync as g,statSync as yr,writeFileSync as Ar}from"fs";import{dirname as gr,isAbsolute as Jt,join as G}from"path";import{chmodSync as Do,existsSync as Ye,mkdirSync as Mo,readlinkSync as Xe,readFileSync as We,renameSync as Po,unlinkSync as Zo,writeFileSync as ko}from"fs";import{homedir as Nt}from"os";import{basename as He,dirname as Ot,isAbsolute as Tt,join as R,normalize as Ke,relative as Ve,resolve as Ee}from"path";var tt=3,ve=1,tr=2,er=1,rr=1,or="Reason may read and write files and run commands in this signed-in OS account, subject to saved restrictions and operating system permissions.",sr="Reason may later execute on this computer inside a Reason sandbox, limited to folders explicitly attached by the user.",ar=[sr,"Ara may later execute on this computer inside an Ara sandbox, limited to folders explicitly attached by the user."];function ir(t){return typeof t==="string"&&ar.includes(t)}var cr=["future_device","updates_only","cli_only"];function qt(t){return typeof t==="string"&&cr.some((e)=>e===t)}function nr(t=process.env,e=Nt()){return t.XDG_CONFIG_HOME?.trim()||R(e,".config")}function et(t=process.env,e=Nt()){let r=z({env:t}),o=nr(t,e);return R(o,gt(o).state==="kept"?"ara":"reason",...r==="staging"?["staging"]:[],"installation.json")}function p(t){return typeof t==="string"&&t.trim().length>0}var dr=/^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;function fr(t){return typeof t==="string"&&dr.test(t)}function J(t){return p(t)&&Tt(t)&&Ke(t)===t}function Ct(t){if(!t||typeof t!=="object"||Array.isArray(t))return!1;let e=t;return J(e.path)&&(e.role==="launcher"&&(e.kind==="file"||e.kind==="symlink")||e.role==="worker_root"&&e.kind==="directory"||e.role==="alias"&&e.kind==="symlink"&&J(e.target))}function Bt(t){return t.owned_paths?.every((e)=>e.role!=="alias"||t.installer==="shell"&&e.path===R(t.install_dir,t.channel==="staging"?"reason-staging":"reason")&&e.target===R(t.install_dir,t.channel==="staging"?"ara-staging":"ara")&&t.owned_paths?.some((r)=>r.role==="launcher"&&r.path===e.target))??!1}function lr(t,e){try{return Ee(Ot(t),Xe(t))===e}catch{return!1}}function rt(t){let e=He(t),r=e==="ara"?"reason":e==="ara-staging"?"reason-staging":null;if(!r)return t;let o=R(Ot(t),r);return lr(o,t)?o:t}function It(t,e){let r=Ve(t,e);return r.length>0&&!r.startsWith("..")&&!Tt(r)}function $t(t){if(t===null)return!0;if(!t||typeof t!=="object"||Array.isArray(t))return!1;let e=t;return e.kind==="shell_profile"&&p(e.location)&&p(e.value)&&(typeof e.added_by_installer==="boolean"||typeof e.added_by_ara==="boolean")&&(e.added_by_installer===void 0||typeof e.added_by_installer==="boolean")&&(e.added_by_ara===void 0||typeof e.added_by_ara==="boolean")}function Ft(t,e){if(e===null)return t==="cli_only";if(!e||typeof e!=="object"||Array.isArray(e))return!1;let r=e;if(r.version!==er||!p(r.recorded_at))return!1;if(t==="future_device")return r.decision==="authorized"&&ir(r.scope);return r.scope===null&&r.decision===(t==="updates_only"?"ask":"disabled")}function _r(t){if(!t||typeof t!=="object"||Array.isArray(t))return null;let e=t;if(e.schema_version!==ve||!v.includes(e.channel)||e.install_method!=="curl"||e.installer!=="shell"||!p(e.installed_at)||!p(e.version)||!p(e.target)||!J(e.install_dir)||!Array.isArray(e.owned_paths)||!e.owned_paths.every(Ct)||!e.owned_paths.every((r)=>It(e.install_dir,r.path))||!Bt(e)||!$t(e.path_entry))return null;return{...e,schema_version:tt,installation_id:null,background_mode:"cli_only",device_consent:null}}function ur(t){if(!t||typeof t!=="object"||Array.isArray(t))return null;let e=t;if(e.schema_version!==tr||!Rt(e)||!qt(e.background_mode)||!Ft(e.background_mode,e.device_consent))return null;return{...e,schema_version:tt,installation_id:null}}function Rt(t){return v.includes(t.channel)&&(t.install_method==="curl"&&t.installer==="shell"||t.install_method==="desktop"&&t.installer==="desktop"&&Array.isArray(t.owned_paths)&&t.owned_paths.length===0&&t.path_entry===null)&&p(t.installed_at)&&p(t.version)&&p(t.target)&&J(t.install_dir)&&Array.isArray(t.owned_paths)&&t.owned_paths.every(Ct)&&t.owned_paths.every((e)=>It(t.install_dir,e.path))&&Bt(t)&&$t(t.path_entry)}function mr(t){let e=_r(t);if(e)return e;let r=ur(t);if(r)return r;if(!t||typeof t!=="object"||Array.isArray(t))return null;let o=t;if(o.schema_version!==tt||!fr(o.installation_id)||!Rt(o)||!qt(o.background_mode)||!Ft(o.background_mode,o.device_consent)||o.desktop_account_access!==void 0&&(o.desktop_account_access===null||typeof o.desktop_account_access!=="object"||Array.isArray(o.desktop_account_access)||o.desktop_account_access.version!==rr||o.desktop_account_access.scope!==or||!p(o.desktop_account_access.recorded_at)||!Number.isFinite(Date.parse(o.desktop_account_access.recorded_at))))return null;return o}function Gt(t=et()){if(!Ye(t))return null;try{return mr(JSON.parse(We(t,"utf8")))}catch{return null}}var at="REASON_WORKER_SERVICE",Nr="worker_identity_unavailable";function zt(t){return{platform:process.platform,home:t,uid:process.getuid?.(),command:(e,r)=>j(e,[...r],{stdio:"ignore"}).status??1,read:(e,r)=>{let o=j(e,[...r],{stdio:["ignore","pipe","ignore"],encoding:"utf8",timeout:1e4});return o.status===0?o.stdout:null},sleep:(e)=>Atomics.wait(new Int32Array(new SharedArrayBuffer(4)),0,0,e),workerIdentity:(e)=>Lt(t,e),insideService:(e)=>Or(e,process.platform,process.getuid?.())}}function Or(t,e,r){if(process.env[at]===t||process.env.XPC_SERVICE_NAME===t)return!0;if(e==="linux")try{return M("/proc/self/cgroup","utf8").includes(`/${t}.service`)}catch{return!0}if(e!=="darwin")return!1;if(typeof r!=="number")return!0;let o=j("/bin/launchctl",["print",`gui/${r}/${t}`],{encoding:"utf8",timeout:5000});if(o.status===Tr)return!1;if(o.error||o.status!==0)return!0;let s=Number(/^\s*pid = (\d+)$/m.exec(o.stdout??"")?.[1]??0);if(!s)return!1;let a=process.pid;for(let c=0;c<64&&a>1;c+=1){if(a===s)return!0;let i=j("/bin/ps",["-o","ppid=","-p",String(a)],{encoding:"utf8",timeout:5000}),n=Number(i.stdout?.trim());if(i.error||i.status!==0||!Number.isSafeInteger(n)||n<=0)return!0;if(n===a)return!1;a=n}return a>1}var Tr=113;class A extends Error{code;constructor(t,e){super(t);this.code=e}}function jt(t,e){if(t.platform!=="darwin"&&t.platform!=="linux")throw new A("A background service needs macOS (launchd) or Linux (systemd --user).");if(t.platform==="darwin")U(t);let o=[Gt(et(e,t.home))?.owned_paths.find((s)=>s.role==="launcher")?.path,E(e,"REASON_CLI_LAUNCHER_PATH")?.trim(),G(t.home,".local","bin",z({env:e})==="staging"?"ara-staging":"ara")];for(let s of o)try{if(s&&Jt(s)&&yr(s).isFile())return rt(s)}catch{}throw new A("A background service runs the installed Reason launcher, which was not found. Install Reason with: curl -fsSL https://reasonmachines.com/install | sh","worker_launcher_missing")}var Dt=/(<key>ProgramArguments<\/key>\s*<array>\n)((?:\s*<string>[^<]*<\/string>\n)*)(\s*<\/array>)/,ot=new RegExp(`(\\n\\s*<key>${D}</key>\\s*<string>)[^<]*(</string>)`),qr=/(<key>EnvironmentVariables<\/key>\s*<dict>[\s\S]*?)(\n\s*<\/dict>)/;function Cr(t){return t.replaceAll("<","<").replaceAll(">",">").replaceAll(""",'"').replaceAll("'","'").replaceAll("&","&")}function Br(t){let e=[...(Dt.exec(t)?.[2]??"").matchAll(/<string>([^<]*)<\/string>/g)].map((o)=>Cr(o[1])),r=e[1]==="--run-worker"?e.slice(2):e;return r[0]?{launcher:r[0],args:r.slice(1)}:null}function Mt(t,e,r){let o=r.workerIdentity?.(t)??null;return{program:o?[o.executable,"--run-worker",t,...e]:[t,...e],identity:o}}function Pt(t){return t.map((e)=>` <string>${N(e)}</string>`).join(`
|
|
697
|
+
`)}function Zt(t,e){let r;try{r=M(t,"utf8")}catch{return null}let o=Br(r);if(!o)return null;let s=Jt(o.launcher)?rt(o.launcher):o.launcher,{program:a,identity:c}=Mt(s,o.args,e),i=r.replace(Dt,(n,f,d,l)=>`${f}${Pt(a)}
|
|
698
|
+
${l}`);if(!c)i=i.replace(ot,"");else if(ot.test(i))i=i.replace(ot,(n,f,d)=>`${f}${N(c.app)}${d}`);else i=i.replace(qr,(n,f,d)=>`${f}
|
|
699
|
+
<key>${D}</key>
|
|
700
|
+
<string>${N(c.app)}</string>${d}`);return i===r?null:i}function Ir(t,e){let r=Zt(t,e);if(r!==null)Z(t,r)}function cs(t,e,r){if(e.platform!=="darwin")return!1;let o=ct(t,e,r);return o!==null&&Zt(o.definition,e)!==null}var P="com.reasonmachines.worker.",it="so.reason.worker.",kt=[P,it];function Qt(t,e=P){return`${e}connect${t==="production"?"":`.${t}`}`}function ns(t,e){return kt.some((r)=>t===Qt(e,r))}function $r(t,e,r){if(e==="connect")return Qt(t.channel,r);let o=br("sha256").update(t.workspaceRoot).digest("hex").slice(0,8);return`${r}${t.name.toLowerCase()}.${o}`}function Fr(t,e){if(e==="darwin")return{directory:G(t,"Library","LaunchAgents"),suffix:".plist"};if(e==="linux")return{directory:G(t,".config","systemd","user"),suffix:".service"};return null}function B(t,e,r,o=P){let s=$r(t,r,o),a=Fr(e.home,e.platform);if(!a)return null;return{label:s,definition:G(a.directory,`${s}${a.suffix}`),logs:e.platform==="darwin"?G(t.stateDirectory,"service.log"):`journalctl --user -u ${s}.service`}}function ct(t,e,r="serve"){return Yt(t,e,r)[0]??null}function Yt(t,e,r){return kt.map((o)=>B(t,e,r,o)).filter((o)=>o!==null&&st(o.definition))}function Xt(t,e,r){let o=B(t,e,r,it);return o&&st(o.definition)?o:null}function Rr(t,e,r){let o=B(t,e,r),s=Xt(t,e,r);if(!o||!s||!st(o.definition)||e.insideService?.(s.label))return null;if(!Gr(o,e))return"current_not_running";if(e.platform==="darwin"){if(typeof e.uid==="number"&&e.uid>0)e.command("/bin/launchctl",["bootout",`gui/${e.uid}/${s.label}`]);return g(s.definition,{force:!0}),"retired"}return e.command("systemctl",["--user","disable","--now",`${s.label}.service`]),g(s.definition,{force:!0}),e.command("systemctl",["--user","daemon-reload"]),"retired"}function Gr(t,e){if(e.platform==="darwin")return typeof e.uid==="number"&&e.uid>0&&e.command("/bin/launchctl",["print",`gui/${e.uid}/${t.label}`])===0;let r=`${t.label}.service`;return e.command("systemctl",["--user","is-enabled","--quiet",r])===0&&Ur.has(e.read("systemctl",["--user","show","--property=ActiveState","--value",r])?.trim()??"")}var Ur=new Set(["active","activating","reloading"]);function Wt(t,e){if(e==="connect")return{args:["connect"],environment:{REASON_CLI_CHANNEL:t.channel},description:"Reason connection of this computer"};return{args:["serve",t.name,"--dir",t.workspaceRoot,...t.executionMode==="confined"?["--sandbox"]:[]],environment:{},description:`Reason machine ${t.name} worker`}}function N(t){return t.replaceAll("&","&").replaceAll("<","<").replaceAll(">",">").replaceAll('"',""").replaceAll("'","'")}function Ut(t,e=!1){if(/[\0-\x1f\x7f]/.test(t))throw new A("A background service cannot use a path with control characters.");let r=t.replaceAll("\\","\\\\").replaceAll('"',"\\\"").replaceAll("%","%%");return`"${e?r.replaceAll("$","$$$$"):r}"`}function hr(t,e,r,o,s,a){let c=Wt(t,e),i=Mt(o,c.args,s),n={HOME:s.home,PATH:a,[at]:r.label,...c.environment,...i.identity?{[D]:i.identity.app}:{}};return{identity:i.identity,plist:`<?xml version="1.0" encoding="UTF-8"?>
|
|
701
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
702
|
+
<plist version="1.0">
|
|
703
|
+
<dict>
|
|
704
|
+
<key>Label</key>
|
|
705
|
+
<string>${N(r.label)}</string>
|
|
706
|
+
<key>AssociatedBundleIdentifiers</key>
|
|
707
|
+
<array>
|
|
708
|
+
<string>${ht}</string>
|
|
709
|
+
</array>
|
|
710
|
+
<key>ProgramArguments</key>
|
|
711
|
+
<array>
|
|
712
|
+
${Pt(i.program)}
|
|
713
|
+
</array>
|
|
714
|
+
<key>EnvironmentVariables</key>
|
|
715
|
+
<dict>
|
|
716
|
+
${Object.entries(n).map(([f,d])=>` <key>${f}</key>
|
|
717
|
+
<string>${N(d)}</string>`).join(`
|
|
718
|
+
`)}
|
|
719
|
+
</dict>
|
|
720
|
+
<key>RunAtLoad</key>
|
|
721
|
+
<true/>
|
|
722
|
+
<key>KeepAlive</key>
|
|
723
|
+
<dict>
|
|
724
|
+
<key>SuccessfulExit</key>
|
|
725
|
+
<false/>
|
|
726
|
+
</dict>
|
|
727
|
+
<key>ThrottleInterval</key>
|
|
728
|
+
<integer>10</integer>
|
|
729
|
+
<key>ExitTimeOut</key>
|
|
730
|
+
<integer>${Math.ceil(t.shutdownGraceMs/1000)+10}</integer>
|
|
731
|
+
<key>ProcessType</key>
|
|
732
|
+
<string>Standard</string>
|
|
733
|
+
<key>Umask</key>
|
|
734
|
+
<integer>63</integer>
|
|
735
|
+
<key>StandardOutPath</key>
|
|
736
|
+
<string>${N(r.logs)}</string>
|
|
737
|
+
<key>StandardErrorPath</key>
|
|
738
|
+
<string>${N(r.logs)}</string>
|
|
739
|
+
</dict>
|
|
740
|
+
</plist>
|
|
741
|
+
`}}function Lr(t,e,r,o,s,a){let c=Wt(t,e);return`[Unit]
|
|
742
|
+
Description=${c.description}
|
|
743
|
+
|
|
744
|
+
[Service]
|
|
745
|
+
ExecStart=${[o,...c.args].map((i)=>Ut(i,!0)).join(" ")}
|
|
746
|
+
Environment=${[`HOME=${s}`,`PATH=${a}`,`${at}=${r.label}`,...Object.entries(c.environment).map(([i,n])=>`${i}=${n}`)].map((i)=>Ut(i)).join(" ")}
|
|
747
|
+
Restart=on-failure
|
|
748
|
+
RestartSec=10
|
|
749
|
+
TimeoutStopSec=${Math.ceil(t.shutdownGraceMs/1000)+10}
|
|
750
|
+
UMask=0077
|
|
751
|
+
|
|
752
|
+
[Install]
|
|
753
|
+
WantedBy=default.target
|
|
754
|
+
`}function Z(t,e){Sr(gr(t),{recursive:!0,mode:448});let r=`${t}.${wr()}.tmp`;try{Ar(r,e,{mode:384,flag:"wx"}),xr(r,384),pr(r,t)}finally{g(r,{force:!0})}}function U(t){if(typeof t.uid!=="number"||!Number.isSafeInteger(t.uid)||t.uid<=0)throw new A("Set up a background service as your own user, not with sudo.");return`gui/${t.uid}`}function ds(t){let{config:e,launcher:r,host:o}=t,s=t.kind??"serve",a=Xt(e,o,s),c=a!==null&&o.insideService?.(a.label)===!0,i=B(e,o,s,c?it:P);if(!i)throw new A("A background service needs macOS (launchd) or Linux (systemd --user).");if(o.platform==="darwin")U(o);if(o.platform==="linux"&&o.command("systemctl",["--user","show-environment"])!==0)throw new A("No background service here: this computer has no systemd user session.","worker_service_manager_missing");let n=o.platform==="darwin"?hr(e,s,i,r,o,t.path):null;if(Z(i.definition,n?n.plist:Lr(e,s,i,r,o.home,t.path)),Ht(e,o,i,c?null:a),n)return n.identity?i:{...i,identityProblem:Nr};return{...i,lingering:o.command("loginctl",["enable-linger"])===0}}function Ht(t,e,r,o){if(e.platform==="darwin"){let c=U(e);if(o)e.command("/bin/launchctl",["bootout",`${c}/${o.label}`]);if(Kt(t,r,e)){if(o)g(o.definition,{force:!0});return}if(g(r.definition,{force:!0}),o)e.command("/bin/launchctl",["bootstrap",c,o.definition]);throw new A("Could not start the background service (launchctl bootstrap failed).")}let s=`${r.label}.service`,a=o?`${o.label}.service`:null;if(e.command("systemctl",["--user","daemon-reload"])!==0||a!==null&&e.command("systemctl",["--user","disable","--now",a])!==0||e.command("systemctl",["--user","enable",s])!==0||e.command("systemctl",["--user","restart",s])!==0){if(e.command("systemctl",["--user","disable",s]),g(r.definition,{force:!0}),e.command("systemctl",["--user","daemon-reload"]),a)e.command("systemctl",["--user","enable","--now",a]);throw new A("Could not start the background service (systemctl --user failed).")}if(o)g(o.definition,{force:!0}),e.command("systemctl",["--user","daemon-reload"])}function Kt(t,e,r){let o=U(r);r.command("/bin/launchctl",["bootout",`${o}/${e.label}`]);let s=(Math.ceil(t.shutdownGraceMs/1000)+10)*1000;for(let a=0,c=100;;c=Math.min(c*2,2000)){if(r.command("/bin/launchctl",["bootstrap",o,e.definition])===0)return!0;if(a>=s)return!1;r.sleep(c),a+=c}}function fs(t,e,r){let o=ct(t,e,r);if(!o)return null;let s=e.insideService?.(o.label)===!0;if(e.platform==="darwin"&&!s)Ir(o.definition,e);let a=B(t,e,r);if(a&&o.label!==a.label&&!s)return Z(a.definition,M(o.definition,"utf8").replaceAll(o.label,a.label)),Ht(t,e,a,o),a;let c=e.platform==="darwin"&&s,i;if(c)i=e.command("/bin/launchctl",["kickstart","-k",`${U(e)}/${o.label}`])===0;else if(e.platform==="darwin")i=Kt(t,o,e);else e.command("systemctl",["--user","enable",`${o.label}.service`]),i=e.command("systemctl",["--user","restart",`${o.label}.service`])===0;if(!i)throw new A(`Could not restart the background service ${o.label}.`);if(a&&o.label===a.label)Rr(t,e,r);return o}function ls(t,e,r,o){let s=ct(t,r,o),a=B(e,r,o);if(r.platform!=="darwin"||!s||!a)return;let c=`<string>${N(s.logs)}</string>`,i=M(s.definition,"utf8");if(i.includes(c))Z(s.definition,i.replaceAll(c,`<string>${N(a.logs)}</string>`))}function _s(t,e,r="serve"){let o=Yt(t,e,r);for(let s of o){if(e.platform==="darwin"){if(typeof e.uid==="number"&&e.uid>0)e.command("/bin/launchctl",["bootout",`gui/${e.uid}/${s.label}`]);g(s.definition,{force:!0});continue}e.command("systemctl",["--user","disable","--now",`${s.label}.service`]),g(s.definition,{force:!0}),e.command("systemctl",["--user","daemon-reload"])}return o[0]??null}var kr=Zr(Jr),Vt="x-apple.systempreferences:com.apple.preference.security",Qr={accessibility:`${Vt}?Privacy_Accessibility`,screen:`${Vt}?Privacy_ScreenCapture`},Yr=64,h=new Map;function Q(t,e){let r=h.get(t);if(r?.has(e))return!1;if(!r){if(h.size>=Yr)h.delete(h.keys().next().value);r=new Set,h.set(t,r)}return r.add(e),!0}function Xr(){let t=T(),e=jt(zt(t),process.env),r=Pr(t,".local","share","reason","desktop-install.log");Dr(k(r),{recursive:!0,mode:448});let o=Mr(r,"a",384);try{let s=zr(e,["desktop","install"],{detached:!0,stdio:["ignore",o,o],env:{...process.env,REASON_DESKTOP_BACKGROUND:"1"}});s.on("error",()=>{return}),s.unref()}finally{jr(o)}}var Y={open:async(t,e)=>{await kr("/usr/bin/open",t,{signal:e,timeout:1e4,maxBuffer:8192})},request:(t,e)=>re(t,e),tile:oe,installDesktop:Xr},Et=!1;async function vt(t,e,r){try{await e.open(["-a","Google Chrome",t],r)}catch{r.throwIfAborted(),await e.open([t],r)}}async function se(t,e,r,o){if(await r.open([Qr[e]],o).catch(()=>{}),t[0]&&!o.aborted)r.tile(k(k(k(t[0]))),e)}async function Wr(t,e,r,o,s){for(let a of r){if(!Q(t,`grant:${a}`))continue;s.throwIfAborted(),await o.request(e,a).catch(()=>null),await se(e,a,o,s)}}var Hr={name:"open_tab",description:"Open a web page in the user's own Chrome on this Mac. Reason cannot read, screenshot or click in that tab, and no tab_id is returned: browser control needs Reason's Chrome extension, whose Chrome Web Store page Reason opened for the user.",inputSchema:{type:"object",properties:{url:{type:"string",description:"An http or https URL."}},required:["url"],additionalProperties:!1}};function te(t){return{code:0,stderr:"",stdout:Buffer.from(JSON.stringify({ok:!0,...t}))}}function Kr(t){if(typeof t!=="string")return null;try{let e=new URL(t);return e.protocol==="https:"||e.protocol==="http:"?e.href:null}catch{return null}}async function nt(t,e=Y){let{call:r,runId:o,signal:s}=t;if(t.desktopMissing&&!Et){Et=!0;try{e.installDesktop()}catch{}}if(r.kind==="computer"){if(t.identity.length>0&&Q(o,"grant:probe")){let i=await e.request(t.identity).catch(()=>null),n=i?[...i.accessibility?[]:["accessibility"],...i.screen?[]:["screen"]]:[];for(let f of n)Q(o,`grant:${f}`);for(let f of n)await se(t.identity,f,e,s)}throw new _("automation_computer_setup_required","Computer use is not set up on this Mac yet; Reason asked macOS for any missing permission.")}if(Q(o,"page:chrome-extension"))await vt(ee,e,s).catch(()=>{});if(r.operation==="discover")return te({result:{tools:[Hr]}});let a=r.arguments,c=r.operation==="call"&&r.tool==="open_tab"&&a&&typeof a==="object"&&!Array.isArray(a)?Kr(a.url):null;if(!c)throw new _("automation_browser_setup_required","Browser control is not set up on this Mac yet; Reason opened its Chrome Web Store page. open_tab still opens pages.");return await vt(c,e,s),te({result:{content:[{type:"text",text:JSON.stringify({opened:c,browser:"the user's Chrome",controllable:!1,note:"Opened for the user to see. Reason cannot read or act in this tab until Reason's Chrome extension is added (its Chrome Web Store page is open)."})}]}})}async function ae(t,e=Y){if(t.call.kind!=="computer"||t.call.operation!=="permission_status"||t.output.code!==0||t.identity.length===0)return t.output;let r;try{r=JSON.parse(t.output.stdout.toString("utf8"))}catch{return t.output}if(!r||typeof r!=="object"||Array.isArray(r))return t.output;let o=r.permissions;if(!o||typeof o!=="object")return t.output;let s=o,a=[...s.accessibility==="missing"?["accessibility"]:[],...s.screenRecording==="missing"?["screen"]:[]];if(a.length===0)return t.output;return await Wr(t.runId,t.identity,a,e,t.signal),{...t.output,stdout:Buffer.from(JSON.stringify({...r,requested:a}))}}var mt=172032,ao=18874368,io=8192,co=65536,no=55000,fo=60000,lo=4,_o=40000,uo=/^[0-9a-f-]{36}$/,de=yt,mo=ye();function W(t){return Boolean(t)&&typeof t==="object"&&!Array.isArray(t)}function H(){throw new _("invalid_automation_request","Reason automation request is malformed.")}function Ne(t){let e=Object.keys(t).sort().join(",");if(e==="call"||e==="browser,call"){let r=t.call,o=e==="browser,call";if(!W(r)||r.version!==1||r.kind!=="computer"&&r.kind!=="browser"||o&&(t.browser!==me||r.kind!=="browser")||JSON.stringify(r).length>co)H();return{call:r,userChrome:o}}if(e==="offset,output_id"){if(typeof t.output_id!=="string"||!uo.test(t.output_id)||!Number.isSafeInteger(t.offset)||t.offset<0)H();return{output_id:t.output_id,offset:t.offset}}return H()}function Ys(t=process.platform,e=$){if(t==="darwin")return!0;return t==="linux"&&e()?.features.computer===!0}var bo="__computer-use";function wo(t){let e=(r)=>r?"granted":"missing";return`Permissions: accessibility=${e(t.accessibility)}, screenRecording=${e(t.screen)}
|
|
755
|
+
`}function xo(t,e,r,o={stdin:process.stdin,stdout:process.stdout}){return new Promise((s)=>{let a=I(t,e,{env:r,stdio:["pipe","pipe","inherit"]}),c=(d)=>{a.kill(d)},i=["SIGTERM","SIGINT","SIGHUP"];for(let d of i)process.on(d,c);o.stdin.pipe(a.stdin),a.stdin.on("error",()=>{return}),a.stdout.pipe(o.stdout,{end:!1});let n=!1,f=(d)=>{if(n)return;n=!0;for(let l of i)process.off(l,c);o.stdin.unpipe(a.stdin),o.stdout.write("",()=>s(d))};a.once("error",()=>f(127)),a.once("close",(d)=>f(d??1))})}async function Xs(t,e={}){let r=e.stderr??process.stderr,o=(e.identity??wt)(),s=(e.components??$)();if(o.length===0||s?.platform!=="darwin-arm64")return r.write(`Reason computer use: Reason's background identity cannot run Open Computer Use here (computer_use_identity_unavailable)
|
|
756
|
+
`),1;if(t.length===1&&t[0]==="doctor"){let i=await(e.grants??Se)(o);if(!i)return r.write(`Reason computer use: Reason's permissions could not be read (computer_use_permission_status_unavailable)
|
|
757
|
+
`),1;return(e.stdout??process.stdout).write(wo(i)),0}if(t.length!==1||t[0]!=="mcp")return r.write(`Usage: reason __computer-use mcp|doctor
|
|
758
|
+
`),2;let[a,...c]=o;return(e.run??xo)(a,[...c,s.ocu,"mcp"],{...process.env,OPEN_COMPUTER_USE_DISABLE_APP_AGENT_PROXY:"1"})}function Ws(t=$){return t()?.userChrome??{code:"user_chrome_browser_use_missing"}}function Oe(t,e,r,o){return new Promise((s)=>{let a=I(t,e,{env:r,stdio:"ignore"}),c=setTimeout(()=>{a.kill("SIGKILL"),s(-1)},o);a.once("error",()=>{clearTimeout(c),s(-1)}),a.once("exit",(i)=>{clearTimeout(c),s(i??-1)})})}function So(t,e,r,o){return new Promise((s)=>{let a=I(t,e,{env:r,stdio:["ignore","pipe","ignore"]}),c="";a.stdout.setEncoding("utf8").on("data",(n)=>{c+=n});let i=setTimeout(()=>{a.kill("SIGKILL"),s(null)},o);a.once("error",()=>{clearTimeout(i),s(null)}),a.once("close",(n)=>{clearTimeout(i),s(n===0?c:null)})})}var po="/tmp/open-browser-use",yo=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/,Ao=33554432;function go(t,e){let r;try{r=t===null?null:JSON.parse(t)}catch{return null}let o=W(r)&&W(r.result)?r.result.metadata:null;if(!W(o)||o.extensionId!==e)return null;return typeof o.extensionInstanceId==="string"&&yo.test(o.extensionInstanceId)?o.extensionInstanceId:null}async function Te(t,e,r){let o=r.storages.map((i)=>{try{return K(i).filter((n)=>n.endsWith(".log")||n.endsWith(".ldb")).map((n)=>b(i,n))}catch{return[]}}),s;try{s=o.some((i)=>i.length>0)?K(r.socketDir).filter((i)=>i.endsWith(".sock")):[]}catch{return[]}let a=[];for(let i of s){let n=b(r.socketDir,i),f=go(await So(t,["info","--socket",n,"--timeout","2s"],e,5000),r.extensionId);if(f)a.push({instance:f,socket:n})}let c=[];for(let[i,n]of(a.length>0?o:[]).entries())for(let f of n)try{if(oo(f).size>Ao)continue;let d=Ae(f),l=a.find((m)=>d.includes(m.instance));if(l){c.push({storage:r.storages[i],...l});break}}catch{}return c}async function No(t,e,r){return(await Te(t,e,r))[0]?.instance??null}function qe(t,e,r){return No(t,e,{socketDir:po,extensionId:lt,storages:[b(r,"browser-profile","Default","Local Extension Settings",lt)]})}var fe=/^(Default|Profile ([0-9]+))$/;async function Oo(t,e,r=T(),o=xt){let s=b(r,"Library","Application Support","Google","Chrome"),a=[];try{a=K(s)}catch(d){if(d.code==="EPERM")throw new _("browser_use_chrome_access_denied","macOS blocked Reason from reading Chrome's profiles. Allow Full Disk Access for Reason on this Mac and try again.")}let c=(d)=>Number(fe.exec(d)?.[2]??-1),i=a.filter((d)=>fe.test(d)&&_t(b(s,d,"Extensions",X))).sort((d,l)=>c(d)-c(l)),n=(d)=>b(s,d,"Local Extension Settings",X);return(await Te(t,e,{socketDir:o,extensionId:X,storages:i.map(n)})).map(({storage:d,instance:l,socket:m})=>({profile:i.find((u)=>n(u)===d),instance:l,socket:m}))}async function To(t,e,r=T(),o=xt){let s=(await Oo(t,e,r,o))[0]?.instance;if(!s)throw new _("automation_user_chrome_not_connected","No Chrome profile on this Mac is connected to Reason. Open Chrome with the Reason extension, then try again.");return s}function le(t){return new Promise((e)=>{let r=so({path:t}),o=(s)=>{r.destroy(),e(s)};r.setTimeout(1000,()=>o(!1)),r.once("error",()=>o(!1)),r.once("connect",()=>o(!0))})}async function Ce(t,e){let r=Date.now()+e;while(Date.now()<r){if(await t())return!0;await new Promise((o)=>setTimeout(o,200))}return!1}var dt=new Map;function ft(t,e){let r=dt.get(t);if(!r)r=e().finally(()=>dt.delete(t)),dt.set(t,r);return r}async function qo(t,e){if(await le(t))return;if(ro(t,{force:!0}),await Oe("dbus-daemon",["--session",`--address=unix:path=${t}`,"--fork"],e,1e4)!==0||!await Ce(()=>le(t),5000))throw new _("automation_unavailable","Reason could not start the desktop session bus.")}async function _e(t,e,r){let o=t.platform==="darwin-arm64"?async()=>await qe(t.nativeHost,e,r)!==null:async()=>await Oe(t.nativeHost,["info"],e,5000)===0;if(await o())return;let s=["--profile-directory=Default",`--load-extension=${t.extensionDir}`,"--no-first-run","--no-default-browser-check","--window-size=1280,800","about:blank"],a,c=null,i=0;if(t.platform==="darwin-arm64"){let d=b(r,"browser-profile");L(d);let l=b(d,"NativeMessagingHosts");ce(l,{recursive:!0,mode:448});let m=bt(t.platform).nativeHostManifests[0];ie(m,b(l,ne(m)));let u=t.browser.slice(0,t.browser.indexOf(".app/")+4),S=s.filter((C)=>C!=="about:blank").concat("--no-startup-window","--use-mock-keychain");a=I("/usr/bin/open",["-g","-n","-a",u,"--args",`--user-data-dir=${d}`,...S],{env:e,stdio:"ignore"})}else{let d=b(e.HOME,".config","google-chrome"),l=b(d,"NativeMessagingHosts");ce(l,{recursive:!0,mode:448});let m=bt(t.platform).nativeHostManifests[0];ie(m,b(l,ne(m)));let u=process.geteuid?.()===0?["--no-sandbox"]:[];c=b(r,"browser.log");let S=vr(c,"a",384);i=Er(S).size,a=I(t.browser,[`--user-data-dir=${d}`,...s,...u,"--force-renderer-accessibility"],{detached:!0,stdio:["ignore",S,S],env:{...e,ACCESSIBILITY_ENABLED:"1"}}),Vr(S)}let n=!1;a.once("error",()=>{n=!0}),a.once("exit",(d)=>{n||=d!==0}),a.unref();let f=!1;if(await Ce(async()=>n||(f=await o()),_o),!f){if(c&&Ae(c).subarray(i).toString("utf8").includes("No usable sandbox"))throw new _("automation_browser_sandbox_unavailable","Chromium found no usable sandbox on this machine. Run the worker as a non-root user with unprivileged user namespaces enabled, or in a container with seccomp unconfined.");throw new _("automation_browser_unavailable","Reason could not connect the desktop browser.")}}async function Hs(t){await $e(t,!0)}function Ks(t){return Be(t,Ie(t))}function Be(t,e){return t.platform==="darwin-arm64"?b(e,"browser-profile"):b(e,"home",".config","google-chrome")}function Ie(t){let e=b(t.root,"automation");if(!_t(e)||!Co(t.automation))return t.automation;if(!Bo(Be(t,e)))try{eo(e,t.automation)}catch{}return _t(e)?e:t.automation}function Co(t){try{return K(t).length===0}catch(e){return e.code==="ENOENT"}}function Bo(t){let e;try{e=to(b(t,"SingletonLock"))}catch{return!1}let r=Number(e.slice(e.lastIndexOf("-")+1));if(!Number.isSafeInteger(r)||r<=0)return!0;try{return process.kill(r,0),!0}catch(o){return o.code==="EPERM"}}async function $e(t,e){we({root:t.root,automation:t.automation,extensionDir:t.extensionDir});let r=Ie(t);L(r);let o=ge().username,s={USER:o,LOGNAME:o,REASON_AUTOMATION_OCU:t.ocu,REASON_AUTOMATION_OBU:t.nativeHost};if(t.platform==="darwin-arm64"){let d={...s,HOME:T(),PATH:`${ut(t.node)}:/usr/bin:/bin:/usr/sbin:/sbin`,REASON_AUTOMATION_OCU:process.execPath,REASON_AUTOMATION_OCU_SUBCOMMAND:bo};if(!e)return d;await ft("browser",()=>_e(t,d,r));let l=await qe(t.nativeHost,d,r);if(!l)throw new _("automation_browser_unavailable","Reason could not connect the desktop browser.");return{...d,REASON_AUTOMATION_OBU_INSTANCE:l,REASON_AUTOMATION_BACKGROUND_TABS:"1"}}let a=await Ge(),c=b(r,"home"),i=b(r,"runtime");L(c),L(i);let n=b(r,"bus"),f={...s,HOME:c,PATH:`${ut(t.node)}:/usr/bin:/bin`,DISPLAY:a.display,XAUTHORITY:a.xauthority,XDG_RUNTIME_DIR:i,DBUS_SESSION_BUS_ADDRESS:`unix:path=${n}`,...a.sound?{PULSE_SERVER:pe(a.sound)}:{}};if(await ft("bus",()=>qo(n,f)),e)await ft("browser",()=>_e(t,f,r));return f}async function Io(t,e,r){let o=ge().username,s={USER:o,LOGNAME:o,HOME:T(),PATH:`${ut(t.node)}:/usr/bin:/bin:/usr/sbin:/sbin`,REASON_AUTOMATION_OBU:e,REASON_AUTOMATION_BACKGROUND_TABS:"1"},a=await To(e,s);return r.throwIfAborted(),{...s,REASON_AUTOMATION_OBU_INSTANCE:a}}function Fe(t,e,r,o){if(typeof de!=="string")return Promise.reject(new _("automation_unavailable","This Reason build has no automation runtime."));let s=de;return new Promise((a,c)=>{let i=I(t,["--input-type=module","--eval",s],{env:{...e,REASON_CLOUD_GATEWAY_EXEC:"1",REASON_AUTOMATION_INLINE_RESULT:"1"},stdio:["pipe","pipe","pipe"],detached:!0}),n=[],f=0,d="",l=null,m=!1,u=(w)=>{l??=w;try{process.kill(-i.pid,"SIGKILL")}catch{i.kill("SIGKILL")}},S=(w)=>{if(m)return;m=!0,clearTimeout(C),o.removeEventListener("abort",O),w()},C=setTimeout(()=>u(new _("automation_timed_out","Reason automation did not finish in time.")),no),O=()=>u(new _("cancelled","Reason cancelled the automation call."));if(o.addEventListener("abort",O,{once:!0}),i.stdout.on("data",(w)=>{if(f+=w.byteLength,f>ao)u(new _("automation_output_too_large","Reason automation output is too large."));else n.push(w)}),i.stderr.setEncoding("utf8").on("data",(w)=>{d=(d+w).slice(-io)}),i.stdin.on("error",()=>{}),i.once("error",()=>S(()=>c(l??new _("automation_unavailable","Reason could not start the automation runtime.")))),i.once("close",(w)=>S(()=>{if(l)c(l);else a({code:w??1,stdout:Buffer.concat(n),stderr:d})})),o.aborted)O();i.stdin.end(r)})}var q=new Map;function $o(t,e,r){for(let[s,a]of q)if(a.expiresAt<=r)q.delete(s);while(q.size>=lo)q.delete(q.keys().next().value);let o=ye();return q.set(o,{runId:t,bytes:e,expiresAt:r+fo}),o}async function Re(t,e,r,o={}){let s=await(o.environment??$e)(t,e.kind==="browser");return r.throwIfAborted(),await(o.runtime??Fe)(t.node,s,JSON.stringify(e),r)}async function Vs(t,e){let r=Ne({call:t});if(!("call"in r))H();let o=$();if(!o?.features.computer||r.call.kind==="browser"&&!o.features.browser)throw new _("automation_unavailable","Run `reason desktop install` on this machine to enable computer and browser use.");return await Re(o,r.call,e)}function ue(t,e,r){let o=t.stdout.byteLength>mt?$o(e,t.stdout,r):void 0;return{code:t.code,stderr:t.stderr,instance_id:mo,output_bytes:t.stdout.byteLength,chunk:t.stdout.subarray(0,mt).toString("base64"),...o?{output_id:o}:{}}}async function Es(t,e={}){let r=Ne(t.payload),o=(e.now??Date.now)();if("output_id"in r){let u=q.get(r.output_id);if(!u||u.runId!==t.runId||u.expiresAt<=o||r.offset>u.bytes.byteLength)throw new _("automation_output_unavailable","Reason automation output expired.");return{chunk:u.bytes.subarray(r.offset,r.offset+mt).toString("base64")}}let s=(e.components??$)(),a=(e.platform??process.platform)==="darwin",c=a?(e.macIdentity??wt)():[],i=e.macSetup??Y,n=!s?.features.computer||r.call.kind==="browser"&&!r.userChrome&&!s.features.browser;if(a&&(n||c.length===0||r.call.kind==="browser"&&r.userChrome&&!t.userChrome))return ue(await nt({runId:t.runId,call:r.call,identity:c,desktopMissing:n,signal:t.signal},i),t.runId,o);if(!s?.features.computer)throw new _("automation_unavailable","Run `reason desktop install` on this machine to enable computer and browser use.");let f=r.userChrome?t.userChrome:void 0;if(r.userChrome&&!f)throw new _("automation_user_chrome_unavailable","Your own Chrome is not enabled on this computer: run `reason desktop install` on this Mac (and `reason connect --user-chrome on` if you turned it off).");if(r.call.kind==="browser"&&!f&&!s.features.browser)throw new _("automation_browser_not_installed",s.platform==="darwin-arm64"?"Browser use is unavailable on this machine: re-run `reason desktop install`.":"Browser use is unavailable on this machine: install a non-snap Chromium at /usr/bin/chromium, then re-run `reason desktop install`.");t.signal.throwIfAborted();let d=await xe(t.runId,t.signal,e.now),l="account"in e?e.account??null:be(),m;try{if(f){let u=await(e.userChromeEnvironment??Io)(s,f,t.signal);t.signal.throwIfAborted(),m=await(e.runtime??Fe)(s.node,u,JSON.stringify(r.call),t.signal)}else if(l)m=await(e.delegate??At)(l,r.call,t.signal);else m=await Re(s,r.call,t.signal,e)}catch(u){if(!a||!(u instanceof _)||u.code!=="automation_user_chrome_not_connected")throw u;m=await nt({runId:t.runId,call:r.call,identity:c,desktopMissing:!1,signal:t.signal},i)}finally{d()}if(a)m=await ae({runId:t.runId,call:r.call,identity:c,output:m,signal:t.signal},i);return ue(m,t.runId,o)}
|
|
759
|
+
export{Gt as lc,at as mc,zt as nc,A as oc,jt as pc,cs as qc,Qt as rc,ns as sc,ct as tc,Rr as uc,ds as vc,fs as wc,ls as xc,_s as yc,Qe as zc,mt as Ac,Ne as Bc,Ys as Cc,xo as Dc,Xs as Ec,Ws as Fc,Oo as Gc,To as Hc,Hs as Ic,Ks as Jc,Vs as Kc,Es as Lc};
|