@mirasim/computer-use 0.0.0-stage → 0.1.0-rc.3

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.
Files changed (341) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +16 -0
  3. package/README.md +154 -2
  4. package/THIRD_PARTY_NOTICES +488 -0
  5. package/bin/mirasim-cu.mjs +30 -0
  6. package/dist/entries/cli.d.ts +2 -0
  7. package/dist/entries/cli.js +32 -0
  8. package/dist/entries/commands/approvals.d.ts +1 -0
  9. package/dist/entries/commands/approvals.js +17 -0
  10. package/dist/entries/commands/bridge-host.d.ts +1 -0
  11. package/dist/entries/commands/bridge-host.js +7 -0
  12. package/dist/entries/commands/bridge.d.ts +1 -0
  13. package/dist/entries/commands/bridge.js +16 -0
  14. package/dist/entries/commands/browser-state.d.ts +1 -0
  15. package/dist/entries/commands/browser-state.js +8 -0
  16. package/dist/entries/commands/capture-server.d.ts +1 -0
  17. package/dist/entries/commands/capture-server.js +5 -0
  18. package/dist/entries/commands/help.d.ts +2 -0
  19. package/dist/entries/commands/help.js +1 -0
  20. package/dist/entries/commands/native-host-options.d.ts +32 -0
  21. package/dist/entries/commands/native-host-options.js +7 -0
  22. package/dist/entries/commands/setup-macos.d.ts +1 -0
  23. package/dist/entries/commands/setup-macos.js +4 -0
  24. package/dist/entries/commands/setup.d.ts +1 -0
  25. package/dist/entries/commands/setup.js +15 -0
  26. package/dist/entries/mcp.d.ts +1 -0
  27. package/dist/entries/mcp.js +6 -0
  28. package/dist/entries/version.d.ts +1 -0
  29. package/dist/entries/version.js +1 -0
  30. package/dist/index.d.ts +11 -0
  31. package/dist/index.js +1 -0
  32. package/dist/platform/browser/acquire.d.ts +15 -0
  33. package/dist/platform/browser/acquire.js +1 -0
  34. package/dist/platform/browser/bridge/acquire-bridge.d.ts +7 -0
  35. package/dist/platform/browser/bridge/acquire-bridge.js +3 -0
  36. package/dist/platform/browser/bridge/client.d.ts +69 -0
  37. package/dist/platform/browser/bridge/client.js +1 -0
  38. package/dist/platform/browser/bridge/extension-channel.d.ts +12 -0
  39. package/dist/platform/browser/bridge/extension-channel.js +1 -0
  40. package/dist/platform/browser/bridge/extension-id.d.ts +8 -0
  41. package/dist/platform/browser/bridge/extension-id.js +1 -0
  42. package/dist/platform/browser/bridge/extension-updates.d.ts +37 -0
  43. package/dist/platform/browser/bridge/extension-updates.js +1 -0
  44. package/dist/platform/browser/bridge/model-commands.d.ts +20 -0
  45. package/dist/platform/browser/bridge/model-commands.js +1 -0
  46. package/dist/platform/browser/bridge/model-core.d.ts +59 -0
  47. package/dist/platform/browser/bridge/model-core.js +1 -0
  48. package/dist/platform/browser/bridge/model.d.ts +4 -0
  49. package/dist/platform/browser/bridge/model.js +1 -0
  50. package/dist/platform/browser/bridge/native-frames.d.ts +9 -0
  51. package/dist/platform/browser/bridge/native-frames.js +1 -0
  52. package/dist/platform/browser/bridge/native-host.d.ts +24 -0
  53. package/dist/platform/browser/bridge/native-host.js +5 -0
  54. package/dist/platform/browser/bridge/runtime-contexts.d.ts +12 -0
  55. package/dist/platform/browser/bridge/runtime-contexts.js +1 -0
  56. package/dist/platform/browser/bridge/server-core.d.ts +66 -0
  57. package/dist/platform/browser/bridge/server-core.js +1 -0
  58. package/dist/platform/browser/bridge/server.d.ts +13 -0
  59. package/dist/platform/browser/bridge/server.js +1 -0
  60. package/dist/platform/browser/bridge-tabs.d.ts +22 -0
  61. package/dist/platform/browser/bridge-tabs.js +1 -0
  62. package/dist/platform/browser/cdp-connection.d.ts +3 -0
  63. package/dist/platform/browser/cdp-connection.js +1 -0
  64. package/dist/platform/browser/chrome-binary.d.ts +6 -0
  65. package/dist/platform/browser/chrome-binary.js +1 -0
  66. package/dist/platform/browser/config.d.ts +29 -0
  67. package/dist/platform/browser/config.js +1 -0
  68. package/dist/platform/browser/devtools-port.d.ts +3 -0
  69. package/dist/platform/browser/devtools-port.js +2 -0
  70. package/dist/platform/browser/downloads.d.ts +22 -0
  71. package/dist/platform/browser/downloads.js +1 -0
  72. package/dist/platform/browser/driver-connect.d.ts +13 -0
  73. package/dist/platform/browser/driver-connect.js +1 -0
  74. package/dist/platform/browser/driver-core.d.ts +50 -0
  75. package/dist/platform/browser/driver-core.js +1 -0
  76. package/dist/platform/browser/driver-tabs.d.ts +16 -0
  77. package/dist/platform/browser/driver-tabs.js +1 -0
  78. package/dist/platform/browser/driver-verbs.d.ts +13 -0
  79. package/dist/platform/browser/driver-verbs.js +2 -0
  80. package/dist/platform/browser/driver.d.ts +38 -0
  81. package/dist/platform/browser/driver.js +3 -0
  82. package/dist/platform/browser/focus-guard.d.ts +2 -0
  83. package/dist/platform/browser/focus-guard.js +1 -0
  84. package/dist/platform/browser/journal.d.ts +21 -0
  85. package/dist/platform/browser/journal.js +2 -0
  86. package/dist/platform/browser/keep-in-tab.d.ts +1 -0
  87. package/dist/platform/browser/keep-in-tab.js +1 -0
  88. package/dist/platform/browser/messages.d.ts +25 -0
  89. package/dist/platform/browser/messages.js +8 -0
  90. package/dist/platform/browser/navigate.d.ts +4 -0
  91. package/dist/platform/browser/navigate.js +2 -0
  92. package/dist/platform/browser/occupancy.d.ts +2 -0
  93. package/dist/platform/browser/occupancy.js +2 -0
  94. package/dist/platform/browser/overlay-guard.d.ts +3 -0
  95. package/dist/platform/browser/overlay-guard.js +1 -0
  96. package/dist/platform/browser/save-picker.d.ts +2 -0
  97. package/dist/platform/browser/save-picker.js +1 -0
  98. package/dist/platform/browser/session-files.d.ts +11 -0
  99. package/dist/platform/browser/session-files.js +1 -0
  100. package/dist/platform/browser/state.d.ts +25 -0
  101. package/dist/platform/browser/state.js +2 -0
  102. package/dist/platform/browser/tabs.d.ts +15 -0
  103. package/dist/platform/browser/tabs.js +2 -0
  104. package/dist/platform/browser/window-placement.d.ts +8 -0
  105. package/dist/platform/browser/window-placement.js +1 -0
  106. package/dist/platform/chromium/actions-value.d.ts +11 -0
  107. package/dist/platform/chromium/actions-value.js +5 -0
  108. package/dist/platform/chromium/actions.d.ts +20 -0
  109. package/dist/platform/chromium/actions.js +2 -0
  110. package/dist/platform/chromium/checkable-target.d.ts +1 -0
  111. package/dist/platform/chromium/checkable-target.js +1 -0
  112. package/dist/platform/chromium/collect-viewport.d.ts +1 -0
  113. package/dist/platform/chromium/collect-viewport.js +1 -0
  114. package/dist/platform/chromium/collect.d.ts +11 -0
  115. package/dist/platform/chromium/collect.js +1 -0
  116. package/dist/platform/chromium/coverer-label.d.ts +8 -0
  117. package/dist/platform/chromium/coverer-label.js +1 -0
  118. package/dist/platform/chromium/frames.d.ts +2 -0
  119. package/dist/platform/chromium/frames.js +1 -0
  120. package/dist/platform/chromium/headings.d.ts +1 -0
  121. package/dist/platform/chromium/headings.js +1 -0
  122. package/dist/platform/chromium/locate.d.ts +2 -0
  123. package/dist/platform/chromium/locate.js +1 -0
  124. package/dist/platform/chromium/page-acts.d.ts +9 -0
  125. package/dist/platform/chromium/page-acts.js +1 -0
  126. package/dist/platform/chromium/page-base.d.ts +33 -0
  127. package/dist/platform/chromium/page-base.js +1 -0
  128. package/dist/platform/chromium/page-driver.d.ts +18 -0
  129. package/dist/platform/chromium/page-driver.js +2 -0
  130. package/dist/platform/chromium/page-refs.d.ts +4 -0
  131. package/dist/platform/chromium/page-refs.js +1 -0
  132. package/dist/platform/chromium/page-text.d.ts +15 -0
  133. package/dist/platform/chromium/page-text.js +1 -0
  134. package/dist/platform/chromium/page-waits.d.ts +7 -0
  135. package/dist/platform/chromium/page-waits.js +1 -0
  136. package/dist/platform/chromium/preview-stream.d.ts +37 -0
  137. package/dist/platform/chromium/preview-stream.js +1 -0
  138. package/dist/platform/chromium/preview.d.ts +3 -0
  139. package/dist/platform/chromium/preview.js +1 -0
  140. package/dist/platform/chromium/read-page-script.d.ts +1 -0
  141. package/dist/platform/chromium/read-page-script.js +1 -0
  142. package/dist/platform/chromium/read-page.d.ts +11 -0
  143. package/dist/platform/chromium/read-page.js +6 -0
  144. package/dist/platform/chromium/run-js.d.ts +4 -0
  145. package/dist/platform/chromium/run-js.js +2 -0
  146. package/dist/platform/chromium/scroll-regions.d.ts +1 -0
  147. package/dist/platform/chromium/scroll-regions.js +1 -0
  148. package/dist/platform/chromium/scroll-scripts.d.ts +3 -0
  149. package/dist/platform/chromium/scroll-scripts.js +1 -0
  150. package/dist/platform/chromium/scroll.d.ts +18 -0
  151. package/dist/platform/chromium/scroll.js +5 -0
  152. package/dist/platform/chromium/select-text.d.ts +4 -0
  153. package/dist/platform/chromium/select-text.js +2 -0
  154. package/dist/platform/chromium/shot.d.ts +10 -0
  155. package/dist/platform/chromium/shot.js +2 -0
  156. package/dist/platform/chromium/unreached.d.ts +8 -0
  157. package/dist/platform/chromium/unreached.js +1 -0
  158. package/dist/platform/cli-command.d.ts +1 -0
  159. package/dist/platform/cli-command.js +1 -0
  160. package/dist/platform/driver-base.d.ts +83 -0
  161. package/dist/platform/driver-base.js +1 -0
  162. package/dist/platform/driver-diagnostics.d.ts +30 -0
  163. package/dist/platform/driver-diagnostics.js +4 -0
  164. package/dist/platform/driver-lines.d.ts +15 -0
  165. package/dist/platform/driver-lines.js +2 -0
  166. package/dist/platform/driver-narrate.d.ts +26 -0
  167. package/dist/platform/driver-narrate.js +3 -0
  168. package/dist/platform/driver-refs.d.ts +16 -0
  169. package/dist/platform/driver-refs.js +1 -0
  170. package/dist/platform/driver-render.d.ts +26 -0
  171. package/dist/platform/driver-render.js +4 -0
  172. package/dist/platform/driver-select.d.ts +31 -0
  173. package/dist/platform/driver-select.js +2 -0
  174. package/dist/platform/driver-settle.d.ts +22 -0
  175. package/dist/platform/driver-settle.js +4 -0
  176. package/dist/platform/driver-verdict.d.ts +11 -0
  177. package/dist/platform/driver-verdict.js +5 -0
  178. package/dist/platform/driver-wait.d.ts +9 -0
  179. package/dist/platform/driver-wait.js +1 -0
  180. package/dist/platform/driver-waits.d.ts +10 -0
  181. package/dist/platform/driver-waits.js +1 -0
  182. package/dist/platform/driver.d.ts +39 -0
  183. package/dist/platform/driver.js +15 -0
  184. package/dist/platform/electron/driver.d.ts +30 -0
  185. package/dist/platform/electron/driver.js +7 -0
  186. package/dist/platform/electron/preflight.d.ts +9 -0
  187. package/dist/platform/electron/preflight.js +1 -0
  188. package/dist/platform/electron/verbs.d.ts +3 -0
  189. package/dist/platform/electron/verbs.js +1 -0
  190. package/dist/platform/home.d.ts +2 -0
  191. package/dist/platform/home.js +1 -0
  192. package/dist/platform/macos/act-element.d.ts +27 -0
  193. package/dist/platform/macos/act-element.js +1 -0
  194. package/dist/platform/macos/app-names.d.ts +2 -0
  195. package/dist/platform/macos/app-names.js +2 -0
  196. package/dist/platform/macos/app-notes.d.ts +2 -0
  197. package/dist/platform/macos/app-notes.js +86 -0
  198. package/dist/platform/macos/approvals.d.ts +10 -0
  199. package/dist/platform/macos/approvals.js +2 -0
  200. package/dist/platform/macos/ax-client.d.ts +53 -0
  201. package/dist/platform/macos/ax-client.js +3 -0
  202. package/dist/platform/macos/ax-errors.d.ts +16 -0
  203. package/dist/platform/macos/ax-errors.js +1 -0
  204. package/dist/platform/macos/ax-shapes.d.ts +53 -0
  205. package/dist/platform/macos/ax-shapes.js +1 -0
  206. package/dist/platform/macos/capture-client.d.ts +11 -0
  207. package/dist/platform/macos/capture-client.js +2 -0
  208. package/dist/platform/macos/click-verb.d.ts +4 -0
  209. package/dist/platform/macos/click-verb.js +1 -0
  210. package/dist/platform/macos/driver-apps.d.ts +59 -0
  211. package/dist/platform/macos/driver-apps.js +2 -0
  212. package/dist/platform/macos/driver-read.d.ts +47 -0
  213. package/dist/platform/macos/driver-read.js +1 -0
  214. package/dist/platform/macos/driver-targets.d.ts +9 -0
  215. package/dist/platform/macos/driver-targets.js +1 -0
  216. package/dist/platform/macos/driver.d.ts +65 -0
  217. package/dist/platform/macos/driver.js +2 -0
  218. package/dist/platform/macos/element-lines.d.ts +12 -0
  219. package/dist/platform/macos/element-lines.js +1 -0
  220. package/dist/platform/macos/element-shapes.d.ts +54 -0
  221. package/dist/platform/macos/element-shapes.js +0 -0
  222. package/dist/platform/macos/element-text.d.ts +3 -0
  223. package/dist/platform/macos/element-text.js +2 -0
  224. package/dist/platform/macos/elements.d.ts +26 -0
  225. package/dist/platform/macos/elements.js +1 -0
  226. package/dist/platform/macos/extra-verbs.d.ts +2 -0
  227. package/dist/platform/macos/extra-verbs.js +1 -0
  228. package/dist/platform/macos/interaction-notes.d.ts +2 -0
  229. package/dist/platform/macos/interaction-notes.js +1 -0
  230. package/dist/platform/macos/interaction-policy.d.ts +5 -0
  231. package/dist/platform/macos/interaction-policy.js +1 -0
  232. package/dist/platform/macos/menu-commands.d.ts +22 -0
  233. package/dist/platform/macos/menu-commands.js +5 -0
  234. package/dist/platform/macos/mouse.d.ts +23 -0
  235. package/dist/platform/macos/mouse.js +1 -0
  236. package/dist/platform/macos/narrate.d.ts +20 -0
  237. package/dist/platform/macos/narrate.js +1 -0
  238. package/dist/platform/macos/permissions.d.ts +5 -0
  239. package/dist/platform/macos/permissions.js +1 -0
  240. package/dist/platform/macos/press-rungs.d.ts +49 -0
  241. package/dist/platform/macos/press-rungs.js +5 -0
  242. package/dist/platform/macos/preview-stream.d.ts +15 -0
  243. package/dist/platform/macos/preview-stream.js +1 -0
  244. package/dist/platform/macos/prompt-fragment.d.ts +1 -0
  245. package/dist/platform/macos/prompt-fragment.js +1 -0
  246. package/dist/platform/macos/scroll-pages.d.ts +1 -0
  247. package/dist/platform/macos/scroll-pages.js +1 -0
  248. package/dist/platform/macos/scroll-verb.d.ts +5 -0
  249. package/dist/platform/macos/scroll-verb.js +1 -0
  250. package/dist/platform/macos/select-text-verb.d.ts +8 -0
  251. package/dist/platform/macos/select-text-verb.js +1 -0
  252. package/dist/platform/macos/type-verb.d.ts +5 -0
  253. package/dist/platform/macos/type-verb.js +1 -0
  254. package/dist/platform/ownership.d.ts +20 -0
  255. package/dist/platform/ownership.js +1 -0
  256. package/dist/platform/platform-package.d.ts +1 -0
  257. package/dist/platform/platform-package.js +1 -0
  258. package/dist/platform/preview.d.ts +39 -0
  259. package/dist/platform/preview.js +1 -0
  260. package/dist/platform/profile.d.ts +20 -0
  261. package/dist/platform/profile.js +1 -0
  262. package/dist/platform/registry.d.ts +12 -0
  263. package/dist/platform/registry.js +1 -0
  264. package/dist/platform/tools/api-contract.d.ts +33 -0
  265. package/dist/platform/tools/api-contract.js +3 -0
  266. package/dist/platform/tools/api-expect.d.ts +3 -0
  267. package/dist/platform/tools/api-expect.js +1 -0
  268. package/dist/platform/tools/api-run.d.ts +6 -0
  269. package/dist/platform/tools/api-run.js +1 -0
  270. package/dist/platform/tools/api-target.d.ts +22 -0
  271. package/dist/platform/tools/api-target.js +1 -0
  272. package/dist/platform/tools/api-wait.d.ts +3 -0
  273. package/dist/platform/tools/api-wait.js +1 -0
  274. package/dist/platform/tools/api.d.ts +7 -0
  275. package/dist/platform/tools/api.js +1 -0
  276. package/dist/platform/tools/find-target.d.ts +54 -0
  277. package/dist/platform/tools/find-target.js +1 -0
  278. package/dist/platform/tools/index.d.ts +8 -0
  279. package/dist/platform/tools/index.js +1 -0
  280. package/dist/platform/tools/normalize.d.ts +2 -0
  281. package/dist/platform/tools/normalize.js +1 -0
  282. package/dist/platform/tools/repl-budget.d.ts +8 -0
  283. package/dist/platform/tools/repl-budget.js +4 -0
  284. package/dist/platform/tools/repl-code.d.ts +1 -0
  285. package/dist/platform/tools/repl-code.js +4 -0
  286. package/dist/platform/tools/repl-docs.d.ts +5 -0
  287. package/dist/platform/tools/repl-docs.js +4 -0
  288. package/dist/platform/tools/repl-state.d.ts +21 -0
  289. package/dist/platform/tools/repl-state.js +0 -0
  290. package/dist/platform/tools/repl.d.ts +8 -0
  291. package/dist/platform/tools/repl.js +19 -0
  292. package/dist/platform/tools/shot-file.d.ts +7 -0
  293. package/dist/platform/tools/shot-file.js +1 -0
  294. package/dist/platform/tools/validate.d.ts +2 -0
  295. package/dist/platform/tools/validate.js +1 -0
  296. package/dist/platform/types.d.ts +220 -0
  297. package/dist/platform/types.js +1 -0
  298. package/dist/platform/windows/app-gate.d.ts +24 -0
  299. package/dist/platform/windows/app-gate.js +2 -0
  300. package/dist/platform/windows/background-verb.d.ts +13 -0
  301. package/dist/platform/windows/background-verb.js +1 -0
  302. package/dist/platform/windows/dates.d.ts +2 -0
  303. package/dist/platform/windows/dates.js +1 -0
  304. package/dist/platform/windows/driver-apps.d.ts +9 -0
  305. package/dist/platform/windows/driver-apps.js +1 -0
  306. package/dist/platform/windows/driver-read.d.ts +12 -0
  307. package/dist/platform/windows/driver-read.js +3 -0
  308. package/dist/platform/windows/driver-targets.d.ts +58 -0
  309. package/dist/platform/windows/driver-targets.js +1 -0
  310. package/dist/platform/windows/driver-yield.d.ts +10 -0
  311. package/dist/platform/windows/driver-yield.js +1 -0
  312. package/dist/platform/windows/driver.d.ts +39 -0
  313. package/dist/platform/windows/driver.js +2 -0
  314. package/dist/platform/windows/elements.d.ts +66 -0
  315. package/dist/platform/windows/elements.js +2 -0
  316. package/dist/platform/windows/launch.d.ts +23 -0
  317. package/dist/platform/windows/launch.js +1 -0
  318. package/dist/platform/windows/menu-verb.d.ts +7 -0
  319. package/dist/platform/windows/menu-verb.js +6 -0
  320. package/dist/platform/windows/prompt-fragment.d.ts +2 -0
  321. package/dist/platform/windows/prompt-fragment.js +1 -0
  322. package/dist/platform/windows/text-verbs.d.ts +10 -0
  323. package/dist/platform/windows/text-verbs.js +5 -0
  324. package/dist/platform/windows/uia-client.d.ts +32 -0
  325. package/dist/platform/windows/uia-client.js +3 -0
  326. package/dist/platform/windows/verbs.d.ts +2 -0
  327. package/dist/platform/windows/verbs.js +1 -0
  328. package/extension/background.js +280 -0
  329. package/extension/connect.html +27 -0
  330. package/extension/connect.js +28 -0
  331. package/extension/group.js +283 -0
  332. package/extension/icons/icon-128.png +0 -0
  333. package/extension/icons/icon-16.png +0 -0
  334. package/extension/icons/icon-32.png +0 -0
  335. package/extension/icons/icon-48.png +0 -0
  336. package/extension/manifest.json +36 -0
  337. package/extension/relay.js +361 -0
  338. package/extension/runtime-info.js +9 -0
  339. package/extension/transport.js +81 -0
  340. package/package.json +76 -4
  341. package/scripts/extension.mjs +111 -0
