@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,23 @@
1
+ import type { ActResult, TargetInfo } from "../types.ts";
2
+ export declare const LAUNCH_WAIT_MS = 10000;
3
+ export declare const LAUNCH_NEEDS_DESKTOP: string;
4
+ export interface StartApp {
5
+ name: string;
6
+ id: string;
7
+ }
8
+ export interface Started {
9
+ name: string;
10
+ via: string;
11
+ pid: number;
12
+ }
13
+ interface Row {
14
+ hwnd: number;
15
+ pid: number;
16
+ process: string;
17
+ title: string;
18
+ }
19
+ export declare function filterApps(list: StartApp[], query: string): ActResult;
20
+ export declare function startAppTarget(list: StartApp[], query: string): TargetInfo | null;
21
+ export declare function launchedBy(app: string, started: Started): (r: Row) => boolean;
22
+ export declare function pickAppeared<R extends Row>(appeared: R[], named: (r: R) => boolean): R | undefined;
23
+ export {};
@@ -0,0 +1 @@
1
+ const s=1e4,r="starting an app opens its window in front of the user and takes their keyboard focus, so it needs interaction:'desktop': explain that and obtain permission first. Nothing was started";function a(t,e){const n=e.trim().toLowerCase(),i=n?t.filter(o=>o.name.toLowerCase().includes(n)||o.id.toLowerCase().includes(n)):t;return{ok:!0,value:i,note:`${i.length} of ${t.length} Start-menu apps${n?` match "${e}"`:""}`}}function p(t,e){const n=e.trim().toLowerCase(),i=n?t.filter(o=>o.id.toLowerCase()===n||o.name.toLowerCase()===n):[];return i.length!==1?null:{id:`app:${i[0].id}`,kind:"app",title:`${i[0].name} \u2014 no window open; binding it starts the app (its window opens in front of the user)`,active:!1}}function c(t,e){const n=t.trim().toLowerCase().split(/[\\/]/).pop().replace(/\.exe$/,""),i=e.name.toLowerCase();return o=>e.pid!==0&&o.pid===e.pid||o.process.toLowerCase()===n||o.title.toLowerCase().includes(i)}function d(t,e){return t.find(e)??(t.length===1?t[0]:void 0)}export{r as LAUNCH_NEEDS_DESKTOP,s as LAUNCH_WAIT_MS,a as filterApps,c as launchedBy,d as pickAppeared,p as startAppTarget};
@@ -0,0 +1,7 @@
1
+ import type { ActResult } from "../types.ts";
2
+ export interface MenuHost {
3
+ call<T>(op: string, params?: Record<string, unknown>): Promise<T>;
4
+ hwnd(): number;
5
+ canOpen: boolean;
6
+ }
7
+ export declare function menuVerb(host: MenuHost, rawPath: unknown): Promise<ActResult>;
@@ -0,0 +1,6 @@
1
+ import{UiaRequestError as $}from"./uia-client.js";function d(e){return e.split(" ")[0].replace(/\s*\(&?[^()\s]\)/g,"").replace(/&(.)/g,"$1").replace(/(…|\.\.\.)\s*$/,"").trim()}const f=(e,n)=>d(e).toLowerCase()===d(n).toLowerCase(),m=e=>e.map(d).join(" \u203A "),E=new Set(["\u7CFB\u7EDF","system","\u7CFB\u7EDF\u83DC\u5355\u680F","system menu bar"]);function C(e){return`${m(e.path)}${e.submenu?" \u203A":""}${e.checked?" (checked)":""}${e.enabled?"":" (disabled)"}`}function g(e,n,o){const t=e.filter(s=>s.enabled),a=e.length-t.length,r=(a?`
2
+ (${a} marked disabled do not apply to what is open or selected right now)`:"")+(o.length?`
3
+ (${o.map(s=>`${s} \u203A`).join(", ")} list their items only once opened \u2014 that needs interaction:'desktop')`:"");return{ok:!0,note:`${t.length} menu commands runnable (${n}; listed below \u2014 this verb returns them):
4
+ ${e.map(C).join(`
5
+ `)}${r}`}}function L(e){return e.split(`
6
+ `).slice(1).filter(n=>n&&!n.startsWith("(")&&!n.endsWith("(disabled)"))}function I(e,n){const o=e.find(t=>t.path.length===n.length&&t.path.every((a,r)=>f(a,n[r])));if(o)return o;if(n.length===1){const t=e.filter(a=>f(a.path[a.path.length-1],n[0]));if(t.length===1)return t[0];if(t.length>1)return`"${n[0]}" names ${t.length} menu commands (${t.map(a=>m(a.path)).join(", ")}) \u2014 give the whole path`}return`no menu command "${n.join(" \u203A ")}" \u2014 menu({}) lists them`}async function O(e){try{return(await e.call("hmenu",{hwnd:e.hwnd()})).items}catch(n){if(n instanceof $&&n.code==="unknown_op")return[];throw n}}async function _(e,n,o){const t=I(n,o);return typeof t=="string"?{ok:!1,note:t}:t.submenu?g(n.filter(a=>a.path.length===t.path.length+1&&t.path.every((r,s)=>r===a.path[s])),`items of ${m(t.path)}`,[]):t.enabled?(await e.call("hmenucmd",{hwnd:e.hwnd(),item:t.id}),{ok:!0,note:`ran ${m(t.path)} through the menu's own command (no menu was opened) \u2014 observe for its effect; a dialog it opens is listed with the window`}):{ok:!1,note:`${m(t.path)} is disabled right now \u2014 the app does not offer it for what is open or selected. Nothing was run`}}async function w(e){const n=(await e.call("tree",{hwnd:e.hwnd(),max:3e3})).elements,o=[],t=[];return n.forEach((a,r)=>{for(;t.length&&n[t[t.length-1]].depth>=a.depth;)t.pop();o.push(t.length?t[t.length-1]:-1),t.push(r)}),{nodes:n,parent:o}}function T(e){const n=(t,a)=>{if(t.id==="SystemMenuBar"||E.has(d(t.name).toLowerCase()))return!1;for(let r=e.parent[a];r!==-1;r=e.parent[r])if(["TitleBar","MenuItem"].includes(e.nodes[r].type))return!1;return p(e,a).length>0},o=e.nodes.findIndex((t,a)=>t.type==="MenuBar"&&n(t,a));return o!==-1?o:e.nodes.findIndex((t,a)=>t.type==="Menu"&&n(t,a))}function p(e,n){const o=[];return e.nodes.forEach((t,a)=>{if(t.type!=="MenuItem")return;let r=e.parent[a];for(;r!==-1&&r!==n&&e.nodes[r].type!=="MenuItem";)r=e.parent[r];r===n&&o.push(a)}),o}const k=e=>!e.offscreen&&e.rect.w>0&&e.rect.h>0,b=e=>`${e.name}|${e.rect.x},${e.rect.y}`,v=e=>e.expanded==="collapsed"||e.expanded==="expanded",A=e=>new Promise(n=>setTimeout(n,e));async function j(e,n){try{await e.call("pattern",{key:n.key,action:"expand"})}catch(o){if(!(o instanceof $))throw o;await S(e,n)}await A(250)}async function S(e,n){try{return await e.call("press",{key:n.key})}catch(o){if(!(o instanceof $)||o.code!=="no_action"&&o.code!=="prefer_mouse")throw o;return await e.call("click",{x:Math.round(n.rect.x+n.rect.w/2),y:Math.round(n.rect.y+n.rect.h/2),button:"left",count:1}),{via:"mouse"}}}function B(e,n,o){const t=new Set(e.nodes.filter(i=>i.type==="MenuItem"&&k(i)).map(b)),a=n.nodes.findIndex(i=>i.type==="MenuItem"&&b(i)===b(o)),r=a===-1?[]:p(n,a),s=n.nodes.map((i,u)=>i.type==="MenuItem"&&k(i)&&!t.has(b(i))?u:-1).filter(i=>i!==-1),c=new Set;return[...r,...s].filter(i=>{const u=b(n.nodes[i]);return!c.has(u)&&!!c.add(u)})}async function y(e,n){n>0&&await e.call("key",{chord:"escape",count:n+1}).catch(()=>{})}async function N(e,n,o){const t=[],a=[];for(const r of p(n,o)){const s=n.nodes[r];t.push({path:[s.name],enabled:!s.disabled,submenu:!0});let c=p(n,r).map(i=>n.nodes[i]);if(!c.length&&e.canOpen&&!s.disabled){await j(e,s);const i=await w(e);c=B(n,i,s).map(u=>i.nodes[u]),await y(e,1)}else c.length||a.push(d(s.name));for(const i of c)t.push({path:[s.name,i.name],enabled:!i.disabled,submenu:v(i),...i.toggle==="on"?{checked:!0}:{}})}return g(t,"the window's menu bar",a)}async function W(e,n,o,t){if(t.length===1&&!p(n,o).some(s=>f(n.nodes[s].name,t[0]))){const s=await N(e,n,o),c=L(s.note??"").map(u=>({path:u.replace(/ ›$| \((checked|disabled)\)/g,"").split(" \u203A "),enabled:!0,submenu:!1})),i=I(c,t);if(typeof i=="string")return{ok:!1,note:i};t=i.path,n=await w(e)}let a=p(n,o).map(s=>n.nodes[s]),r=0;for(const[s,c]of t.entries()){const i=a.find(l=>f(l.name,c)&&k(l))??a.find(l=>f(l.name,c)),u=t.slice(0,s+1).join(" \u203A ");if(!i){await y(e,r);const l=a.map(h=>d(h.name)).filter(Boolean);return{ok:!1,note:`no "${c}" in ${s?t.slice(0,s).join(" \u203A "):"the menu bar"} (it has: ${l.join(", ")||"nothing readable"}). Nothing was run`}}if(i.disabled)return await y(e,r),{ok:!1,note:`${u} is disabled right now \u2014 the app does not offer it for what is open or selected. Nothing was run`};const M=s===t.length-1;if(M&&!v(i)&&s>0){const l=await S(e,i);return{ok:!0,journal:{via:l.via},note:l.pending?`ran ${u}; the app is still handling it \u2014 typically the dialog it opened. Observe to see it; do not run it again`:`ran ${u} (${l.via==="mouse"?"clicked":`pressed through UI Automation (${l.via})`}) \u2014 observe for its effect`}}if(!e.canOpen)return{ok:!1,note:"running a menu command opens the menu and moves the window's focus, so it needs interaction:'desktop'; explain that and obtain permission first. Nothing was run"};const U=await w(e);await j(e,i),r++;const x=await w(e);if(a=B(U,x,i).map(l=>x.nodes[l]),M){await y(e,r);const l=a.map(h=>({path:[...t,h.name],enabled:!h.disabled,submenu:v(h)}));return g(l,`items of ${t.map(d).join(" \u203A ")}`,[])}}return{ok:!1,note:"menu needs a path"}}async function R(e,n){const o=typeof n=="string"?n.trim():"",t=o?o.split(/\s*(?:›|>|\/)\s*/).filter(Boolean):[],a=await O(e);if(a.length)return t.length?e.canOpen?_(e,a,t):{ok:!1,note:"running a menu command acts on the app, so it needs interaction:'desktop'; explain that and obtain permission first. Nothing was run"}:g(a.filter(c=>!c.submenu||c.path.length>1),"the window's classic menu bar, state refreshed the way opening it would",[]);const r=await w(e),s=T(r);return s===-1?{ok:!1,note:"this window has no menu bar UI Automation can see \u2014 its commands are buttons, a ribbon or a toolbar: observe and click them"}:t.length?W(e,r,s,t):N(e,r,s)}export{R as menuVerb};
@@ -0,0 +1,2 @@
1
+ export declare const WINDOWS_INTERACTION_NOTES: readonly string[];
2
+ export declare function promptFragmentFor(): string;
@@ -0,0 +1 @@
1
+ const e='Every app \u2014 reading it included \u2014 must be cleared for this session. One not cleared yet is listed by its program name only ("WINWORD \u2014 not cleared in this session yet\u2026"); bind it by that name, and the bind clears it: at once when the user always allows it, otherwise by asking them (pass `purpose` to say why). If they decline, do not retry or ask again in this task.',t="Windows defaults to limited background UIA: supported setText, click, uiaAction and scroll by ref do not activate the window or inject mouse/keyboard input, and never fall back to those routes. Apps may still react by opening windows or changing focus; detected interference stops background actions on that window. Do not automatically replay uncertain actions. They are refused while the target window is in front, where the user may be typing: they would move focus inside it. Coordinates, type, keys, hover, activation and unsupported controls need interaction:'desktop', which brings the window to the FRONT and can use the real pointer/keyboard. Explain those effects and obtain permission before choosing desktop. Reading never activates the window; app-access approval is required in both modes.",a=[t,e,"Background setText requires a readable, writable ValuePattern on an editor in a standard Win32/WinForms/WPF window (including Notepad), a Chromium page (Chrome, Edge), an Electron app or a XAML app; password fields and unsupported editors are refused before writing. It never focuses, selects all or types as a fallback.","In a Chromium page, an Electron app (VS Code\u2026) or a XAML/DirectUI app (Settings, Calculator, Explorer), background click does what a desktop click does through UIA: it selects an item, presses a button or link, toggles a checkbox, or expands/collapses; uiaAction({ref, action}) runs a named one. Elsewhere it only selects standard list items and toggles WinForms/WPF checkboxes; other Win32/WinForms/WPF buttons, menus and expansion are excluded. Those apps must be unminimized (they may stay covered). A press is dispatched, not verified: observe its result. Missing patterns are refusals, not a reason to switch modes automatically.","Background scroll needs a specific ScrollPattern container ref from observe/find; window-wide scrolling has no background fallback. count is 1\u201320 UIA large increments; to:'top'/'bottom' uses the scroll percentage.","In desktop mode, click by ref does what a single click does: it SELECTS a list/tree/table item (open one with count:2), picks an entry of an open combo box list, and presses a control through its UIA action \u2014 WinForms buttons and menu items with the real mouse, since their handlers run inside UIA and a dialog they open would freeze it. A press that opens a dialog reports the app as still busy; observe and answer the dialog, never press again. Right/middle/double clicks and point clicks use the real mouse. A tree node [collapsed] is expanded with uiaAction({ref, action:'expand'}), not by clicking it. interaction:desktop is not proof of consent; obtain it first.","Open menus, dropdown lists and context menus are read with the window: their items are listed last.","An app with no window open is started by binding its Start-menu name (computer.surface('Calculator')) or, from a bound window, launchApp({app}); listApps({query}) gives the names, which are in the system language. Starting one opens its window in front of the user, so both need interaction:'desktop' and the user's permission; the app itself is cleared like any bind.","A menu-bar command runs by its path: menu({path:'File \u203A Save As'}) \u2014 names in the system language, so list them first with menu({}); the last segment alone works when unique, and a path ending at a submenu lists it. A classic Win32 menu bar runs the command directly; others are opened level by level. Prefer it to clicking through menus yourself.","A datetime element (a Win32/WinForms date or time picker) shows its text and exact date; setText(ref, '2026-03-09'), '14:30' or '2026-03-09 14:30' types it field by field as a person would (the app sees each step) and reads it back. A calendar element is read-only here: pick a day by clicking it in a screenshot.","To change part of a field, selectText({ref, text, near}) selects that text (read back from the field) and focuses it; type, keys or paste then replace just that. caret:'before'/'after' puts the caret there instead; caret alone goes to the start/end. paste({text, html, ref}) uses Ctrl+V through the user's clipboard, which is put back afterwards and kept out of clipboard history: tell the user, and pass ref so a paste into the wrong field is refused before the clipboard is touched.","drag({ref or x/y, toRef or toX/toY}) holds the real left button along a path and releases it: files onto folders, slider thumbs, strokes on a drawing canvas (which often has no ref \u2014 take a screenshot and drag by coordinates inside it).","If the user is typing in another window, Windows can refuse to bring the target forward; the action then reports that and sends nothing. While a Windows security prompt is in front, nothing can come forward until the user answers it: tell them, do not retry.","The user comes first. A desktop action waits up to 1.5 s for them to pause the keyboard and pointer for 2 s, and otherwise is refused with nothing sent \u2014 wait and retry. If they switched to another app since your last action, they have taken over: actions are refused until you tell them what is left and, once they agree, take the window back with kind:'activate'. A type or keys call stops part-way when they touch the keyboard or mouse; observe what landed instead of resending.","setText waits for the requested value. type captures the focused editor and its selection before input, then waits for that same editor to match the expected edit when readable. A mismatch stops subsequent actions; unavailable read-back is explicitly unverified. Do not replay uncertain input. Field comparisons normalize CRLF/LF; real trailing newlines are retained.","keys accepts Multiply, Add, Subtract and Divide (also NumpadMultiply, NumpadAdd, NumpadSubtract and NumpadDivide), case-insensitively. These send the native keypad key, without depending on Shift+8 or a keyboard layout.","Decimal and NumpadDecimal enter the system's decimal separator as text, without changing NumLock. They do not accept modifiers. NumLock itself is available only as an explicit keypress; Decimal never toggles it."];function s(){return t+" Grounding: observations are text lists of one Windows application window's controls with refs, read from its UI Automation tree (the same tree screen readers use). Act by ref from the latest observation. set_text uses a field's UI Automation value; focusing/selecting/typing fallback exists only in desktop mode. A control you can SEE and cannot name (self-drawn, no node in the tree) is reachable by position: take w.observe({screenshot: true}) and w.click({x, y}) with coordinates read off that picture. Chromium/Electron apps publish their tree a moment after the first read, so a near-empty first observation of one is worth repeating once. Windows are surfaces: computer.surfaces() lists every visible application window by title and program name \u2014 "+e+" Use text() to read all text in the window."}export{a as WINDOWS_INTERACTION_NOTES,s as promptFragmentFor};
@@ -0,0 +1,10 @@
1
+ import type { ActResult } from "../types.ts";
2
+ import type { RefTarget } from "./elements.ts";
3
+ export interface TextHost {
4
+ target(ref: unknown): RefTarget;
5
+ call<T>(op: string, params?: Record<string, unknown>): Promise<T>;
6
+ hwnd(): number;
7
+ }
8
+ export declare function selectText(host: TextHost, a: Record<string, any>): Promise<ActResult>;
9
+ export declare function setDate(host: TextHost, t: RefTarget, text: string): Promise<ActResult>;
10
+ export declare function paste(host: TextHost, a: Record<string, any>): Promise<ActResult>;
@@ -0,0 +1,5 @@
1
+ import{parseDateTime as _}from"./dates.js";import{UiaRequestError as d}from"./uia-client.js";const S=2500;function M(n){let o="";const e=[];for(let t=0;t<n.length;t++)e.push(t),n[t]==="\r"?(o+=`
2
+ `,n[t+1]===`
3
+ `&&t++):o+=n[t];return e.push(n.length),{lf:o,at:e}}function T(n,o,e,t){return Math.max(0,e-(n+o),n-(e+t))}const l=n=>`${n.type.toLowerCase()} "${n.name}"`;async function N(n,o){const e=n.target(o.ref),t=String(o.caret??"select");if(!["select","before","after"].includes(t))return{ok:!1,note:"caret is select, before or after"};if(!(e.type==="Edit"||e.type==="Document"||e.actions.includes("text")))return{ok:!1,note:`${l(e)} is not a text field \u2014 select_text works on text fields and documents; click a control instead`};let a;try{a=String((await n.call("value",{key:e.key})).value??"")}catch(r){if(r instanceof d)return{ok:!1,note:`${l(e)} cannot be read (${r.message}), so nothing in it can be selected`};throw r}const{lf:s,at:w}=M(a),i=String(o.text??"").replace(/\r\n?/g,`
4
+ `);let h,k,y="";if(i){const r=[];for(let c=s.indexOf(i);c!==-1;c=s.indexOf(i,c+1))r.push(c);if(!r.length)return{ok:!1,note:`"${i}" is not in ${l(e)} \u2014 read it with text() and copy it exactly, whitespace included`};let u=r[0],$=0;const f=String(o.near??"").replace(/\r\n?/g,`
5
+ `);if(f){const c=s.indexOf(f);if(c===-1)return{ok:!1,note:`"${f}" is not in ${l(e)}, so it cannot pick between the ${r.length} matches for "${i}" \u2014 drop near, or give text that really sits beside the one you mean`};let m=1/0;for(const[v,x]of r.entries()){const b=T(x,i.length,c,f.length);b<m&&(m=b,u=x,$=v)}}r.length>1&&(y=` (match ${$+1} of ${r.length}${f?`, the one nearest "${f}"`:", the first"})`),h=t==="after"?u+i.length:u,k=t==="select"?u+i.length:h}else{if(t==="select")return{ok:!1,note:"select_text needs the text to select, or caret:'before' / caret:'after' alone to put the caret at the start / end of the field"};h=k=t==="after"?s.length:0}const p=w[h],g=w[k];try{await n.call("selectrange",{key:e.key,start:p,length:g-p,want:a.slice(p,g),prefix:a.slice(0,p)})}catch(r){if(r instanceof d&&(r.code==="no_selection"||r.code==="stale_key"))return{ok:!1,note:`${r.message}${r.code==="no_selection"?" \u2014 setText replaces the whole field instead":""}`};if(r instanceof d&&r.code==="unknown_op")return{ok:!1,note:"this Windows native helper predates select_text; update @mirasim/computer-use, or the app that bundles it"};throw r}return i?t==="select"?{ok:!0,note:`selected "${i}" in ${l(e)}${y}, read back from the field \u2014 keys, type or paste now replace exactly this (setText still replaces the whole field)`}:{ok:!0,note:`caret placed ${t} "${i}" in ${l(e)}${y} \u2014 keys, type or paste now insert there`}:{ok:!0,note:`caret at the ${t==="after"?"end":"start"} of ${l(e)}, which has the keyboard focus \u2014 keys and type go there now`}}async function O(n,o,e){const t=_(e);if(!t)return{ok:!1,note:`"${e}" is not a date or time this picker can take \u2014 give the date as 2026-03-09, the time as 14:30, or both ("2026-03-09 14:30"). Nothing was typed`};let a;try{a=await n.call("setdate",{key:o.key,want:t})}catch(s){if(!(s instanceof d))throw s;return s.code==="unknown_op"?{ok:!1,note:"this Windows native helper predates date pickers; update the full desktop app. Nothing was typed"}:{ok:!1,mayHaveActed:s.code!=="date_unreadable",note:s.message}}return a.verified?{ok:!0,verified:!0,note:`${l(o)} now reads ${a.shown} (${a.iso}) \u2014 typed field by field the way a person would, so the app saw each step`}:{ok:!0,verified:!1,note:`${l(o)} reads ${a.shown} (${a.iso}) after typing, not what was asked \u2014 do not retype blindly; observe it`}}async function W(n,o){const e=String(o.text??"");if(!e)return{ok:!1,note:"paste needs text (and optionally html for apps that take rich text)"};const t=typeof o.ref=="string"?n.target(o.ref).key:void 0;let a;try{a=await n.call("paste",{hwnd:n.hwnd(),text:e,...typeof o.html=="string"&&o.html?{html:o.html}:{},...t?{key:t}:{},verifyMs:S})}catch(s){if(!(s instanceof d))throw s;if(["wrong_focus","clipboard_busy","clipboard_large","stale_key"].includes(s.code))return{ok:!1,note:s.message};if(s.code==="unknown_op")return{ok:!1,note:"this Windows native helper predates paste; update @mirasim/computer-use, or the app that bundles it. Nothing was put on the clipboard"};throw s}return{ok:!0,note:a.note,...typeof a.verified=="boolean"?{verified:a.verified}:{},journal:{restored:a.restored}}}export{W as paste,N as selectText,O as setDate};
@@ -0,0 +1,32 @@
1
+ export interface UiaTransport {
2
+ onReset?: () => void;
3
+ request(op: string, params?: Record<string, unknown>): Promise<unknown>;
4
+ close(): Promise<void>;
5
+ }
6
+ export declare class UiaRequestError extends Error {
7
+ readonly code: string;
8
+ constructor(code: string, message: string);
9
+ }
10
+ export interface UiaHelperOptions {
11
+ env?: NodeJS.ProcessEnv;
12
+ onStart?: (pid: number) => Promise<void>;
13
+ onLog?: (line: string) => void;
14
+ timeoutMs?: number;
15
+ }
16
+ export declare class UiaHelperProcess implements UiaTransport {
17
+ private readonly opts;
18
+ onReset?: () => void;
19
+ private child;
20
+ private starting;
21
+ private readonly pending;
22
+ private nextId;
23
+ private stderrTail;
24
+ private readonly env;
25
+ private readonly timeoutMs;
26
+ constructor(opts?: UiaHelperOptions);
27
+ request(op: string, params?: Record<string, unknown>): Promise<unknown>;
28
+ close(): Promise<void>;
29
+ private start;
30
+ private onLine;
31
+ private fail;
32
+ }
@@ -0,0 +1,3 @@
1
+ import{spawn as _,execFile as y}from"node:child_process";import{createHash as E}from"node:crypto";import{existsSync as c,mkdirSync as S,readdirSync as m,readFileSync as f,rmSync as b,statSync as h}from"node:fs";import{homedir as R}from"node:os";import{join as o}from"node:path";import{fileURLToPath as w}from"node:url";import{createInterface as x}from"node:readline";import{computerUseHome as T}from"../home.js";import{platformPackageHelper as $}from"../platform-package.js";class l extends Error{code;constructor(i,t){super(t),this.code=i,this.name="UiaRequestError"}}class j{opts;onReset;child=null;starting=null;pending=new Map;nextId=1;stderrTail="";env;timeoutMs;constructor(i={}){this.opts=i,this.env=i.env??process.env,this.timeoutMs=i.timeoutMs??2e4}async request(i,t={}){const e=await this.start(),s=this.nextId++;return new Promise((a,n)=>{const d=setTimeout(()=>{this.pending.delete(s),this.close().then(()=>n(new l("timeout",`uia helper: '${i}' did not answer within ${this.timeoutMs} ms; helper stopped`)))},this.timeoutMs);this.pending.set(s,{resolve:a,reject:n,timer:d}),e.stdin.write(`${JSON.stringify({id:s,op:i,...t})}
2
+ `)})}async close(){const i=this.child;this.fail("uia helper closed; any in-flight action's outcome is unknown \u2014 observe before acting again"),!(!i||i.exitCode!==null)&&await new Promise(t=>{const e=setTimeout(()=>i.kill(),2e3);i.once("exit",()=>{clearTimeout(e),t()}),i.stdin.end()})}start(){return this.child?Promise.resolve(this.child):(this.starting??=(async()=>{const i=await P(this.env),t=_(i,[],{stdio:["pipe","pipe","pipe"],env:this.env,windowsHide:!0});return this.stderrTail="",t.stdin.on("error",()=>{}),t.stderr.on("data",e=>{const s=e.toString();this.stderrTail=`${this.stderrTail}${s}`.slice(-2e3);for(const a of s.split(`
3
+ `)){const n=a.replace(/^computer-use-uia-helper:\s*/,"").trim();n&&this.opts.onLog?.(n)}}),x({input:t.stdout}).on("line",e=>{this.child===t&&this.onLine(e)}),t.on("exit",e=>{this.child===t&&this.fail(`uia helper exited (${e??"?"})${this.stderrTail?`: ${this.stderrTail.trim()}`:""}`)}),t.on("error",e=>{this.child===t&&this.fail(`uia helper could not start: ${e.message}`)}),this.child=t,t.pid&&await this.opts.onStart?.(t.pid),t})().finally(()=>{this.starting=null}),this.starting)}onLine(i){let t;try{t=JSON.parse(i)}catch{return}const e=typeof t.id=="number"?this.pending.get(t.id):void 0;e&&(this.pending.delete(t.id),clearTimeout(e.timer),t.ok?e.resolve(t.result??{}):e.reject(new l(t.error?.code??"uia_error",t.error?.message??"uia helper error")))}fail(i){this.child=null;for(const[t,e]of this.pending)clearTimeout(e.timer),e.reject(new l("helper_gone",i)),this.pending.delete(t);this.onReset?.()}}const u="computer-use-uia-helper.exe";function v(){const r=w(new URL("./uia/",import.meta.url));return c(o(r,"Cargo.toml"))?r:null}function I(){const r=w(new URL(`./${u}`,import.meta.url));return c(r)?r:null}function M(r){const i=E("sha256");for(const e of["Cargo.toml","Cargo.lock"])c(o(r,e))&&i.update(f(o(r,e)));const t=o(r,"src");for(const e of m(t).filter(s=>s.endsWith(".rs")).sort())i.update(e).update(f(o(t,e)));return i.digest("hex").slice(0,16)}async function P(r){const i=r.COMPUTER_USE_WINDOWS_UIA_BIN?.trim();if(i){if(!c(i))throw new Error(`COMPUTER_USE_WINDOWS_UIA_BIN points at ${i}, which does not exist`);return i}if(process.platform!=="win32")throw new l("unsupported","the windows driver needs Windows");const t=I()??$(u);if(t)return t;const e=v();if(!e)throw new Error(`windows uia helper: no prebuilt binary (COMPUTER_USE_WINDOWS_UIA_BIN, \`${u}\` beside the bundle, or the @mirasim/computer-use-${process.platform}-${process.arch} package) and no Rust sources beside the bundle \u2014 a packaged build must ship the helper`);const s=o(T(),"windows-uia"),a=o(s,M(e)),n=o(a,"release",u);return c(n)||(S(a,{recursive:!0}),await new Promise((d,g)=>{y(H(r),["build","--release","--manifest-path",o(e,"Cargo.toml"),"--target-dir",a],{timeout:6e5,env:r,windowsHide:!0},(p,O,U)=>{p?g(new Error(`windows uia helper: cargo build failed (is the Rust toolchain installed? winget install Rustlang.Rustup): ${String(U||p.message).slice(-800)}`)):d()})}),N(s,a)),n}function H(r){const i=o(r.USERPROFILE??R(),".cargo","bin","cargo.exe");return c(i)?i:"cargo"}function N(r,i){try{const t=m(r).map(e=>o(r,e)).filter(e=>e!==i&&h(e).isDirectory()).sort((e,s)=>h(s).mtimeMs-h(e).mtimeMs);for(const e of t.slice(3))b(e,{recursive:!0,force:!0})}catch{}}export{j as UiaHelperProcess,l as UiaRequestError};
@@ -0,0 +1,2 @@
1
+ import type { VerbSpec } from "../types.ts";
2
+ export declare const WINDOWS_VERBS: VerbSpec[];
@@ -0,0 +1 @@
1
+ import{Type as e}from"typebox";const n=[{kind:"activate",schema:e.Object({kind:e.Literal("activate")},{description:"Bring the bound window to the front (restoring it if minimized) and give it the user's keyboard; explain this and obtain permission first. Also how to take the window back after the user has taken over."})},{kind:"type",schema:e.Object({kind:e.Literal("type"),text:e.String({description:"Text to type with real keystrokes into whatever holds the window's keyboard focus"})},{description:"Type text as real key events into the focused control. When its original text and selection are readable, wait for the expected result in that same field; a mismatch stops the call without retyping. Brings the window to the front."})},{kind:"uia_action",schema:e.Object({kind:e.Literal("uia_action"),ref:e.String({description:"Element ref from the latest observe() (e.g. e12)"}),action:e.Union(["expand","collapse","select","toggle","invoke"].map(t=>e.Literal(t)),{description:"expand/collapse a tree node, combo box or menu; select an item; toggle a checkbox; invoke (an item's invoke OPENS it, like a double-click)"})},{description:"Run one UI Automation action the control itself offers, exactly as named \u2014 for what a click does not do: a click SELECTS a tree node ([collapsed]), expand shows its children. Refused when the control does not offer it. Desktop mode brings the window to the front; background mode runs it without doing so on Chromium, Electron and XAML controls."})},{kind:"list_apps",readOnly:!0,schema:e.Object({kind:e.Literal("list_apps"),query:e.Optional(e.String({description:"Only entries whose name or id contains this (any case), e.g. 'notepad' or 'paint'"}))},{description:"The apps launchApp can start: the Start menu's entries as {name, id} \u2014 names are in the system language (Calculatrice on a French Windows), so look them up here rather than guessing. A read; starts nothing."})},{kind:"launch_app",schema:e.Object({kind:e.Literal("launch_app"),app:e.String({description:"A Start-menu name or id from listApps() ('Notepad', 'Microsoft.WindowsCalculator_8wekyb3d8bbwe!App'), or a program name the Run box takes ('notepad', 'mspaint')"})},{description:"Start an app and bind its new window as this session's target (an app not cleared yet asks the user, like binding it). Opens a window in front of the user: explain and obtain permission first (interaction:'desktop'). A single-instance app may answer by bringing its existing window forward \u2014 or, like Windows 11 Notepad, open a tab in the user's own window; that window is then the target."})},{kind:"menu",schema:e.Object({kind:e.Literal("menu"),path:e.Optional(e.String({description:"Menu command to run, as the listing shows it \u2014 names are in the system language (Fichier \u203A Enregistrer sous on a French Windows), so list first (menu({})) rather than guessing. \u203A > / all separate; the last segment alone also works when unique; a path ending at a submenu lists its items. Omit to list the window's menu commands."}))},{description:"List the window's menu-bar commands, or run one by its path. A classic Win32 menu bar runs the command directly, opening nothing; other menu bars are opened level by level and the item pressed, the way a person does. Running one brings the window to the front: explain and obtain permission first. Listing is a read; in background mode it opens no menu."})},{kind:"select_text",schema:e.Object({kind:e.Literal("select_text"),ref:e.String({description:"The text field or document to select inside (from the latest observe())"}),text:e.Optional(e.String({description:"The exact text to select (copy it verbatim from what you read); omit it to put the caret at the start / end of the field with caret alone"})),near:e.Optional(e.String({description:"Nearby text that tells one occurrence from another; the match closest to it wins"})),caret:e.Optional(e.Union([e.Literal("select"),e.Literal("before"),e.Literal("after")],{description:"select (default) leaves it selected \u2014 type, keys or paste then replace it; before / after put the caret there instead"}))},{description:"Select part of a field's text, or put the caret in it, and give the field the keyboard focus \u2014 so type, keys or paste edit exactly that; set_text always replaces the whole field. The selection is read back from the field. Brings the window to the front."})},{kind:"paste",schema:e.Object({kind:e.Literal("paste"),text:e.String({description:"The content as plain text"}),html:e.Optional(e.String({description:"The same content as an HTML fragment, for apps that take rich text (Word, Outlook, web editors)"})),ref:e.Optional(e.String({description:"The element you EXPECT to have the keyboard focus. Not a destination \u2014 the paste goes to the focus \u2014 but a mismatch is refused before the clipboard is touched. Pass it whenever you know where the text should go."}))},{description:"Paste with Ctrl+V at the window's keyboard focus: brings the window to the front, puts the content on the user's clipboard (kept out of clipboard history and cloud sync) and puts their clipboard back afterwards, unless they copied something meanwhile. Explain these effects and obtain permission first. For long text and rich text; it is read back from the focused field when it can be."})},{kind:"drag",schema:e.Object({kind:e.Literal("drag"),ref:e.Optional(e.String({description:"Element to drag \u2014 REQUIRED unless x/y are given"})),x:e.Optional(e.Number({description:"Drag start x in the latest screenshot (instead of ref)"})),y:e.Optional(e.Number({description:"Drag start y"})),toRef:e.Optional(e.String({description:"Element to drop onto \u2014 REQUIRED unless toX/toY are given"})),toX:e.Optional(e.Number({description:"Destination x in the latest screenshot (instead of toRef)"})),toY:e.Optional(e.Number({description:"Destination y"})),steps:e.Optional(e.Number({description:"Motion events along the path (default 20, 2\u201360); more is slower and smoother, for apps that sample the pointer per frame"}))},{description:"Drag with the user's real pointer: press the left button at the start, move to the destination, release \u2014 a file onto a folder, a slider thumb, a line in a drawing app. Brings the window to the front and moves the pointer; explain and obtain permission first. Needs a start (ref or x/y) AND a destination (toRef or toX/toY). Stops and releases if the user touches the mouse."})}];export{n as WINDOWS_VERBS};
@@ -0,0 +1,280 @@
1
+ /**
2
+ * Service worker: owns the single active bridge connection.
3
+ *
4
+ * ONE way in: **native messaging**. `chrome.runtime.connectNative` makes Chrome spawn mirasim's
5
+ * host, which pumps into the running bridge. No address is handed over, so nothing expires —
6
+ * after a browser restart, a mirasim update or a worker eviction this worker just dials again.
7
+ * An open port also keeps the worker alive.
8
+ *
9
+ * The **connect page** (`bridge.connect` below, over a one-shot `ws://127.0.0.1/…` relay) is NOT
10
+ * a product path: its relay uuid is minted per bridge process, so every restart would need a
11
+ * human click. Mirasim never opens it; it is the seam end-to-end tests use in a throwaway
12
+ * Chromium with no native-host manifest.
13
+ *
14
+ * One bridge at a time: a new connection replaces the old.
15
+ *
16
+ * Reconnect policy: probe with ping/pong, tell "host not installed" from "host went away" by the
17
+ * lastError text and stop fast retries on the former, back off 1s→30s on the latter, and arm a
18
+ * `chrome.alarms` alongside the timer because an evicted worker takes its timers with it.
19
+ */
20
+
21
+ import { RelayConnection, debugLog } from "./relay.js";
22
+ import { MirasimTabGroup } from "./group.js";
23
+ import { nativeTransport, webSocketTransport } from "./transport.js";
24
+
25
+ const CONNECT_TIMEOUT_MS = 10_000;
26
+ /** How long the native probe waits for `pong` before calling the route dead. */
27
+ const PROBE_TIMEOUT_MS = 10_000;
28
+ const BACKOFF_FIRST_MS = 1_000;
29
+ const BACKOFF_MAX_MS = 30_000;
30
+ const RECONNECT_ALARM = "mirasim-bridge-reconnect";
31
+ /** How often to look again once every candidate said "not installed". Never NEVER: a user who
32
+ * installs mirasim while Chrome is open would wait for a browser restart. Not slow either: this
33
+ * recheck IS the time-to-first-connection after loading the extension, and one wake a minute
34
+ * is what the heartbeat already costs. */
35
+ const ABSENT_RECHECK_MINUTES = 1;
36
+ /**
37
+ * The heartbeat, armed even while CONNECTED. A closed native port lets the worker terminate
38
+ * (Chrome docs), so when the bridge's host exits the worker can die with it: no `onDisconnect`
39
+ * runs and no timer survives. Only this alarm wakes it to reconnect; the handler returns early
40
+ * while a connection exists.
41
+ */
42
+ const HEARTBEAT_MINUTES = 1;
43
+
44
+ /**
45
+ * Host names, in the order they are tried. The installed app owns the first; a dev instance
46
+ * writes the second, so the two can coexist on one machine without fighting over one manifest
47
+ * (the shape OpenAI's extension uses for its own `.dev` host).
48
+ */
49
+ const HOST_CANDIDATES = ["ai.mirasim.browser_bridge", "ai.mirasim.browser_bridge.dev"];
50
+
51
+ /** Chrome's way of saying the manifest is missing or policy forbids it — as opposed to a host
52
+ * that started and then went away. The first means retrying forever is pointless. */
53
+ const ABSENT_RE = /native messaging host not found|native messaging host is forbidden|not found|forbidden/i;
54
+
55
+ class BridgeExtension {
56
+ constructor() {
57
+ this._activeGroup = null;
58
+ /** Transport of the active connection, for the status reply. */
59
+ this._transport = null;
60
+ /** Set once every candidate answered "not installed" — stops the retry loop until
61
+ * something changes (a new install, or the user's explicit connect). */
62
+ this._hostAbsent = false;
63
+ this._connecting = false;
64
+ this._attempt = 0;
65
+ this._timer = null;
66
+ this._lastError = null;
67
+ this._updateVersion = null;
68
+ chrome.runtime.onUpdateAvailable.addListener(({ version }) => {
69
+ this._updateVersion = version;
70
+ if (this._activeGroup) this._activeGroup.updateAvailable(version);
71
+ else if (!this._connecting) chrome.runtime.reload();
72
+ });
73
+
74
+ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
75
+ // Promise-based responses are not supported by Chrome; return true when async.
76
+ switch (message?.type) {
77
+ case "bridge.connect":
78
+ this._connectWs(message.relayUrl, sender).then(
79
+ () => sendResponse({ success: true }),
80
+ (e) => sendResponse({ success: false, error: e?.message ?? String(e) }),
81
+ );
82
+ return true;
83
+ case "bridge.status":
84
+ sendResponse({
85
+ connected: !!this._activeGroup,
86
+ transport: this._transport,
87
+ hostAbsent: this._hostAbsent,
88
+ reconnectAttempt: this._attempt,
89
+ error: this._lastError,
90
+ tabIds: this._activeGroup?.connectedTabIds() ?? [],
91
+ });
92
+ return false;
93
+ default:
94
+ return false;
95
+ }
96
+ });
97
+
98
+ // An alarm survives worker eviction; the timer does not. Both land here.
99
+ chrome.alarms.onAlarm.addListener((alarm) => {
100
+ if (alarm.name !== RECONNECT_ALARM) return;
101
+ if (this._activeGroup) return; // connected: the heartbeat has nothing to do
102
+ // Drop the absent flag first, or `_connectNative` returns at once and never rechecks.
103
+ this._hostAbsent = false;
104
+ void this._connectNative();
105
+ });
106
+ chrome.runtime.onStartup.addListener(() => void this._connectNative());
107
+ chrome.runtime.onInstalled.addListener(() => void this._connectNative());
108
+ // Worker start IS the reconnect trigger: eviction plus any event wakes us right back up.
109
+ void this._connectNative();
110
+ }
111
+
112
+ // ─── native messaging ────────────────────────────────────────────────────
113
+
114
+ /** Try each host in turn; keep the first that answers `pong`. */
115
+ async _connectNative() {
116
+ if (this._activeGroup || this._connecting || this._hostAbsent) return;
117
+ this._connecting = true;
118
+ let everyCandidateAbsent = true;
119
+ try {
120
+ for (const name of HOST_CANDIDATES) {
121
+ const port = await this._probe(name);
122
+ if (port === "absent") continue;
123
+ everyCandidateAbsent = false;
124
+ if (!port) continue;
125
+ await this._adopt(new RelayConnection(nativeTransport(port)), "native");
126
+ debugLog("bridge connected over native messaging:", name);
127
+ return;
128
+ }
129
+ } catch (e) {
130
+ this._lastError = e?.message ?? String(e);
131
+ debugLog("native connect failed:", e);
132
+ } finally {
133
+ this._connecting = false;
134
+ }
135
+ if (everyCandidateAbsent) {
136
+ // No manifest anywhere, or policy says no: stop the fast retries but keep a slow alarm,
137
+ // the only thing that survives worker eviction to notice an install.
138
+ this._hostAbsent = true;
139
+ if (this._timer) clearTimeout(this._timer);
140
+ this._timer = null;
141
+ this._armAlarm(ABSENT_RECHECK_MINUTES);
142
+ debugLog(`no native host installed; rechecking every ${ABSENT_RECHECK_MINUTES}min, fallback path meanwhile`);
143
+ return;
144
+ }
145
+ this._scheduleRetry();
146
+ }
147
+
148
+ /**
149
+ * Connect one candidate and ask it whether the whole route is alive.
150
+ *
151
+ * The host answers `pong` only once it has the bridge's socket, so a pong means mirasim is
152
+ * running and reachable — not merely that a binary exists. Returns the port on success,
153
+ * `"absent"` when Chrome says the host is not installed/forbidden, else null.
154
+ */
155
+ _probe(name) {
156
+ return new Promise((resolve) => {
157
+ let port;
158
+ try {
159
+ port = chrome.runtime.connectNative(name);
160
+ } catch (e) {
161
+ resolve(ABSENT_RE.test(e?.message ?? "") ? "absent" : null);
162
+ return;
163
+ }
164
+ let settled = false;
165
+ const finish = (value) => {
166
+ if (settled) return;
167
+ settled = true;
168
+ clearTimeout(timer);
169
+ port.onDisconnect.removeListener(onDisconnect);
170
+ port.onMessage.removeListener(onMessage);
171
+ if (value !== port) {
172
+ try {
173
+ port.disconnect();
174
+ } catch {
175
+ /* already gone */
176
+ }
177
+ }
178
+ resolve(value);
179
+ };
180
+ const onDisconnect = () => {
181
+ const message = chrome.runtime.lastError?.message ?? "";
182
+ this._lastError = message || "native host disconnected";
183
+ finish(ABSENT_RE.test(message) ? "absent" : null);
184
+ };
185
+ const onMessage = (msg) => {
186
+ if (msg?.type === "pong") finish(port);
187
+ };
188
+ port.onDisconnect.addListener(onDisconnect);
189
+ port.onMessage.addListener(onMessage);
190
+ const timer = setTimeout(() => finish(null), PROBE_TIMEOUT_MS);
191
+ try {
192
+ port.postMessage({ type: "ping" });
193
+ } catch {
194
+ finish(null);
195
+ }
196
+ });
197
+ }
198
+
199
+ _scheduleRetry() {
200
+ this._attempt += 1;
201
+ const delay = Math.min(BACKOFF_FIRST_MS * 2 ** (this._attempt - 1), BACKOFF_MAX_MS);
202
+ this._clearRetry();
203
+ this._timer = setTimeout(() => void this._connectNative(), delay);
204
+ // Alarms are minute-grained, so this is the floor, not the schedule: the timer wins while
205
+ // the worker lives and the alarm is what picks up after an eviction.
206
+ this._armAlarm(HEARTBEAT_MINUTES);
207
+ debugLog(`native reconnect in ${delay}ms (attempt ${this._attempt})`);
208
+ }
209
+
210
+ /** Idempotent enough for our purposes: creating an existing alarm replaces it. */
211
+ _armAlarm(minutes) {
212
+ chrome.alarms.create(RECONNECT_ALARM, { delayInMinutes: minutes, periodInMinutes: minutes });
213
+ }
214
+
215
+ /** Stop the FAST retries only. The alarm is never cleared here — see HEARTBEAT_MINUTES. */
216
+ _clearRetry() {
217
+ if (this._timer) clearTimeout(this._timer);
218
+ this._timer = null;
219
+ }
220
+
221
+ // ─── the connect page (TEST SEAM — see the file header) ──────────────────
222
+
223
+ async _connectWs(relayUrl, sender) {
224
+ if (typeof relayUrl !== "string" || !/^wss?:\/\/(127\.0\.0\.1|localhost|\[::1\])[:/]/.test(relayUrl))
225
+ throw new Error("Refusing non-local relay URL");
226
+ const ws = await this._openWebSocket(relayUrl);
227
+ await this._adopt(new RelayConnection(webSocketTransport(ws)), "ws");
228
+ debugLog("bridge connected:", relayUrl);
229
+ // A user who went through the page has told us the host is worth looking for again.
230
+ this._hostAbsent = false;
231
+
232
+ // The connect page did its one job; close it so nothing flashes longer than it must.
233
+ const senderTabId = sender?.tab?.id;
234
+ if (senderTabId !== undefined) await chrome.tabs.remove(senderTabId).catch(() => {});
235
+ }
236
+
237
+ _openWebSocket(relayUrl) {
238
+ return new Promise((resolve, reject) => {
239
+ const ws = new WebSocket(relayUrl);
240
+ const timer = setTimeout(() => {
241
+ ws.close();
242
+ reject(new Error("Timed out connecting to the mirasim bridge"));
243
+ }, CONNECT_TIMEOUT_MS);
244
+ ws.onopen = () => {
245
+ clearTimeout(timer);
246
+ resolve(ws);
247
+ };
248
+ ws.onerror = () => {
249
+ clearTimeout(timer);
250
+ reject(new Error("Could not reach the mirasim bridge (is mirasim running?)"));
251
+ };
252
+ });
253
+ }
254
+
255
+ // ─── whichever transport won ─────────────────────────────────────────────
256
+
257
+ async _adopt(connection, transport) {
258
+ this._activeGroup?.close("Replaced by a new bridge connection");
259
+ this._activeGroup = null;
260
+ const group = new MirasimTabGroup(connection);
261
+ group.onclose = () => {
262
+ if (this._activeGroup !== group) return;
263
+ this._activeGroup = null;
264
+ this._transport = null;
265
+ // Native reconnects on its own; the ws path needs a new hand-over.
266
+ if (transport === "native") this._scheduleRetry();
267
+ };
268
+ this._activeGroup = group;
269
+ this._transport = transport;
270
+ this._attempt = 0;
271
+ this._lastError = null;
272
+ this._clearRetry();
273
+ // Armed WHILE connected, deliberately — see HEARTBEAT_MINUTES.
274
+ this._armAlarm(HEARTBEAT_MINUTES);
275
+ await group.ready;
276
+ if (this._updateVersion) group.updateAvailable(this._updateVersion);
277
+ }
278
+ }
279
+
280
+ new BridgeExtension();
@@ -0,0 +1,27 @@
1
+ <!doctype html>
2
+ <html>
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <title>Mirasim</title>
6
+ <style>
7
+ body {
8
+ font: 14px/1.5 system-ui, sans-serif;
9
+ color: #333;
10
+ display: grid;
11
+ place-items: center;
12
+ min-height: 90vh;
13
+ margin: 0;
14
+ }
15
+ main { text-align: center; max-width: 28rem; padding: 0 1rem; }
16
+ .err { color: #b91c1c; }
17
+ .quiet { color: #888; font-size: 12px; }
18
+ </style>
19
+ </head>
20
+ <body>
21
+ <main>
22
+ <p id="status">Connecting to Mirasim…</p>
23
+ <p class="quiet">This tab closes itself once the bridge is up.</p>
24
+ </main>
25
+ <script src="connect.js"></script>
26
+ </body>
27
+ </html>
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Connect page: carries the relay URL from the local bridge to the service
3
+ * worker. The bridge opens this page through the browser binary (reusing the
4
+ * running Chrome); on success the service worker closes the tab, so the page
5
+ * is on screen for well under a second.
6
+ */
7
+
8
+ const status = document.getElementById("status");
9
+ const relayUrl = new URLSearchParams(location.search).get("relay");
10
+
11
+ if (!relayUrl) {
12
+ status.textContent = "Missing relay URL — this page is meant to be opened by Mirasim.";
13
+ status.className = "err";
14
+ } else {
15
+ chrome.runtime.sendMessage({ type: "bridge.connect", relayUrl }, (response) => {
16
+ if (chrome.runtime.lastError) {
17
+ status.textContent = `Bridge error: ${chrome.runtime.lastError.message}`;
18
+ status.className = "err";
19
+ return;
20
+ }
21
+ if (response?.success) {
22
+ status.textContent = "Connected — handing this tab back.";
23
+ } else {
24
+ status.textContent = `Could not connect: ${response?.error ?? "unknown error"}`;
25
+ status.className = "err";
26
+ }
27
+ });
28
+ }