@@ -0,0 +1 @@
1
+ export declare const MAX_SCROLL_PAGES = 60;
@@ -0,0 +1 @@
1
+ const o=60;export{o as MAX_SCROLL_PAGES};
@@ -0,0 +1,5 @@
1
+ import type { ActResult } from '../types.ts';
2
+ import type { ElementActs } from './act-element.ts';
3
+ import type { AxWindow } from './elements.ts';
4
+ import type { MouseHost } from './mouse.ts';
5
+ export declare function scrollVerb(host: MouseHost, acts: ElementActs, frame: AxWindow, a: Record<string, any>): Promise<ActResult>;
@@ -0,0 +1 @@
1
+ import{AxRequestError as $}from"./ax-client.js";import{MAX_SCROLL_PAGES as v}from"./scroll-pages.js";const x=20,b=400,k=20;async function _(s,p,i,e,n,r,a){if(n!=="up"&&n!=="down")return null;try{const t=await s.call("scrollpage",{pid:s.pid(),...p?{key:p}:{},...i,direction:n,pages:a?v:r});if(!t.moved)return{ok:!0,note:`${e} is already at the ${n==="down"?"end":"start"} \u2014 it did not move. Do not repeat this scroll; read what is here, or scroll the other way`};const u=`${t.applied} page${t.applied===1?"":"s"}`,c=!a&&t.applied<r?` (asked for ${r}, the area ended after ${t.applied})`:"";return{ok:!0,note:a?`scrolled ${e} to the ${a} \u2014 ${u} through the app's own scrolling area, so the pointer never moved and nothing had to come forward`:`scrolled ${u} ${n} in ${e}${c} \u2014 through the app's own scrolling area, so the pointer never moved`,journal:{rung:"axpage",pages:t.applied,delta:Math.round(t.delta*100)/100,unit:t.unit}}}catch(t){if(t instanceof $&&["no_scroll_area","no_element","unsupported"].includes(t.code))return null;throw t}}async function E(s,p,i,e){let n={x:i.x+i.w/2,y:i.y+i.h/2},r="the window";if(typeof e.ref=="string"&&e.ref){const o=p.resolve(e.ref,e.name);if(!("key"in o))return o;const d=s.rectOf(e.ref);if(!d)return{ok:!1,note:`${o.role} "${o.name}" has no frame on this walk, so there is nowhere to scroll \u2014 observe and name a listed element`};n={x:d.x+d.w/2,y:d.y+d.h/2},r=`${o.role} "${o.name}"`}const a={x:Math.round(n.x),y:Math.round(n.y)},t=s.pid(),u=s.call,c=Math.min(Math.max(Math.trunc(Number(e.count))||1,1),k);if(e.to&&Number(e.count)>1)return{ok:!1,note:`scroll takes to:'${e.to}' or count, not both \u2014 to:'${e.to}' already goes as far as the window scrolls; drop one`};const h=e.to?b:x*c,l=e.to==="top"?"up":e.to==="bottom"?"down":String(e.direction??"down"),f=await _(s,typeof e.ref=="string"&&e.ref?String(e.ref):null,a,r,l,c,e.to);if(f)return f;const g=l==="down"?h:l==="up"?-h:0,m=l==="right"?h:l==="left"?-h:0;try{await u("scroll",{pid:t,...a,dy:g,dx:m})}catch(o){if(o instanceof $&&o.code==="no_window")return{ok:!1,note:`${o.message} \u2014 a wheel event lands on whatever is under the point, so scrolling ${r} needs it uncovered there: explain the foreground and pointer effects, obtain user permission, then activate and retry`};throw o}const w=`at ${r}'s centre`,y=c>1?` ${c} screens`:"";return{ok:!0,note:e.to?`posted a long wheel burst ${l} ${w} (not a verified jump-to-${e.to}); used system pointer input and requested cursor restoration without app activation \u2014 observe whether the content scrolled`:`posted wheel input${y} ${l} ${w}; requested cursor restoration without app activation \u2014 observe whether the content scrolled`}}export{E as scrollVerb};
@@ -0,0 +1,8 @@
1
+ import type { ActResult, VerbSpec } from "../types.ts";
2
+ import type { ElementActs } from "./act-element.ts";
3
+ export declare const SELECT_TEXT_VERB: VerbSpec;
4
+ export declare function selectTextVerb(acts: ElementActs, ref: unknown, name: unknown, o: {
5
+ want: string;
6
+ near: string;
7
+ caret: string;
8
+ }): Promise<ActResult>;
@@ -0,0 +1 @@
1
+ import{Type as a}from"typebox";import{AxRequestError as h}from"./ax-client.js";const v={kind:"select_text",schema:a.Object({kind:a.Literal("select_text"),ref:a.String({description:"The text field or text area to select inside (from the latest observe())"}),name:a.Optional(a.String({description:"Visible name of that element \u2014 identity check + fallback locator"})),text:a.Optional(a.String({description:"The exact text to select (copy it verbatim from what you read); omit it to place the caret at the start / end of the field with caret alone"})),near:a.Optional(a.String({description:"Nearby text that tells one occurrence from another; the match closest to it wins"})),caret:a.Optional(a.Union([a.Literal("select"),a.Literal("before"),a.Literal("after")],{description:"select (default) leaves it selected \u2014 keys then replaces it; before / after place the caret there instead"}))},{description:"Select part of a text field's value, or place the caret in it, so keys edits exactly that \u2014 set_text always replaces the whole field."})};function m(n,l,f,e){return Math.max(0,f-(n+l),n-(f+e))}async function O(n,l,f,e){const o=n.resolve(l,f);if(!("key"in o))return o;const i=n.what(o,l);if(!e.want){if(e.caret==="select")return{ok:!1,note:"select_text needs the text to select, or caret:'before' / caret:'after' alone to place the caret at the start / end of the field"};let t="";try{t=String((await n.call("value",{key:o.key})).value??"")}catch(r){if(r instanceof h&&r.code==="stale_key")return n.staleRef(l)}const p=e.caret==="after"?t.length:0;try{await n.call("selectrange",{key:o.key,start:p,length:0})}catch(r){if(r instanceof h&&r.code==="stale_key")return n.staleRef(l);if(r instanceof h&&r.code==="ax_error")return{ok:!1,note:`${i} will not take a caret (AXError ${r.ax??"?"}) \u2014 click it to focus it instead`};throw r}return{ok:!0,note:`caret placed at the ${e.caret==="after"?"end":"start"} of ${i} \u2014 keys and type go there now`}}let c;try{c=String((await n.call("value",{key:o.key})).value??"")}catch(t){if(t instanceof h&&t.code==="stale_key")return n.staleRef(l);c=""}if(!c)return{ok:!1,note:`${i} has no readable text value \u2014 select_text works on text fields and text areas; act on one of those, or use click`};const s=[];let d=c.indexOf(e.want);for(;d!==-1;)s.push(d),d=c.indexOf(e.want,d+1);if(!s.length)return{ok:!1,note:`"${e.want}" is not in ${i} \u2014 read it with text() and copy it exactly, whitespace included`};let k=s[0],w=0;if(e.near){const t=c.indexOf(e.near);if(t===-1)return{ok:!1,note:`"${e.near}" is not in ${i}, so it cannot pick between the ${s.length} matches for "${e.want}" \u2014 drop near, or give text that really sits beside the one you mean`};let p=1/0;for(const[r,y]of s.entries()){const u=m(y,e.want.length,t,e.near.length);u<p&&(p=u,k=y,w=r)}}const $=e.caret==="after"?k+e.want.length:k,g=e.caret==="select"?e.want.length:0;try{await n.call("selectrange",{key:o.key,start:$,length:g})}catch(t){if(t instanceof h&&t.code==="stale_key")return n.staleRef(l);if(t instanceof h&&t.code==="ax_error")return{ok:!1,note:`${i} will not take a selection (AXError ${t.ax??"?"}) \u2014 the app does not expose one for this control; set_text replaces its whole value instead`};throw t}const x=s.length>1?` (match ${w+1} of ${s.length}${e.near?`, the one nearest "${e.near}"`:", the first"})`:"";return e.caret==="select"?{ok:!0,note:`selected "${e.want}" in ${i}${x} \u2014 keys now replaces exactly this (set_text still replaces the whole field)`}:{ok:!0,note:`caret placed ${e.caret} "${e.want}" in ${i}${x} \u2014 keys now types there (set_text still replaces the whole field)`}}export{v as SELECT_TEXT_VERB,O as selectTextVerb};
@@ -0,0 +1,5 @@
1
+ import type { ActResult, VerbSpec } from "../types.ts";
2
+ import type { AxCall } from "./act-element.ts";
3
+ export declare const TYPE_VERB: VerbSpec;
4
+ export declare const TYPE_AFTER_KEY_MS = 250;
5
+ export declare function typeVerb(call: AxCall, pid: number, appName: string, text: string, lastKeyAt?: number): Promise<ActResult>;
@@ -0,0 +1 @@
1
+ import{Type as h}from"typebox";const g={kind:"type",schema:h.Object({kind:h.Literal("type"),text:h.String({description:"Text to type, character by character"})},{description:"Type text with key events at the app's keyboard focus \u2014 for a field that takes typed input only (a file being renamed, a search field that filters as you type). set_text fills a field by ref; type is for where that does not stick. Focus the field first (click, or select_text with caret)."})},l=250;async function u(r,d,f){const{typedKey:i,typedValue:t,typedRange:o}=f;if(!i||typeof t!="string"||!o)return{note:"unverified \u2014 the target field, original text or selection could not be read before input"};const{start:e,length:n}=o;if(!Number.isSafeInteger(e)||!Number.isSafeInteger(n)||e<0||n<0||e>t.length||n>t.length-e)return{note:"unverified \u2014 the original selection was outside the readable text"};const a=t.slice(0,e)+d+t.slice(e+n);try{const s=Date.now()+200;for(;;){const{value:c}=await r("textvalue",{key:i});if(typeof c!="string")break;if(Date.now()>=s)return c===a&&a===t?{note:"unverified \u2014 the expected text was already present before input, so read-back cannot establish whether the keys took effect"}:{verified:c===a,note:c===a?"matched \u2014 the same field reads back exactly the expected text after inserting or replacing the original selection":"content differs \u2014 the same field does not read back the expected text; automatic correction or formatting may change it. This does not establish where the keys landed; inspect it with text() before editing further, and do not automatically replay the input"};await new Promise(p=>setTimeout(p,Math.min(50,s-Date.now())))}}catch{}return{note:"unverified \u2014 the original target field could not be read after input; inspect it before editing further, and do not automatically replay the input"}}async function m(r,d,f,i,t=0){if(!i)return{ok:!1,note:"type needs text"};const o=t?Math.max(0,Date.now()-t):Number.POSITIVE_INFINITY,e=o<l?Math.ceil(l-o):0;e&&await new Promise(s=>setTimeout(s,e));const n=await r("type",{pid:d,text:i}),a=await u(r,i,n??{});return{ok:!0,...a,note:`posted ${n?.postedCount??i.length} characters at ${f}'s keyboard focus with key events`+(e?`, after ${e} ms for the key before it to finish moving the focus \u2014 a field that commits on Enter hands it on asynchronously, and a character posted into that gap is lost with no error`:"")+`; ${a.note}`}}export{l as TYPE_AFTER_KEY_MS,g as TYPE_VERB,m as typeVerb};
@@ -0,0 +1,20 @@
1
+ import type { OwnershipResource } from "./types.ts";
2
+ export interface OwnershipProvider {
3
+ claim(group: string, resources: OwnershipResource[]): Promise<void>;
4
+ release(group: string): Promise<void>;
5
+ releaseAll(): Promise<void>;
6
+ trackProcess?(pid: number): Promise<void>;
7
+ }
8
+ export declare class OwnershipBusyError extends Error {
9
+ constructor(resource: string);
10
+ }
11
+ export declare class ComputerUseOwnership implements OwnershipProvider {
12
+ private readonly owner;
13
+ private db;
14
+ private database;
15
+ private gone;
16
+ claim(group: string, resources: OwnershipResource[]): Promise<void>;
17
+ release(group: string): Promise<void>;
18
+ releaseAll(): Promise<void>;
19
+ trackProcess(pid: number): Promise<void>;
20
+ }
@@ -0,0 +1 @@
1
+ import{randomUUID as c}from"node:crypto";import{mkdirSync as p}from"node:fs";import{join as a}from"node:path";import{sharedHome as d}from"./home.js";class u extends Error{constructor(r){super(`Resource ${r} is in use by another runtime. Nothing was dispatched. Use a different target, or wait for its owner to finish and release it; do not reset another session.`),this.name="OwnershipBusyError"}}function n(E){try{return process.kill(E,0),!0}catch(r){return r.code!=="ESRCH"}}class N{owner=c();db=null;async database(){if(this.db)return this.db;const r=a(d(),"ownership");p(r,{recursive:!0,mode:448});const{DatabaseSync:t}=await import("node:sqlite"),e=new t(a(r,"claims.sqlite"));e.exec("PRAGMA busy_timeout=1000; CREATE TABLE IF NOT EXISTS claims (resource TEXT NOT NULL, owner TEXT NOT NULL, group_id TEXT NOT NULL, pid INTEGER NOT NULL, mode TEXT NOT NULL, PRIMARY KEY(resource, owner, group_id)); CREATE TABLE IF NOT EXISTS executors (owner TEXT NOT NULL, runtime INTEGER NOT NULL, pid INTEGER NOT NULL, PRIMARY KEY(owner, pid))");const o=e.prepare("SELECT owner,runtime,pid FROM executors").all();for(const s of o)!n(s.runtime)&&!n(s.pid)&&e.prepare("DELETE FROM executors WHERE owner=? AND pid=?").run(s.owner,s.pid);return this.db=e,e}gone(r,t,e){return n(e)?!1:!r.prepare("SELECT pid FROM executors WHERE owner=?").all(t).some(s=>n(s.pid))}async claim(r,t){const e=await this.database();e.exec("BEGIN IMMEDIATE");try{for(const o of t){const s=e.prepare("SELECT owner,pid,mode FROM claims WHERE resource=? AND owner<>?").all(o.key,this.owner);for(const i of s)if(this.gone(e,i.owner,i.pid))e.prepare("DELETE FROM claims WHERE owner=?").run(i.owner),e.prepare("DELETE FROM executors WHERE owner=?").run(i.owner);else if(o.mode==="exclusive"||i.mode==="exclusive")throw new u(o.key);e.prepare("INSERT INTO claims VALUES (?,?,?,?,?) ON CONFLICT(resource,owner,group_id) DO UPDATE SET mode=CASE WHEN claims.mode='exclusive' THEN 'exclusive' ELSE excluded.mode END").run(o.key,this.owner,r,process.pid,o.mode)}e.exec("COMMIT")}catch(o){throw e.exec("ROLLBACK"),o}}async release(r){this.db?.prepare("DELETE FROM claims WHERE owner=? AND group_id=?").run(this.owner,r)}async releaseAll(){this.db&&(this.db.prepare("DELETE FROM claims WHERE owner=?").run(this.owner),this.db.close(),this.db=null)}async trackProcess(r){(await this.database()).prepare("INSERT OR IGNORE INTO executors VALUES (?,?,?)").run(this.owner,process.pid,r)}}export{N as ComputerUseOwnership,u as OwnershipBusyError};
@@ -0,0 +1 @@
1
+ export declare function platformPackageHelper(file: string): string | null;
@@ -0,0 +1 @@
1
+ import{createRequire as e}from"node:module";function o(r){try{return e(import.meta.url).resolve(`@mirasim/computer-use-${process.platform}-${process.arch}/${r}`)}catch{return null}}export{o as platformPackageHelper};
@@ -0,0 +1,39 @@
1
+ import type { PreviewFrame, PreviewTarget } from "./types.ts";
2
+ export interface PublishedTarget extends PreviewTarget {
3
+ updatedAt: number;
4
+ taskId: string | null;
5
+ }
6
+ export interface PreviewOutput {
7
+ targets(targets: PublishedTarget[]): void;
8
+ frame(ref: TargetRef, frame: PreviewFrame): void;
9
+ status(ref: TargetRef, status: {
10
+ unavailable: string | null;
11
+ icon?: string;
12
+ }): void;
13
+ }
14
+ export interface TargetRef {
15
+ kind: PreviewTarget["kind"];
16
+ targetId: string;
17
+ }
18
+ export declare class PreviewService {
19
+ private readonly out;
20
+ private readonly env;
21
+ private readonly targets;
22
+ private readonly streams;
23
+ private readonly revokedAt;
24
+ private taskTags;
25
+ private watched;
26
+ private maxSize;
27
+ private timer;
28
+ private ticker;
29
+ private closed;
30
+ constructor(out: PreviewOutput, env?: NodeJS.ProcessEnv);
31
+ setTaskId(id: string | null): void;
32
+ publish(target: PreviewTarget, at: number): void;
33
+ revoke(apps: string[]): void;
34
+ watch(refs: TargetRef[], maxSize?: number): void;
35
+ private sync;
36
+ private drop;
37
+ private schedule;
38
+ close(): void;
39
+ }
@@ -0,0 +1 @@
1
+ import{createPreviewStream as c}from"./registry.js";const n=16,d=250,o=h=>`${h.kind}:${h.targetId}`;class u{out;env;targets=new Map;streams=new Map;revokedAt=new Map;taskTags=[{at:0,id:null}];watched=new Set;maxSize=2560;timer=null;ticker=null;closed=!1;constructor(t,s=process.env){this.out=t,this.env=s}setTaskId(t){this.taskTags[this.taskTags.length-1]?.id!==t&&(this.taskTags.push({at:Date.now(),id:t}),this.taskTags=this.taskTags.slice(-4))}publish(t,s){if(this.closed||t.capture.type==="macos"&&s<=(this.revokedAt.get(t.capture.app)??-1))return;const e=o(t),a=this.targets.get(e);if(a&&a.updatedAt>s)return;this.targets.delete(e);const i=[...this.taskTags].reverse().find(r=>r.at<=s)?.id??null;for(this.targets.set(e,{...t,pointer:t.pointer??a?.pointer,updatedAt:s,taskId:i});this.targets.size>n;)this.drop(this.targets.keys().next().value);this.schedule(),this.sync()}revoke(t){for(const s of t)this.revokedAt.set(s,Date.now());for(const[s,e]of this.targets)e.capture.type==="macos"&&t.includes(e.capture.app)&&this.drop(s);this.schedule()}watch(t,s){typeof s=="number"&&s>=64&&(this.maxSize=Math.min(Math.round(s),4096)),this.watched=new Set(t.map(o)),this.sync()}sync(){if(!this.closed){for(const[t,s]of this.streams)(!this.watched.has(t)||!this.targets.has(t))&&(s.dispose(),this.streams.delete(t));for(const t of this.watched){const s=this.targets.get(t);if(!s||this.streams.has(t))continue;const e={kind:s.kind,targetId:s.targetId},i=c(s,{frame:r=>{this.streams.get(t)===i&&this.out.frame(e,r)},status:r=>{this.streams.get(t)===i&&this.out.status(e,r)},gone:()=>{this.streams.get(t)===i&&(this.drop(t),this.schedule())}},this.maxSize,this.env);this.streams.set(t,i),i.tick().catch(()=>{})}this.streams.size&&!this.ticker?(this.ticker=setInterval(()=>{for(const t of this.streams.values())t.tick().catch(()=>{})},d),this.ticker.unref()):!this.streams.size&&this.ticker&&(clearInterval(this.ticker),this.ticker=null)}}drop(t){this.targets.delete(t),this.streams.get(t)?.dispose(),this.streams.delete(t)}schedule(){this.timer||this.closed||(this.timer=setTimeout(()=>{this.timer=null,this.closed||this.out.targets([...this.targets.values()])},20),this.timer.unref())}close(){this.closed=!0,this.timer&&clearTimeout(this.timer),this.ticker&&clearInterval(this.ticker);for(const t of this.streams.values())t.dispose();this.streams.clear()}}export{u as PreviewService};
@@ -0,0 +1,20 @@
1
+ import type { ComputerUseDriver } from "./driver.ts";
2
+ import type { ComputerUseToolSpec, UserConsent } from "./types.ts";
3
+ import type { BrowserConfigOptions } from "./browser/config.ts";
4
+ import { type OwnershipProvider } from './ownership.ts';
5
+ export interface Profile {
6
+ driver: ComputerUseDriver;
7
+ drivers: ComputerUseDriver[];
8
+ tools: ComputerUseToolSpec[];
9
+ }
10
+ export interface ProfileOptions {
11
+ ownership?: OwnershipProvider;
12
+ driver?: string;
13
+ cdpUrl?: string;
14
+ browser?: BrowserConfigOptions;
15
+ diagnostics?: boolean;
16
+ consent?: UserConsent;
17
+ docsDelivered?: boolean;
18
+ env?: NodeJS.ProcessEnv;
19
+ }
20
+ export declare function resolveProfile(opts?: ProfileOptions): Profile;
@@ -0,0 +1 @@
1
+ import{createDriver as m}from"./registry.js";import{createComputerUseTools as v}from"./tools/index.js";import{ComputerUseOwnership as l}from"./ownership.js";function p(r={}){const o=r.env??process.env,n=(r.driver??o.COMPUTER_USE_PLATFORMS??"browser").split(",").map(e=>e.trim()).filter(Boolean).map(e=>m(e,{cdpUrl:r.cdpUrl,browser:r.browser,...r.consent?{consent:r.consent}:{},env:o})),c=n[0],i=r.diagnostics??o.COMPUTER_USE_DIAGNOSTICS==="1",t=n.map(e=>({driver:e})),d=r.docsDelivered??o.COMPUTER_USE_REPL_DOCS_DELIVERED==="1",s=r.ownership??new l;for(const e of n)e.onExecutorStart=s.trackProcess?.bind(s);return{driver:c,drivers:n,tools:v(t,{diagnostics:i,docsDelivered:d,ownership:s})}}export{p as resolveProfile};
@@ -0,0 +1,12 @@
1
+ import type { ComputerUseDriver } from "./driver.ts";
2
+ import type { BrowserConfigOptions } from "./browser/config.ts";
3
+ import type { PreviewSink, PreviewStream, PreviewTarget, UserConsent } from "./types.ts";
4
+ export type DriverOptions = {
5
+ cdpUrl?: string;
6
+ browser?: BrowserConfigOptions;
7
+ bundle?: string;
8
+ consent?: UserConsent;
9
+ env?: NodeJS.ProcessEnv;
10
+ };
11
+ export declare function createDriver(id: string, opts?: DriverOptions): ComputerUseDriver;
12
+ export declare function createPreviewStream(target: PreviewTarget, sink: PreviewSink, maxSize: number, env?: NodeJS.ProcessEnv): PreviewStream;
@@ -0,0 +1 @@
1
+ import{BrowserDriver as i}from"./browser/driver.js";import{ElectronDriver as t}from"./electron/driver.js";import{MacosDriver as v}from"./macos/driver.js";import{WindowsDriver as w}from"./windows/driver.js";import{CdpPreviewStream as m}from"./chromium/preview-stream.js";import{MacosPreviewStream as a}from"./macos/preview-stream.js";const o={browser:e=>new i(e.cdpUrl,void 0,e.browser,e.env),electron:e=>new t({cdpUrl:e.cdpUrl,env:e.env}),macos:e=>new v({bundle:e.bundle,env:e.env,consent:e.consent}),windows:e=>new w({env:e.env,consent:e.consent})};function b(e,n={}){const r=o[e];if(!r)throw new Error(`unknown driver "${e}" (available: ${Object.keys(o).join(", ")})`);return r(n)}function D(e,n,r,c=process.env){return e.capture.type==="macos"?new a(e,n,r,c):new m(e,n,r)}export{b as createDriver,D as createPreviewStream};
@@ -0,0 +1,33 @@
1
+ import type { ActResult, ToolResult } from "../types.ts";
2
+ export declare const fnName: (kind: string) => string;
3
+ type ActionOutcome = "verified" | "dispatched" | "blocked" | "uncertain";
4
+ export interface JournalLine {
5
+ i: number;
6
+ kind: string;
7
+ target: string;
8
+ ok: boolean;
9
+ note: string;
10
+ outcome?: ActionOutcome;
11
+ ms?: number;
12
+ }
13
+ export interface RunLedger {
14
+ charge(kind: string): void;
15
+ push(kind: string, target: string, ok: boolean, note: string, read?: boolean, outcome?: ActionOutcome): void;
16
+ attach(part: ToolResult["content"][number]): void;
17
+ round?: WriteRound;
18
+ }
19
+ export interface WriteRound {
20
+ fields: number;
21
+ verified: number;
22
+ ended: string | null;
23
+ }
24
+ export interface ApiHooks {
25
+ onRefs?(refs: string[]): void;
26
+ onTarget?(ref: string): void;
27
+ beforeAction?(kind: string, args: Record<string, unknown>): Promise<void>;
28
+ }
29
+ export declare function noteOf(res: ActResult): string;
30
+ export declare function options<T>(verb: string, value: unknown, shape: string): T;
31
+ export declare const firstText: (r: ToolResult) => string;
32
+ export declare const firstLine: (r: ToolResult) => string;
33
+ export {};
@@ -0,0 +1,3 @@
1
+ const o={open_url:"openUrl",select_option:"selectOption",select_text:"selectText",launch_app:"launchApp",ax_action:"axAction",run_js:"runJs",text_gone:"textGone"},p=n=>o[n]??n.replace(/_(\w)/g,(t,e)=>e.toUpperCase());function i(n){return(n.note??"").split(`
2
+ `)[0]??""}function s(n,t,e){if(t==null)return{};if(typeof t=="object"&&!Array.isArray(t))return t;throw new Error(`${n} takes an options object, not ${typeof t=="string"?JSON.stringify(t):String(t)} \u2014 ${e}`)}const r=n=>n.content.find(t=>t.type==="text")?.text??"",c=n=>r(n).split(`
3
+ `)[0]??"";export{c as firstLine,r as firstText,p as fnName,i as noteOf,s as options};
@@ -0,0 +1,3 @@
1
+ import type { ComputerUseDriver } from "../driver.ts";
2
+ import { type RunLedger } from "./api-contract.ts";
3
+ export declare function expectVerb(driver: ComputerUseDriver, ledger: () => RunLedger, value: (target: unknown) => Promise<string>): (raw: unknown) => Promise<boolean>;
@@ -0,0 +1 @@
1
+ import{options as h}from"./api-contract.js";function b(a,i,w){return async f=>{const e=h("expect",f,"expect({text:'Saved', ms:3000}) or expect({value:field, equals:'draft', ms:3000})"),d=new Set(["text","control","url","value","equals","ms"]);if(Object.keys(e).some(t=>!d.has(t)))throw new Error("expect received an unknown option");const u=["text","control","url","value"].filter(t=>e[t]!==void 0);if(u.length!==1)throw new Error("expect needs exactly one of text, control, url, or value");const n=u[0];if(n==="value"?typeof e.equals!="string":typeof e[n]!="string"||!e[n])throw new Error("expect needs a nonempty condition string, or a value target with equals:string");if(e.ms!==void 0&&(typeof e.ms!="number"||!Number.isFinite(e.ms)||e.ms<0||e.ms>16e3))throw new Error("expect ms must be between 0 and 16000");const r=Number(e.ms??3e3);i().charge("expect"),a.noteContentRead();const s=n==="value"?"field value":`${n} ${JSON.stringify(e[n])}`;let o=!1;try{if(n==="value"){const t=Date.now()+r;do{let l;try{const c=Math.max(1,t-Date.now()),p=await Promise.race([w(e.value),new Promise((x,m)=>{l=setTimeout(()=>m(new Error("value read timed out")),c)})]);if(a.valueMatches(p,e.equals)){o=!0;break}}finally{clearTimeout(l)}if(Date.now()>=t)break;await new Promise(c=>setTimeout(c,Math.min(100,t-Date.now())))}while(Date.now()<=t)}else{const t=String(e[n]);o=(n==="text"?await a.waitForText(t,!1,r):n==="control"?await a.waitForControl(t,!1,r):await a.waitForUrl(t,!1,r)).ok}}catch(t){throw i().push("expect",s,!1,`verification unavailable: ${t.message}; do not replay the preceding action`,!0,"uncertain"),t}if(i().push("expect",s,o,o?"condition verified":"condition was not verified; subsequent steps stopped, preceding action was not replayed",!0,o?"verified":"uncertain"),!o)throw new Error(`Expectation failed for ${s}. Observe the result before deciding whether any earlier action can be retried.`);return!0}}export{b as expectVerb};
@@ -0,0 +1,6 @@
1
+ import type { JournalLine } from "./api-contract.ts";
2
+ export declare function verbGate(onRefusal: (note: string) => void): (<T extends Record<string, unknown>>(fns: T) => T) & {
3
+ drain(): Promise<void>;
4
+ };
5
+ export declare function stringify(v: unknown): string;
6
+ export declare function renderJournal(journal: JournalLine[]): string[];
@@ -0,0 +1 @@
1
+ function p(t){let e=null,r=null;return Object.assign(l=>Object.fromEntries(Object.entries(l).map(([i,u])=>{if(i==="log"||typeof u!="function")return[i,u];const h=u;return[i,(...f)=>{if(e){const c=`${i} was started while ${e} was still running \u2014 verbs run one at a time, because the surface has one focus and one caret. Two chains in flight put their input wherever the LAST one clicked. Await each verb before the next.`;throw t(c),new Error(c)}e=i;let o;try{o=h(...f)}catch(c){throw e=null,c}if(!(o instanceof Promise))return e=null,o;const a=o.finally(()=>{e=null,r=null});return r=a,a.catch(()=>{}),a}]})),{async drain(){for(;r;)await r}})}function g(t){if(typeof t=="string")return t;try{return JSON.stringify(t)??String(t)}catch{return String(t)}}const s=12;function w(t){const e=n=>` ${n.i}. ${n.kind}${n.target?` ${n.target}`:""}${n.ok?"":" FAILED"}${n.outcome?` [${n.outcome}]`:""}${n.note?`: ${n.note}`:""}`;if(t.length<=s*2)return t.map(e);const r=t.length-s*2;return[...t.slice(0,s).map(e),` \u2026 ${r} steps not listed \u2026`,...t.slice(-s).map(e)]}export{w as renderJournal,g as stringify,p as verbGate};
@@ -0,0 +1,22 @@
1
+ import type { ComputerUseDriver } from "../driver.ts";
2
+ import type { ToolResult } from "../types.ts";
3
+ import { type FindScope, type Handle } from "./find-target.ts";
4
+ import type { RunLedger, ApiHooks } from "./api-contract.ts";
5
+ export declare function createTargeting(driver: ComputerUseDriver, ledger: () => RunLedger, hooks: ApiHooks, firstLine: (r: ToolResult) => string): {
6
+ findByName: (name: string, scope?: FindScope) => Promise<Handle | null>;
7
+ findWithMiss: (name: string, scope?: FindScope) => Promise<{
8
+ h: Handle | null;
9
+ miss: string;
10
+ }>;
11
+ asPoint: (t: unknown) => {
12
+ x: number;
13
+ y: number;
14
+ } | null;
15
+ resolve: (t: unknown, opts?: {
16
+ write?: boolean;
17
+ }) => Promise<{
18
+ ref: string;
19
+ name?: string;
20
+ }>;
21
+ requireFocus: (t: unknown) => Promise<void>;
22
+ };
@@ -0,0 +1 @@
1
+ import{nearMissNote as y,rankByName as d,tiedOthers as b}from"./find-target.js";function T(i,p,c,E){const l=async(r,t={})=>(await w(r,t)).h,w=async(r,t={})=>{const{elements:e,groups:a,partial:s,covered:o}=await i.readElements();c.onRefs?.(e.map(h=>h.ref));const n=d(e,r,t,a);if(n)return{h:n,miss:""};const f=o&&d(o.elements,r,t),m=o&&f?`no actionable match \u2014 ${f.role??"control"} "${f.name}" (${f.ref}) is here but \xAB${o.elements.find(h=>h.ref===f.ref)?.coveredBy||o.by}\xBB covers it: dismiss that overlay first (find('close') reaches its close control when the page marks one; an unmarked one shows only in a screenshot), `+(i.scrollsInto?`or scroll({into:'${f.ref}'}) to bring it clear, `:"or scroll until the control sits clear of it, ")+"then find again":y(e,r,t,await g(r),a);return{h:n,miss:s?`${m} \u2014 but this read was PARTIAL (${s}), so it may be in the part not read; find again, or narrow with within:`:m}},g=async r=>{const t=r.trim().toLowerCase();if(!t)return!1;try{return(await i.readTextForWait()).toLowerCase().includes(t)}catch{return!1}},$=r=>{const t=r;return t&&typeof t=="object"&&typeof t.x=="number"&&typeof t.y=="number"?{x:t.x,y:t.y}:null},u=async(r,t={})=>{if(typeof r=="string"){if(c.onTarget?.(r),!i.knownRef(r))throw new Error(i.staleRefNote(r));return{ref:r}}if(r==null)throw new Error(`the target is ${r} \u2014 a find() that matched nothing answers null, and its line above says why`);let e=r;if(typeof e.ref!="string")throw new Error("a target must be a find() handle, a ref string, or a point {x, y} read off a screenshot");if(c.onTarget?.(e.ref),e.nearGroup){const o=await l(e.name,e.scope);if(!o||o.nearGroup!==e.nearGroup)throw new Error(`"${e.name}" is no longer in group "${e.nearGroup}" \u2014 observe() before acting`);e=o,c.onTarget?.(e.ref)}const a=t.write?b(e):[];if(a.length){const o=[{ref:e.ref,name:e.name,role:"",id:e.id},...a].map(n=>`${n.ref}${n.id?` (${n.id})`:""}`).join(", ");throw new Error(`"${e.name}" names ${a.length+1} controls of the same role and nothing tells them apart (${o}) \u2014 this write would be a guess. Narrow it: find('${e.name}', {within}) or {near}, or observe() and act by ref.`)}if(i.knownRef(e.ref))return{ref:e.ref,name:e.name};const s=await l(e.name,e.scope??{});if(!s)throw new Error(`"${e.name}" is no longer on the surface (its ref ${e.ref} is gone)`);if(e.matches===1&&(s.matches??1)>1)throw new Error(`"${e.name}" now matches ${s.matches} controls and its own ref ${e.ref} is gone, so re-resolving it would be a guess \u2014 observe() and act by ref, or narrow it: find('${e.name}', {role, within, visible})`);return{ref:s.ref,name:s.name}};return{findByName:l,findWithMiss:w,asPoint:$,resolve:u,requireFocus:async r=>{const{ref:t,name:e}=await u(r),a=Date.now()+200;for(;;){const{elements:s}=await i.readElements(),o=s.filter(n=>/(^|[,\s])focused([,\s]|$)/.test(n.state));if(!s.some(n=>n.ref===t))throw new Error(`focus: ${t} is not in the current observation \u2014 observe() and name a listed control`);if(o.length===1){const n=o[0];if(n.ref===t)return;throw new Error(`the keyboard focus is on ${n.ref} ${n.role}${n.name?` "${n.name}"`:""}, not on ${t}${e?` "${e}"`:""} \u2014 nothing was pressed. click it, set_text it, or select_text with a caret to focus it first, then send the key`)}if(Date.now()>=a)throw new Error(`focus could not be verified for ${t} \u2014 this surface reports ${o.length?"multiple focused elements":"no focused element"}; nothing was pressed. Observe and explicitly focus the intended control before sending a guarded key. Omit focus only when you deliberately want to send to the surface's current focus without verification.`);await new Promise(n=>setTimeout(n,50))}}}}export{T as createTargeting};
@@ -0,0 +1,3 @@
1
+ import type { ComputerUseDriver } from "../driver.ts";
2
+ import { type RunLedger } from "./api-contract.ts";
3
+ export declare function waitVerb(driver: ComputerUseDriver, ledger: () => RunLedger): (raw?: unknown) => Promise<boolean>;
@@ -0,0 +1 @@
1
+ import{noteOf as g,options as u}from"./api-contract.js";const w=6e4;function p(o,i){return async(l={})=>{const t=u("wait",l,"wait({}) waits for the content to stop changing; wait({ms:2000}) sleeps; wait({text}) / ({control}) / ({text_gone}) / ({control_gone}) / ({url}) / ({url_gone}) / ({loaded:true}) wait for a condition");i().charge("wait"),o.noteAction("wait",t);const n=typeof t.text=="string"?t.text:typeof t.text_gone=="string"?t.text_gone:null,e=typeof t.control=="string"?t.control:typeof t.control_gone=="string"?t.control_gone:null,r=typeof t.url=="string"?t.url:typeof t.url_gone=="string"?t.url_gone:null,a=r!==null?await o.waitForUrl(r,typeof t.url_gone=="string",t.ms):t.loaded?await o.waitForLoad(t.ms):e!==null?await o.waitForControl(e,typeof t.control_gone=="string",t.ms):n!==null?await o.waitForText(n,typeof t.text_gone=="string",t.ms):typeof t.ms=="number"?await new Promise(s=>setTimeout(()=>s({ok:!0}),Math.min(Number(t.ms),w))):await o.waitSettled();return i().push("wait",r??e??n??(t.loaded?"loaded":String(t.ms??"")),a.ok,g(a)),a.ok}}export{p as waitVerb};
@@ -0,0 +1,7 @@
1
+ import type { ComputerUseDriver } from "../driver.ts";
2
+ import type { VerbSpec } from "../types.ts";
3
+ import { fnName, noteOf, type ApiHooks, type JournalLine, type RunLedger, type WriteRound } from "./api-contract.ts";
4
+ export { fnName, noteOf, type ApiHooks, type JournalLine, type RunLedger, type WriteRound };
5
+ export declare function isReadVerb(kind: string, args: Record<string, unknown>, verbs: readonly VerbSpec[]): boolean;
6
+ export declare function createComputerUseApi(driver: ComputerUseDriver, ledger: () => RunLedger, hooks?: ApiHooks): Record<string, unknown>;
7
+ export { renderJournal, stringify, verbGate } from "./api-run.ts";
@@ -0,0 +1 @@
1
+ import{STATE_CHANGING_ACTIONS as B}from"../driver.js";import{withoutBlankOptionals as G}from"./normalize.js";import{ambiguityNote as L,findScope as D,scopeLabel as U}from"./find-target.js";import{argComplaint as q}from"./validate.js";import{createTargeting as F}from"./api-target.js";import{waitVerb as H}from"./api-wait.js";import{expectVerb as W}from"./api-expect.js";import{checkShotPath as J,saveShot as Y}from"./shot-file.js";import{fnName as E,firstLine as x,firstText as O,noteOf as S,options as g}from"./api-contract.js";const z=600,j=150;function K(r,c,w){return r==="menu"&&!c.path||w.some($=>$.kind===r&&$.readOnly)}function ie(r,c,w={}){const $=r.extraVerbs(),V=new Map($.map(e=>[e.kind,e.schema])),C=[...V.keys()],{findWithMiss:v,asPoint:N,resolve:y,requireFocus:M}=F(r,c,w,x);let T=null;const k=(e,t)=>{const n=V.get(e);if(!n)return null;const o=G({kind:e,...t},n);return q(n,E(e),o)},R=async(e,t,n,o=!1)=>{c().charge(e);const s=k(e,t);if(s)throw c().push(e,n,!1,s,!1,"blocked"),new Error(s);const i=K(e,t,$);if(!i)try{await w.beforeAction?.(e,t)}catch(u){throw c().push(e,n,!1,u.message,!1,"blocked"),u}i||r.noteAction(e,t),i?r.noteContentRead():T=e,i||await r.ensureSettleBaseline(e);let a;try{a=e==="focus"?await r.focusTarget(String(t.target??"")):await r.perform(e,t)}catch(u){throw c().push(e,n,!1,`${u.message}; outcome unknown \u2014 observe before retrying`,i,i?void 0:"uncertain"),u}a.ok&&r.publishPreview(i?void 0:e,t);const l=(o||e==="type")&&a.ok&&a.verified===!1,f=e==="set_text"&&a.ok&&a.verified===void 0?`${S(a)||"write dispatched"}; read-back unavailable \u2014 the value was not verified`:S(a),m=a.mayHaveActed||a.verified===!1?"uncertain":a.ok?a.verified===!0?"verified":"dispatched":"blocked";c().push(e,n,a.ok&&!l,f,i,i?void 0:m);const p=c().round;if(p&&a.ok&&(e==="set_text"?(p.fields+=1,a.verified===!0&&(p.verified+=1)):p.ended=`${e}${t.chord?` ${String(t.chord)}`:""}${n?` ${n}`:""}`),!a.ok)throw new Error(`${e}${n?` ${n}`:""} failed: ${a.note??"no reason given"}`);if(l){const u=new Error(`${e==="type"?"type":"setText"} ${n}: ${a.note??"the field did not retain the requested text"}. The write was dispatched, but verification failed; subsequent actions were not run. `+(e==="type"?"Observe the focused field before continuing.":"Use setTextBestEffort only when you intend to handle a mismatched value yourself."));throw u.name="WriteVerificationError",u}return a},A=async(e,t,n,o)=>{const s=g("setText",o,"setText(target, text, {ifValue:previousValue})");if(Object.keys(s).some(f=>f!=="ifValue")||s.ifValue!==void 0&&typeof s.ifValue!="string")throw new Error("setText's only option is ifValue:string");const{ref:i,name:a}=await y(e,{write:!0});if(s.ifValue!==void 0&&(c().charge("precondition"),!r.readValue||!r.valueMatches(await r.readValue(i),s.ifValue))){const f="The field no longer matches ifValue, or cannot be read. Nothing was written; observe and reconcile the edit.";throw c().push("set_text",i,!1,f,!1,"blocked"),new Error(f)}return(await R("set_text",{ref:i,...a?{name:a}:{},text:String(t??"")},i,n)).verified},h=async(e,t,n)=>{const o=await R(e,t,n);return"value"in o?o.value:o.note||(n?`${e} ${n}`:e)},d=(e,t,n,o)=>async()=>{c().charge(e),r.noteContentRead();const s=await o();return r.publishPreview(),c().push(e,t,!0,n(s)),s},_=(e,t,n)=>async(...o)=>{if(o.length)throw new Error(`${e}() takes no arguments \u2014 ${t}`);return n()},I={observe:async(e={})=>{const t=g("observe",e,"observe() lists every element; observe({screenshot: true}) also attaches the picture"),n=Object.keys(t).filter(b=>b!=="screenshot");if(n.length)throw new Error(`observe() does not take ${n.join(", ")} \u2014 it already answers with EVERY element, on screen and beyond it (each one off-viewport carries \`offscreen: true\`); narrow it by filtering the array it returns. Its one option is screenshot: true.`);c().charge("observe");const{url:o,title:s,elements:i,groups:a,headings:l,partial:f,coverage:m,target:p}=await r.readElements();w.onRefs?.(i.map(b=>b.ref));let u="";if(t.screenshot){c().charge("screenshot");const b=await r.observeImage();for(const P of b.content)P.type==="image"&&(c().attach(P),u=" + image");u||(u=` (no image \u2014 ${x(b)})`)}return c().push("observe","",!0,`${i.length} elements${u}`),{url:o,title:s,elements:i,coverage:m,...p?{target:p}:{},...a?.length?{groups:a}:{},...l?.length?{headings:l}:{},...f?{partial:f}:{}}},find:async(e,t={})=>{c().charge("find");const n=D(t,r.readsOffscreen);let{h:o,miss:s}=await v(String(e??""),n);if(!o&&B.has(T??"")){T=null;for(let i=0;!o&&i<z;i+=j)await new Promise(a=>setTimeout(a,j)),{h:o,miss:s}=await v(String(e??""),n)}return c().push("find",`${String(e??"")}${U(n)}`,!0,o?L(o):s),o},text:_("text","it returns the surface's whole readable text",d("text","",e=>`${e.length} chars`,()=>r.readText())),value:async e=>{if(!r.readValue)throw new Error("value() is not supported on this platform \u2014 text() reads the whole surface instead");const t=await y(e);return d("value",t.ref,n=>`${n.length} chars`,()=>r.readValue(t.ref))()},page:async(e={})=>{const t=g("page",e,"page() reads the whole page and page({ref}) one node \u2014 to NAVIGATE, openUrl({url})");if(!r.readPage)throw new Error("page reading is not supported on this platform \u2014 use text()");if(t.ref&&!r.knownRef(t.ref))throw new Error(r.staleRefNote(t.ref));const n=d("page",t.ref??t.node??"",o=>x(o),()=>r.readPage(t));return O(await n())},screenshot:async(e={})=>{const{path:t,...n}=g("screenshot",e,"screenshot() takes the viewport; screenshot({ref}) / ({region}) / ({fullPage:true}) narrow it");if(n.ref&&!r.knownRef(n.ref))throw new Error(r.staleRefNote(n.ref));if(t!==void 0&&typeof t!="string")throw new Error("screenshot path must be a string");t&&J(t,r.screenshotMimeType);const o=!!(n.ref||n.region||n.fullPage||n.padding!==void 0);let s=null;const i=f=>s?`saved ${s.path}`:`no image \u2014 ${x(f)}`,l=await d("screenshot",n.ref??(n.fullPage?"full page":n.region?"region":""),i,async()=>{const f=await r.observeImage(o?{scope:n}:{}),m=f.content.find(p=>p.type==="image");return m&&m.type==="image"&&(s=Y(m.data,m.mimeType,t||void 0)),f})();for(const f of l.content)f.type==="image"&&c().attach(f);return s?{...s,note:x(l),...l.details.capture?{capture:l.details.capture}:{}}:{path:null,note:i(l)}},diagnostics:async(e={})=>{const t=g("diagnostics",e,"diagnostics() reads every channel, diagnostics({id}) one of them");return O(await d("diagnostics",t.id??"",x,async()=>r.readDiagnostics(t))())},targets:_("targets","it lists every surface there is",d("targets","",e=>`${e.length} targets`,()=>r.listTargets())),click:async(e,t={})=>{const n=N(e);if(n)return h("click",{...n,...t},`${n.x},${n.y}`);const{ref:o,name:s}=await y(e,{write:!0});return h("click",{ref:o,...s?{name:s}:{},...t},o)},hover:async e=>{const t=N(e);if(t)return h("hover",{...t},`${t.x},${t.y}`);const{ref:n,name:o}=await y(e);return h("hover",{ref:n,...o?{name:o}:{}},n)},setText:(e,t,n)=>A(e,t,!0,n),setTextBestEffort:(e,t,n)=>A(e,t,!1,n),keys:async(e,t,n)=>{const o=typeof t=="object"&&t!==null?t:n,s=typeof t=="number"?t:void 0;return o?.focus!==void 0&&await M(o.focus),h("keys",{chord:String(e??""),...s?{count:s}:{}},String(e??""))},scroll:async(e={})=>{const t=g("scroll",e,"scroll({direction:'down'}) or scroll({to:'bottom'})");if(t.into!==void 0){const n=typeof t.into=="string"?t.into:t.into?.ref;if(typeof n!="string")throw new Error("scroll({into}) takes a ref string or a find() handle");if(!r.scrollsInto)throw new Error("scroll({into}) is not available on this platform \u2014 scroll by direction, count or to:");const o=["direction","count","to","ref"].filter(i=>t[i]!==void 0);if(o.length)throw new Error(`scroll({into}) moves to one control and takes nothing else \u2014 drop ${o.join(", ")}`);w.onTarget?.(n);const s=await h("scroll",{into:n},n);return await r.readElements().catch(()=>{}),s}return typeof t.ref=="string"&&w.onTarget?.(t.ref),h("scroll",{...t},String(t.ref??t.direction??t.to??""))},wait:H(r,c),expect:W(r,c,async e=>{if(!r.readValue)throw new Error("This platform cannot read field values");const{ref:t}=await y(e);return r.readValue(t)}),focus:async e=>h("focus",{target:String(e??"")},String(e??"")),log:(...e)=>{c().charge("log"),c().push("log","",!0,e.map(Q).join(" ").slice(0,200))},act:async(e,t={})=>{if(typeof t.ref=="string"&&(w.onTarget?.(t.ref),!r.knownRef(t.ref)))throw new Error(r.staleRefNote(t.ref));return h(String(e??""),{...t},String(t.ref??""))}};for(const e of C)I[E(e)]=async(t={})=>{const n=g(E(e),t,`pass its arguments as an object, e.g. ${E(e)}({\u2026})`),o=n.ref!==void 0?await y(n.ref,{write:!0}):null;return h(e,{...n,...o?{ref:o.ref}:{}},String(n.ref??""))};return I}import{renderJournal as le,stringify as he,verbGate as pe}from"./api-run.js";import{stringify as Q}from"./api-run.js";export{ie as createComputerUseApi,E as fnName,K as isReadVerb,S as noteOf,le as renderJournal,he as stringify,pe as verbGate};
@@ -0,0 +1,54 @@
1
+ export type Handle = {
2
+ ref: string;
3
+ name: string;
4
+ role?: string;
5
+ matches?: number;
6
+ others?: Array<{
7
+ ref: string;
8
+ name: string;
9
+ role: string;
10
+ id?: string;
11
+ }>;
12
+ id?: string;
13
+ scope?: FindScope;
14
+ nearGroup?: string;
15
+ incidental?: boolean;
16
+ byValue?: boolean;
17
+ byShortName?: boolean;
18
+ };
19
+ export interface FindScope {
20
+ role?: string;
21
+ within?: string;
22
+ visible?: boolean;
23
+ near?: string;
24
+ }
25
+ interface Named {
26
+ ref: string;
27
+ name: string;
28
+ role: string;
29
+ id?: string;
30
+ state?: string;
31
+ offscreen?: boolean;
32
+ rect: {
33
+ x: number;
34
+ y: number;
35
+ w: number;
36
+ h: number;
37
+ };
38
+ }
39
+ type NamedGroup = {
40
+ label: string;
41
+ members: string[];
42
+ };
43
+ export declare function rankByName(elements: Named[], name: string, scope?: FindScope, groups?: NamedGroup[]): Handle | null;
44
+ export declare function findScope(opts: unknown, readsOffscreen: boolean): FindScope;
45
+ export declare function scopeLabel(scope: FindScope): string;
46
+ export declare function nearMissNote(elements: Named[], name: string, scope?: FindScope, inText?: boolean, groups?: NamedGroup[]): string;
47
+ export declare function tiedOthers(h: Handle): Array<{
48
+ ref: string;
49
+ name: string;
50
+ role: string;
51
+ id?: string;
52
+ }>;
53
+ export declare function ambiguityNote(h: Handle): string;
54
+ export {};
@@ -0,0 +1 @@
1
+ const O=3;function L(e){const n=/(?:^|,)\s*filled:([^,]*)/.exec(e.state??"");if(!n)return null;const t=n[1].trim();return t?{text:t.replace(/…$/,""),cut:/…$/.test(t)}:null}function N(e){const n=o=>o.rect.w*o.rect.h,t=(o,s)=>{const r=s.rect.x+s.rect.w/2,h=s.rect.y+s.rect.h/2;return n(s)<=n(o)&&o.rect.x<=r&&r<=o.rect.x+o.rect.w&&o.rect.y<=h&&h<=o.rect.y+o.rect.h},i=new Map;for(const o of e){const s=o.name.trim().toLowerCase();(i.get(s)??i.set(s,[]).get(s)).push(o)}const a=new Set;for(const o of i.values())if(!(o.length<2))for(const s of o)for(const r of o)s!==r&&t(s,r)&&a.add(s);return a.size?e.filter(o=>!a.has(o)):e}const S=3;function A(e,n){const t=e.trim().toLowerCase();return t.length<S||t.length>=n.length||!n.startsWith(t)?!1:/[^\p{L}\p{N}]/u.test(n.charAt(t.length))}function v(e,n,t=[]){if(!n)return;const i=n.trim().toLowerCase(),a=e.filter(s=>s.label.trim().toLowerCase()===i);if(!a.length&&t.some(s=>s.name.trim().toLowerCase()===i))return;const o=a.length?a:e.filter(s=>s.label.toLowerCase().includes(i));if(o.length>1)throw new Error(`near: "${n}" names ${o.length} groups \u2014 use a more specific group label or observe() and act by ref`);return o[0]}function E(e,n,t={},i=[]){const a=n.trim().toLowerCase(),o=v(i,t.near,e),s=o?new Set(o.members):null,r=I(e,t).filter(l=>!s||s.has(l.ref)),h=r.filter(l=>l.name.trim().toLowerCase()===a||(l.id??"").trim().toLowerCase()===a),d=h.length?[]:r.filter(l=>l.name.toLowerCase().includes(a)).sort((l,m)=>l.name.length-m.name.length),f=d.filter(l=>!G(l.name,a)),w=h.length||f.length?[]:r.filter(l=>A(l.name,a)).sort((l,m)=>m.name.trim().length-l.name.trim().length),g=h.length||f.length||w.length?[]:r.filter(l=>{const m=L(l);return!!m&&(m.cut?m.text.toLowerCase().startsWith(a):m.text.toLowerCase()===a)}),$=h.length||f.length||w.length||g.length?[]:r.filter(l=>L(l)?.text.toLowerCase().includes(a)).sort((l,m)=>(L(l)?.text.length??0)-(L(m)?.text.length??0)),y=!h.length&&!f.length&&w.length>0,c=!h.length&&!f.length&&!y&&(g.length>0||$.length>0),u=!h.length&&!f.length&&!y&&!c&&d.length>0,p=h.length?h:f.length?f:w.length?w:g.length?g:$.length?$:d,x=p.filter(l=>l.role!=="text"),C=N(T(x.length?x:p,e,o?void 0:t.near)),b=C[0];return b?{ref:b.ref,name:b.name,role:b.role,matches:C.length,...o?{nearGroup:o.label}:{},...b.id?{id:b.id}:{},others:C.slice(1,4).map(l=>({ref:l.ref,name:l.name,role:l.role,...l.id?{id:l.id}:{}})),...u?{incidental:!0}:{},...c?{byValue:!0}:{},...y?{byShortName:!0}:{},...t.role||t.within||t.visible||t.near?{scope:t}:{}}:null}function I(e,n){let t=e;if(n.role){const i=n.role.trim().toLowerCase();t=t.filter(a=>a.role.toLowerCase()===i)}if(n.visible&&(t=t.filter(i=>!i.offscreen)),n.within){const i=e.find(a=>a.ref===n.within)?.rect;if(!i)throw new Error(`within: ${n.within} is not in the current observation \u2014 observe() and use a ref it lists`);t=t.filter(a=>{const o=a.rect.x+a.rect.w/2,s=a.rect.y+a.rect.h/2;return o>=i.x&&o<=i.x+i.w&&s>=i.y&&s<=i.y+i.h})}return t}function T(e,n,t){if(!t)return e;const i=t.trim().toLowerCase(),a=n.find(r=>r.name.trim().toLowerCase()===i)??n.find(r=>r.name.toLowerCase().includes(i));if(!a)throw new Error(`near: nothing on the surface is named "${t}", so it cannot pick between the matches \u2014 read the surface with text() and name something that is really beside the one you mean`);const o=r=>({x:r.x+r.w/2,y:r.y+r.h/2}),s=o(a.rect);return[...e].sort((r,h)=>{const d=o(r.rect),f=o(h.rect);return Math.hypot(d.x-s.x,d.y-s.y)-Math.hypot(f.x-s.x,f.y-s.y)})}function j(e,n){if(e==null)return{};if(typeof e!="object")throw new Error(`find's second argument is an options object: {role, within${n?", visible":""}, near}`);const t=e,i=typeof t.within=="string"?t.within:t.within&&typeof t.within=="object"&&typeof t.within.ref=="string"?t.within.ref:void 0;if(t.within!==void 0&&i===void 0)throw new Error("find's `within` must be a ref string or a find() handle");if(t.visible&&!n)throw new Error("find's `visible` narrows nothing on this platform \u2014 its read never contains an off-screen control, so every match find returns is already on screen. Drop it; to reach what is scrolled out, scroll first, then find");return{...typeof t.role=="string"&&t.role.trim()?{role:t.role.trim()}:{},...i?{within:i}:{},...t.visible?{visible:!0}:{},...typeof t.near=="string"&&t.near.trim()?{near:t.near.trim()}:{}}}function _(e){const n=[...e.role?[`role:${e.role}`]:[],...e.within?[`within:${e.within}`]:[],...e.visible?["visible"]:[],...e.near?[`near:${e.near}`]:[]];return n.length?` {${n.join(", ")}}`:""}const k=6,M=40;function G(e,n){return e.length>M&&e.length>k*n.length}function R(e,n,t={},i=!1,a=[]){const o=n.trim().toLowerCase(),s=v(a,t.near,e);if(s)return`no matching control in group "${s.label}" \u2014 nothing outside this group's members was considered; observe() to check its controls`;const r=i?" \u2014 but the surface's TEXT carries it, so it IS here and is not a control: nothing to act on, read it with text(), or use it to scope another find (near:'\u2026')":"",h=e.filter(c=>c.name.trim()&&c.role!=="text");if(!h.length)return`no match \u2014 this surface lists no named controls at all${r}`;if(t.role||t.visible||t.within){const c=E(e,n,{});if(c)return`no match under ${t.role?`role:'${t.role}'`:t.visible?"visible:true":`within:${t.within}`} \u2014 without it the name finds ${c.ref} (${c.name.slice(0,40)}); widen the scope or name the control the surface actually carries`}const d=c=>c.toLowerCase().split(/[^\p{L}\p{N}]+/u).filter(u=>u.length>1),f=d(o);if(!f.length)return`no match${r}`;const w=h.map(c=>{const u=new Set(d(c.name)),p=f.filter(x=>u.has(x)).length;return{e:c,score:p/f.length}}).filter(c=>c.score>=.34).sort((c,u)=>u.score-c.score||c.e.name.length-u.e.name.length),g=new Set,$=w.filter(c=>{const u=`${c.e.role}\0${c.e.name.trim().toLowerCase()}`;return g.has(u)?!1:(g.add(u),!0)}).slice(0,3);return $.length?`no match \u2014 closest by name: ${$.map(c=>`${c.e.role} "${c.e.name.slice(0,40)}"`).join(", ")}${r}`:`no match \u2014 nothing among the ${h.length} named controls is close to it${r}`}function D(e){const n=e.scope??{};if((e.matches??1)<2||!e.others?.length)return[];if(!e.nearGroup&&n.near)return[];const t=e.name.trim().toLowerCase();return e.others.filter(i=>i.name.trim().toLowerCase()===t&&i.role===e.role)}function H(e){const n=e.matches??1,t=e.nearGroup?` (in group "${e.nearGroup}")`:"";if(e.incidental){const r=n>1?` (${n} such matches)`:"";return`${e.ref} \u2014 nothing here is NAMED that${r}; the closest is the word inside \xAB${e.name.slice(0,60)}${e.name.length>60?"\u2026":""}\xBB, which is probably not the control you meant \u2014 name it as the surface spells it, or observe() and pick a ref`}if(e.byShortName){const r=n>1?` (${n} such matches)`:"";return`${e.ref} \u2014 the surface spells it \xAB${e.name}\xBB${r}, which is where your name STARTS; the rest of what you asked for was matched against nothing. Controls relabel themselves as a flow progresses, so check this is the one you meant`}if(e.byValue){const r=n>1?` (${n} such matches)`:"";return`${e.ref} \u2014 matched by the VALUE it holds, not by a name${r}: ${e.name?`its own name is "${e.name}"`:"it has no name of its own"}. A file row and a filled field are the usual shape`}if(n<=1)return e.ref+t;const i=r=>r.id&&r.id.toLowerCase()!==r.name.trim().toLowerCase()?` \u27E8id: ${r.id}\u27E9`:"",a=(e.others??[]).map(r=>`${r.ref} "${r.name}"${i(r)}`).join(", "),o=n-1-(e.others?.length??0),s=e.id||(e.others??[]).some(r=>r.id)?", or name one of their ids \u2014 exact, and never translated":"";return`${e.ref}${i(e)}${t} \u2014 1 of ${n} matching "${e.name}" (also ${a}${o>0?`, +${o} more`:""}); name it more precisely if that is the wrong one${s}`}export{H as ambiguityNote,j as findScope,R as nearMissNote,E as rankByName,_ as scopeLabel,D as tiedOthers};
@@ -0,0 +1,8 @@
1
+ import { type ComputerUseToolSpec } from "../types.ts";
2
+ import { type PlatformSlice } from "./repl.ts";
3
+ import type { OwnershipProvider } from '../ownership.ts';
4
+ export declare function createComputerUseTools(mounted: PlatformSlice | PlatformSlice[], opts?: {
5
+ diagnostics?: boolean;
6
+ docsDelivered?: boolean;
7
+ ownership?: OwnershipProvider;
8
+ }): ComputerUseToolSpec[];
@@ -0,0 +1 @@
1
+ import{SurfaceGoneError as p,SurfaceUnreadableError as f,spec as l,text as i}from"../types.js";import{withoutBlankOptionals as m}from"./normalize.js";import{createReplTools as d}from"./repl.js";import{argComplaint as h}from"./validate.js";function g(e,r){const c=e.execute.bind(e);return l({...e,async execute(u){const t=r.epoch,o=r.p.then(async()=>{if(t!==r.epoch)return i("This queued call was cancelled by a runtime reset; bind fresh handles before continuing.",{error:"runtime_reset"});const a=m(u,e.parameters),n=h(e.parameters,e.name,a);if(n)return i(n,{error:"bad_arguments"});try{return await c(a)}catch(s){if(s instanceof p)return i(s.message,{error:"surface_gone"});if(s instanceof f)return i(s.message,{error:"surface_unreadable"});throw s}});return r.p=o.then(()=>{},()=>{}),o}})}function v(e,r={}){const c=Array.isArray(e)?e:[e];for(const{driver:n}of c)r.diagnostics&&n.supportsDiagnostics&&n.enableDiagnostics();const[u,t]=d(c,{docsDelivered:r.docsDelivered,ownership:r.ownership}),o={p:Promise.resolve(),epoch:0},a=t.execute.bind(t);return t.execute=async n=>(o.epoch++,a(n)),[g(u,o),t]}export{v as createComputerUseTools};
@@ -0,0 +1,2 @@
1
+ import type { TSchema } from "typebox";
2
+ export declare function withoutBlankOptionals(params: unknown, schema: TSchema): unknown;
@@ -0,0 +1 @@
1
+ function c(e){if(typeof e=="string")return e.trim()==="";if(typeof e!="object"||e===null||Array.isArray(e))return!1;const n=Object.values(e);return n.length>0&&n.every(t=>t===0)}function f(e,n){const t=Object.entries(n.properties??{}).filter(([,r])=>r.const!==void 0);return t.length>0&&t.every(([r,i])=>e[r]===i.const)}function l(e,n){if(n.anyOf)return n.anyOf.filter(r=>f(e,r));const t=Object.entries(n.properties??{}).filter(([r,i])=>i.anyOf&&!(r in e)).flatMap(([,r])=>r.anyOf.filter(i=>f(e,i)));return[n,...t]}function s(e,n){if(typeof e!="object"||e===null||Array.isArray(e))return;const t=e;for(const r of l(t,n)){const i=r.required??[];for(const[o,u]of Object.entries(r.properties??{}))o in t&&(!i.includes(o)&&c(t[o])?delete t[o]:s(t[o],u))}}function y(e,n){if(typeof e!="object"||e===null)return e;const t=structuredClone(e);return s(t,n),t}export{y as withoutBlankOptionals};
@@ -0,0 +1,8 @@
1
+ import { type ToolResult } from "../types.ts";
2
+ export declare const MAX_ACTIONS = 100;
3
+ export declare const MAX_MS = 60000;
4
+ export declare const SYNC_MAX_MS = 10000;
5
+ export declare const RETURN_CAP = 12000;
6
+ export declare const MAX_REF_OWNERS = 4000;
7
+ export type Render = "full" | "brief" | "none";
8
+ export declare function trimObservation(r: ToolResult, render: Render): ToolResult;
@@ -0,0 +1,4 @@
1
+ import"../types.js";const f=100,x=6e4,_=1e4,d=12e3,u=4e3;function A(t,o){if(o==="full")return t;const n=t.content.find(e=>e.type==="text");if(!n||n.type!=="text")return t;const p=n.text.split(`
2
+ `),i=o==="brief"?t.details.headLines:t.details.actionLines??t.details.ledgerLines;if(i===void 0)return t;const s=p.slice(0,Number(i)),c=Array.isArray(t.details.keep)?t.details.keep:[],a=new Set(s);for(const e of c)e.split(`
3
+ `).every(l=>a.has(l))||s.push(e);return n.text=s.join(`
4
+ `),t.content=t.content.filter(e=>e.type!=="image"),t.details.observation=o,t}export{f as MAX_ACTIONS,x as MAX_MS,u as MAX_REF_OWNERS,d as RETURN_CAP,_ as SYNC_MAX_MS,A as trimObservation};
@@ -0,0 +1 @@
1
+ export declare function prepareCode(src: string): string;
@@ -0,0 +1,4 @@
1
+ import{parse as d}from"acorn";function r(n,s){switch(n.type){case"Identifier":s.push(n.name);break;case"ObjectPattern":for(const t of n.properties)r(t.type==="RestElement"?t.argument:t.value,s);break;case"ArrayPattern":for(const t of n.elements)t&&r(t,s);break;case"AssignmentPattern":r(n.left,s);break;case"RestElement":r(n.argument,s);break}}function u(n){const s=d(n,{ecmaVersion:"latest",sourceType:"script",allowAwaitOutsideFunction:!0,allowReturnOutsideFunction:!0}),t=[],i=[],l=e=>n.slice(e.start,e.end);for(const e of s.body)if(e.type==="VariableDeclaration"){const c=e.declarations.map(a=>(r(a.id,t),`(${l(a.id)} = ${a.init?l(a.init):"undefined"})`));i.push({start:e.start,end:e.end,text:`${c.join(", ")};`})}else(e.type==="FunctionDeclaration"||e.type==="ClassDeclaration")&&e.id&&(t.push(e.id.name),i.push({start:e.end,end:e.end,text:";"}),i.push({start:e.start,end:e.start,text:`${e.id.name} = `}));let o=n;for(const e of i.sort((c,a)=>a.start-c.start))o=o.slice(0,e.start)+e.text+o.slice(e.end);return`${t.length?`var ${[...new Set(t)].join(", ")};
2
+ `:""}(async () => {
3
+ ${o}
4
+ })()`}export{u as prepareCode};
@@ -0,0 +1,5 @@
1
+ import type { ComputerUseDriver } from "../driver.ts";
2
+ export declare const REPL_DESCRIPTION: string;
3
+ export declare const RESET_DESCRIPTION: string;
4
+ export declare function apiReference(driver: ComputerUseDriver): string;
5
+ export declare function platformReference(driver: ComputerUseDriver): string;
@@ -0,0 +1,4 @@
1
+ import{fnName as u}from"./api.js";const w="Run JavaScript in this session's PERSISTENT runtime to look at and act on the GUI \u2014 web pages in a browser, Electron apps and native macOS / Windows apps, whichever this server mounts \u2014 the one tool for both. The code is an async function body: `await` every verb, `return` a value to get it back as JSON, and top-level const/let/function declarations SURVIVE to later calls \u2014 keep handles and collected data in them instead of re-reading. Surfaces (tabs / windows / apps) are HANDLES and EVERY verb lives on one \u2014 there are no top-level verbs. Bind first: computer.surface('<id or title>') names one, computer.surface() alone takes the only surface when there is just one, computer.surfaces() lists them. Then w.observe() w.find(name) w.click(t) w.setText(t, text) w.keys(chord, n) w.scroll({\u2026}) w.wait({\u2026}) w.text() w.page({\u2026}) w.screenshot({\u2026}) and this platform's own verbs, each switching to that surface as needed; a ref read on one surface is refused on another. Bind ONCE \u2014 keep the handle in a top-level variable and later calls reuse it. The first bound operation claims the target across calls; await w.release() when its task is done. Another runtime's claim refuses input before dispatch. The first result carries the full API reference (computer.help() returns it again) \u2014 use only what it documents. One verb at a time (one focus, one caret): a failed action or expect() throws and stops the run; wait() instead returns false on timeout, so check it or use expect() for a required condition. The result is the step journal plus one observation of the surface it acted on. Per call: 100 actions, 60 s. No require/import/fetch/process/timers. Before using an operation that may affect the user's foreground, real pointer, keyboard focus or system clipboard (including automatic fallback), explain its target and effects and obtain permission unless existing permission covers them. Do not silently escalate a background operation; inspect computer.help() for platform-specific effects. Before sending a message or publishing a post, let the user confirm its content and recipients or destination, unless they already approved those exact details.",m=`Reset computer_repl's persistent JavaScript runtime: every binding \u2014 handles, variables, functions \u2014 is discarded, and the next computer_repl call starts fresh and returns the API reference again. Nothing on the surfaces changes: no tab, window or app is closed. This runtime's target ownership is released after any active call stops. Use it only when the runtime is wedged or its state is wrong, never as a way to "start over" on the screen.`;function p(e,a){const o=a,s=Object.keys(o.properties??{}).filter(t=>t!=="kind"),n=new Set(o.required??[]),r=t=>n.has(t)?t:`${t}?`,h=s.filter(t=>t!=="ref"&&t!=="name").map(t=>o.properties?.[t]?.description).find(Boolean),i=o.description??h??"",c=/^.*?[.!?](?=\s+[A-Z(`]|$)/s.exec(i)?.[0]??i,d=s.filter(t=>t==="ref"||t==="name").flatMap(t=>o.properties?.[t]?.description?[` ${r(t)}: ${o.properties[t].description}`]:[]);return[` w.${u(e)}({${s.map(r).join(", ")}})${c?` \u2014 ${c.trim()}`:""}`,...d,...n.has("ref")&&s.includes("name")?[" name does not replace the required ref."]:[]].join(`
2
+ `)}function l(e){const a=e.extraVerbs();return{head:["API reference \u2014 computer_repl (this text comes once per session; computer.help() returns it again)","","Everything is async \u2014 the verbs AND computer.surfaces() / computer.surface(\u2026): await each. A verb that","fails THROWS with the reason, which stops the run; the journal in the result says what ran. Refs (e12)","come from observe()/find() and stay valid until the surface changes; never invent one, and never carry one in from","another session \u2014 a fresh session re-mints them, so the same e12 names a different element. A target `t` is "+"a find() handle, a ref string, or a point {x, y} read off a screenshot"+".","","Bind a surface first \u2014 every verb below is a method on the handle it returns, never a name of its own:",' await computer.surfaces() \u2192 [handle] \u2014 every surface (tab / window / app) as {id, kind, title, active}; `id` is an opaque key, what the platform knows the surface by lives in `title` (macOS: "TextEdit (com.apple.TextEdit) \u2014 Untitled"; Windows: the window title, then the program name \u2014 "Untitled - Notepad \u2014 Notepad")'," await computer.surface(name) \u2192 handle \u2014 by id, by whole title, or by a piece of the title; several matches or none is a refusal that lists the candidates, never a guess",' await computer.surface() \u2192 handle \u2014 the ONE surface, when there is only one. Not "whatever is in front": with more than one open it refuses and lists them, so a script always says which it meant'," Bind once per surface and keep the handle in a top-level declaration \u2014 it stays valid for later calls, and re-binding costs a read each time."," A browser tab marked unloaded:true is still discoverable. Using its handle restores that exact tab in the background after acquiring ownership; merely listing tabs never reloads it.",""],reads:[...e.interactionNotes(),"Reads (return data to your code; none of them is shown to you except through `return` or computer.log):",e.readsOffscreen?" w.observe({screenshot?}) \u2192 {url, title, elements:[{ref, role, name, state, rect, offscreen?}], groups?, headings?, partial?, coverage, target?} \u2014 every control, viewport and beyond; filter the array rather than asking it to narrow. `rect` {x, y, w, h} is the control's box \u2014 compare boxes to answer layout questions (which row a control sits on, whether it moved) without a screenshot. `groups` is the page's OWN labelling of those controls \u2014 [{label, members:[ref]}] over that same set, off-viewport members included \u2014 present where the author wrote one, and the only place some facts exist (a feed labels each post's button row with its counts, bookmarks included, which no control name and no page text carries). `headings` (web surfaces) is [{name, level, rect}] for the page's headings, never listed as controls \u2014 the fixed point to measure a control's box against, since a click can scroll the container under every control in a card. `partial` (a reason) says the read stopped before covering the surface, so what it did not list may still be there. screenshot:true also attaches the picture of the same screen (one call, two reads), for when you need to SEE where something is":" w.observe({screenshot?}) \u2192 {url, title, elements:[{ref, role, name, state, rect}], coverage} \u2014 every control the window SHOWS (`rect` {x, y, w, h} is its box \u2014 compare boxes to answer layout questions without a screenshot); one scrolled out of view is counted in the observation and never listed, so no ref names it until a scroll brings it in; filter the array rather than asking it to narrow. screenshot:true also attaches the picture of the same screen"," coverage reports exact {offscreen, covered, capped, modal?} counts (null when unreported) even when repeated context notes are omitted. Electron target reports the CDP endpoint, windowId and, when its user agent carries them, the app's own name/version; never infer the app version from the tool or helper path."," w.find(name, {role?, within?, "+(e.readsOffscreen?"visible?, ":"")+"near?}) \u2192 {ref, name, matches, others, incidental?} | null \u2014 by visible name, "+(e.readsOffscreen?"whole surface":"everything the window shows \u2014 NOT what is scrolled out of it, which this platform's read does not contain")+" (an exact name beats a partial one, a partial the query is most of beats one it is only a fragment of, and a control beats a text label); a handle that re-resolves itself when used, so a loop survives a re-render; `matches` says how many controls carried the name and `others` names the runners-up, so an ambiguous name is visible BEFORE you act on the pick; `incidental: true` says NOTHING here is NAMED that and the pick is only a long name that happens to contain the word \u2014 the one `matches: 1` not to trust; narrow it with role (button / textbox / \u2026), within (a ref or handle \u2014 matches inside that element's box, e.g. one sheet or one message)"+(e.readsOffscreen?" visible (on screen only)":"")+" and near (a labelled group restricts candidates to its members; otherwise prefer the control closest to the named element). Duplicate group labels or same-named controls within a group still need disambiguation"," w.text() \u2192 the surface's full readable text",...e.readValue?[" w.value(t) \u2192 ONE control's whole value as a string \u2014 a long field, a clipped label, the text you are about to edit. The listing shows a value cut to what a line holds; this is the rest of it, for one element, where text() re-reads the entire surface"]:[],...e.readPage?[" w.page({ref?, depth?, node?}) \u2192 the content tree as text, with a completeness receipt on its first line"]:[]," w.screenshot({ref?, padding?|region:{x,y,width,height}?|fullPage?, path?}) \u2192 {path, host, width, height, note, capture?} \u2014 the image is attached to this result for you to see AND written to a file the code can keep: to `path` (absolute, never overwrites an existing file, extension must match the surface's format \u2014 .jpg on web surfaces, .png on macOS and Windows) or else to a rolling temp directory; `host` names the machine the path is on; no image \u2192 {path: null, note: why}. ref is a ref string and crops to a visible element's box; padding keeps its surroundings. fullPage is web-only. On macOS and Windows, ref/region preserve native detail; region uses FULL screenshot pixels, so take a full screenshot first. Windows padding and crop bounds use physical pixels; macOS padding uses full-screenshot pixels when available, otherwise window points. capture reports the window, crop, units, pointsPerPixel, and toFullScreenshot (null without a full screenshot or when window geometry changed). Windows refuses a region based on old window geometry; take a new full screenshot. Convert crop pixel p to full screenshot coordinates with toFullScreenshot.origin + p * toFullScreenshot.scale on each axis. A crop never changes point-action coordinates; use a current mapping or act by ref.",...e.supportsDiagnostics?[` w.diagnostics({id?}) \u2192 ${e.diagnosticsAbout.empty} \u2014 the newest 20, with the ones new since your last read marked "new"`]:[]," computer.log(\u2026) \u2192 a line in the journal; the one member that is not on a surface",""],actions:["Actions (every one is w.verb(\u2026) on a bound handle, which switches to that surface first):"," w.click(t, {button?, count?}) w.hover(t)"," w.setText(t, text) \u2014 replace the target field's ENTIRE value, even after selectText; never a selection/caret edit. Returns true when read-back matches; throws WriteVerificationError on mismatch, stopping the run unless caught."," w.setTextBestEffort(t, text) \u2014 the same whole-field replacement, returning true/false without throwing on mismatch. Both return undefined when read-back is unavailable; that is not verification."," click and hover also take a POINT \u2014 w.click({x, y}) \u2014 in the coordinates of the last screenshot, for a control you"," can see and cannot name. It checks no identity (no stale-ref gate, no name, no surface check) and so acts on"," whatever is at that spot NOW: take observe({screenshot: true}) and read the coordinates off that picture."," click / hover / keys / scroll / focus answer with what they did, as text \u2014 the same note the journal shows, e.g. which"," rung a click ended on. A verb that FAILS throws instead, so a returned string always describes something that happened."," w.keys(chord, count?, {focus?}) e.g. w.keys('Enter'), w.keys('Backspace', 3), w.keys('Enter', {focus: field}) \u2014 one key or chord, never a text string. setText replaces a whole field; use the platform's paste/type verb to edit at a selection or caret."," keys go wherever the surface has the keyboard focus. Pass focus: <target> to REQUIRE it be there: if it is not,"," nothing is pressed, including when focus is unknown or ambiguous. Omit focus only to send without that check. Focusing stays explicit (click / setText / selectText with a caret)."," w.scroll({direction?: up|down|left|right, count?: screens (default 1), to?: top|bottom, ref?}) w.wait({} | {ms} | {text} | {text_gone} | {control} | {control_gone} | {url} | {url_gone} | {loaded}) \u2192 ok"," count covers that many screens in ONE call \u2014 the note says how far it actually got, which is less once the end is reached;"," count and to: are refused together, since to: already goes as far as the surface scrolls."," Browser/Electron list scrollable regions with refs. With several regions, pass ref:<region or a control inside it>; a bare scroll refuses and lists the choices. ref:'window' explicitly scrolls the page. Reaching an edge never switches to another region.",...e.scrollsInto?[" w.scroll({into: t}) instead brings ONE control (a ref or a find() handle) to the middle of the viewport and says whether anything still covers it. It takes a covered control's ref too \u2014 the way out from under a sticky header or a bottom strip that a screen of scrolling overshoots."]:[]," a wait polls until the condition holds or it times out, and says which: {text\u2026} searches the readable text,",' {control\u2026} the CONTROLS by name ("until the Stop button is gone" \u2014 the honest way to wait out a long answer),'," {url}/{url_gone} where the surface IS (substring, so a query string needs no spelling out), and {loaded:true} that"," the DOCUMENT finished. Reach for it before reporting that a page has nothing on it: a document that never finishes"," reads as an empty page and no content condition can tell you which it was. It is NOT rendered \u2014 a single-page app"," reaches loaded with nothing drawn yet (measured), so follow it with the CONTENT wait, which is w.wait({}):",' it polls until the readable text stops changing, so it is the one "the page is ready" condition that needs no word'," from the page and no knowledge of the site. Use it after a navigation, a tab switch inside a page, or a search \u2014 a"," single-page app mounts its rows well after {loaded}, and an observation taken before that reads as an empty page."," Also after a click that REDRAWS content (a chart, a list) and before you screenshot or read it: the control's own state"," updates at once, the content a beat later, so a read straight after the click can pair the new button with the old view."," Every condition takes ms as its OWN polling budget (default 16s): w.wait({text:'Done', ms:3000}) gives up in three seconds,"," give or take the one read already in flight when it runs out \u2014 on a heavy surface a single read is seconds, and the result says so when that happened."," wait() returns true/false; awaiting it alone does NOT stop on timeout. Use expect() for a required postcondition, or check the result: if (!await w.wait({...})) throw new Error('condition not met');"," which is what a GUESS at the wording is worth \u2014 an unguarded guess that no page ever matches costs the full 16s."," w.focus() \u2014 select w as the target for this runtime; target selection is not an OS-activation guarantee (see platform interaction notes)"," w.release() \u2014 release this target and its input resources for other sessions. Claims span calls until release, reset, turn end or process exit. Observe again before acting after handoff; old refs are invalidated. Separate windows can still share app/account/document state; ownership does not isolate those or a human editor."," w.expect({text, control, url, value, equals, ms}) \u2014 verify exactly ONE condition; value takes a ref/find handle and equals:string. Read-only polling, default 3000 ms, maximum 16000 ms. Returns true when verified; otherwise throws before subsequent steps. It never repeats the preceding action. Use it to verify save/submit results, not screen-change verdicts."," w.setText(t, text, {ifValue:previousValue}) \u2014 optional precondition: refuse before writing if the field changed or cannot be read. This is a checked precondition, not an atomic transaction with a human or another app.","Action journal evidence: [verified] confirms the stated value/condition; [dispatched] only confirms delivery; [blocked] means no dispatch; [uncertain] may already have acted. Never replay an uncertain send/submit/delete; inspect its actual result first."],extras:[...a.length?[" This platform's verbs \u2014 each returns what it did as text (the journal keeps only its first line, so a listing is read from the return value):"," In these signatures, ? marks an optional property; every unmarked property is required. Omit the ? in JavaScript calls.",...a.map(s=>p(s.kind,s.schema))]:[],...a.some(s=>s.kind==="select_option")?[" selectOption example: const choice = await w.find('Priority', {role:'combobox'}); if (!choice || choice.matches !== 1 || choice.incidental) throw new Error('Priority is missing or ambiguous'); await w.selectOption({ref:choice.ref, option:'High'});"]:[],...a.some(s=>s.kind==="run_js")?[' runJs is the exception: it returns the VALUE the code produced, whole (the journal shows a short preview); an exception thrown in the page comes back as the string "threw: \u2026" instead of stopping the run.']:[],...a.some(s=>s.kind==="download")?[` download also returns a VALUE: const watch = await w.download(); await w.click(await w.find('Download')); const result = await w.download({id:watch.id}); return result; result.downloads carries the actual path, filename, host and state. Read/upload only complete receipts. Query the same ID after a timeout; several candidates mean ambiguous, never the newest file. Re-arming this tab ends its previous collection; stop:true explicitly ends collection without canceling any download. On this session's own tabs a page's save dialog (showSaveFilePicker) never opens: its file arrives as a download marked source "save-picker".`]:[]],tail:[" w.act(kind, {\u2026}) \u2014 any verb by its wire name, arguments forwarded whole; returns its text like the platform verbs","","Observation changes use + for added controls, - for removed controls, and ~ for a surviving ref whose state or label changed. Scope and grouping explanations repeat only when their context changes; observe() still returns the complete available data.","The changed/frozen verdict compares this run's end with the previous run's end: a run whose last action puts the","surface back where it started reads as unchanged \u2014 return your own intermediate observe() results when you need proof.","","Rules that are enforced, not advised:"," \xB7 one verb in flight \u2014 a second one before the first resolves is refused by name (one focus, one caret)"," \xB7 a ref read on surface A is refused on surface B \u2014 act on it through A's handle"," \xB7 a handle whose surface is gone throws: re-acquire with computer.surfaces()"," \xB7 100 actions and 60 s per call; reads, find(), waits, verification and computer.log() also spend this budget"," \xB7 nothing outside this API exists: no require, import, fetch, process, timers, console","","Persistence: top-level const/let/var/function/class declarations live until computer_repl_reset. Keep a","handle in one and reuse it; collect results in a variable across calls; return only what you need.","Batch at verified task boundaries, with room for observation and recovery. A helper's nested verbs also count: a loop of 35 find/setText/expect sequences exceeds 100 actions. Split it into smaller calls.","Keep a top-level checkpoint after each confirmed stage. After an uncertain save/submit, inspect or expect its result before resuming; replaying the whole batch can submit twice. A dispatched checkpoint is not proof of success. Reset or runtime restart loses checkpoints, so recover from surface facts then.","Returned values are capped at 12000 characters and a cut JSON value is not parseable. Read a large result once into a top-level variable, return a small slice per call, and check the final count/identity; do not re-read or replay the task to retrieve the next slice."]}}function g(e){const a=l(e);return[...a.head,...a.reads,...a.actions,...a.extras,...a.tail].join(`
3
+ `)}function v(e){const a=l(e);return[`Reaching a second platform: ${e.platform} \u2014 everything else in the reference above still holds, and these are the reads and verbs that are its own.`,"",...a.reads,...a.extras].join(`
4
+ `)}export{w as REPL_DESCRIPTION,m as RESET_DESCRIPTION,g as apiReference,v as platformReference};
@@ -0,0 +1,21 @@
1
+ import type { ComputerUseDriver } from "../driver.ts";
2
+ import type { TargetInfo } from "../types.ts";
3
+ export interface PlatformSlice {
4
+ driver: ComputerUseDriver;
5
+ }
6
+ export interface Slice extends PlatformSlice {
7
+ id: string;
8
+ api: Record<string, unknown>;
9
+ docsDue: boolean;
10
+ lastMiss: string | null;
11
+ }
12
+ export type SurfaceHandle = Record<string, unknown> & {
13
+ id: string;
14
+ kind: string;
15
+ title: string;
16
+ own?: boolean;
17
+ };
18
+ export type MountedTarget = TargetInfo & {
19
+ slice: Slice;
20
+ inner: string;
21
+ };
File without changes
@@ -0,0 +1,8 @@
1
+ import { type ComputerUseToolSpec } from "../types.ts";
2
+ import { type OwnershipProvider } from "../ownership.ts";
3
+ import type { PlatformSlice } from "./repl-state.ts";
4
+ export type { PlatformSlice } from "./repl-state.ts";
5
+ export declare function createReplTools(mounted: PlatformSlice[], opts?: {
6
+ docsDelivered?: boolean;
7
+ ownership?: OwnershipProvider;
8
+ }): ComputerUseToolSpec[];