@toddzheng024/dscode-desktop 0.0.0-stage → 0.7.34

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 (485) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +120 -2
  3. package/THIRD_PARTY_NOTICES.md +30 -0
  4. package/bin/apply_patch +9 -0
  5. package/cordis.patch.yml +433 -0
  6. package/docs/CHANGELOG.md +649 -0
  7. package/docs/account-login.md +44 -0
  8. package/docs/auto-review.md +128 -0
  9. package/docs/browser-use.md +784 -0
  10. package/docs/computer-use.md +81 -0
  11. package/docs/custom-providers.md +88 -0
  12. package/docs/demo.md +158 -0
  13. package/docs/distribution.md +58 -0
  14. package/docs/dscode-ultra.md +75 -0
  15. package/docs/email.md +296 -0
  16. package/docs/exec.md +40 -0
  17. package/docs/hub-distribution.md +212 -0
  18. package/docs/memory.md +121 -0
  19. package/docs/opencode-go.md +61 -0
  20. package/docs/plugin-hub.md +68 -0
  21. package/docs/releases/0.2.0.md +28 -0
  22. package/docs/releases/0.3.0.md +21 -0
  23. package/docs/releases/0.4.0.md +20 -0
  24. package/docs/releases/0.5.0.md +19 -0
  25. package/docs/releases/0.6.0.md +20 -0
  26. package/docs/releases/0.7.0.md +22 -0
  27. package/docs/releases/0.7.1.md +20 -0
  28. package/docs/releases/0.7.10.md +10 -0
  29. package/docs/releases/0.7.11.md +17 -0
  30. package/docs/releases/0.7.12.md +11 -0
  31. package/docs/releases/0.7.13.md +12 -0
  32. package/docs/releases/0.7.14.md +11 -0
  33. package/docs/releases/0.7.15.md +11 -0
  34. package/docs/releases/0.7.16.md +10 -0
  35. package/docs/releases/0.7.17.md +12 -0
  36. package/docs/releases/0.7.18.md +13 -0
  37. package/docs/releases/0.7.19.md +8 -0
  38. package/docs/releases/0.7.2.md +19 -0
  39. package/docs/releases/0.7.20.md +10 -0
  40. package/docs/releases/0.7.21.md +9 -0
  41. package/docs/releases/0.7.22.md +11 -0
  42. package/docs/releases/0.7.23.md +14 -0
  43. package/docs/releases/0.7.24.md +11 -0
  44. package/docs/releases/0.7.25.md +8 -0
  45. package/docs/releases/0.7.26.md +18 -0
  46. package/docs/releases/0.7.27.md +8 -0
  47. package/docs/releases/0.7.28.md +22 -0
  48. package/docs/releases/0.7.29.md +12 -0
  49. package/docs/releases/0.7.3.md +23 -0
  50. package/docs/releases/0.7.30.md +19 -0
  51. package/docs/releases/0.7.31.md +14 -0
  52. package/docs/releases/0.7.32.md +14 -0
  53. package/docs/releases/0.7.33.md +37 -0
  54. package/docs/releases/0.7.34.md +24 -0
  55. package/docs/releases/0.7.4.md +16 -0
  56. package/docs/releases/0.7.5.md +18 -0
  57. package/docs/releases/0.7.6.md +29 -0
  58. package/docs/releases/0.7.7.md +36 -0
  59. package/docs/releases/0.7.8.md +14 -0
  60. package/docs/releases/0.7.9.md +15 -0
  61. package/docs/session-bridge.md +80 -0
  62. package/docs/session-cards.md +53 -0
  63. package/docs/session-communication.md +186 -0
  64. package/docs/session-metrics.md +41 -0
  65. package/docs/skills.md +81 -0
  66. package/docs/triggers.md +338 -0
  67. package/docs/tui-commands.md +204 -0
  68. package/extensions/browser/manifest.json +22 -0
  69. package/extensions/browser/popup.css +6 -0
  70. package/extensions/browser/popup.html +11 -0
  71. package/extensions/browser/popup.mjs +47 -0
  72. package/extensions/browser/protocol.mjs +168 -0
  73. package/extensions/browser/worker.mjs +94 -0
  74. package/icon.svg +7 -0
  75. package/locale/en.json +6 -0
  76. package/locale/zh-cn.json +6 -0
  77. package/node_modules/@deepseek-ai/node-addon-system/LICENSE +21 -0
  78. package/node_modules/@deepseek-ai/node-addon-system/README.i18n.yaml +6 -0
  79. package/node_modules/@deepseek-ai/node-addon-system/README.md +15 -0
  80. package/node_modules/@deepseek-ai/node-addon-system/README.zh.md +15 -0
  81. package/node_modules/@deepseek-ai/node-addon-system/lib/flock.d.ts +13 -0
  82. package/node_modules/@deepseek-ai/node-addon-system/lib/flock.js +51 -0
  83. package/node_modules/@deepseek-ai/node-addon-system/lib/index.d.ts +73 -0
  84. package/node_modules/@deepseek-ai/node-addon-system/lib/index.js +98 -0
  85. package/node_modules/@deepseek-ai/node-addon-system/package.json +45 -0
  86. package/node_modules/@deepseek-ai/node-addon-system/src/flock.c +164 -0
  87. package/node_modules/@deepseek-ai/node-addon-system/src/main.c +298 -0
  88. package/node_modules/@deepseek-ai/node-addon-system-darwin-arm64/LICENSE +28 -0
  89. package/node_modules/@deepseek-ai/node-addon-system-darwin-arm64/README.i18n.yaml +6 -0
  90. package/node_modules/@deepseek-ai/node-addon-system-darwin-arm64/README.md +9 -0
  91. package/node_modules/@deepseek-ai/node-addon-system-darwin-arm64/README.zh.md +9 -0
  92. package/node_modules/@deepseek-ai/node-addon-system-darwin-arm64/bin/system.node +0 -0
  93. package/node_modules/@deepseek-ai/node-addon-system-darwin-arm64/package.json +31 -0
  94. package/node_modules/@deepseek-ai/node-addon-system-darwin-arm64/prebuilds.json +6 -0
  95. package/node_modules/@deepseek-ai/node-addon-system-darwin-x64/LICENSE +28 -0
  96. package/node_modules/@deepseek-ai/node-addon-system-darwin-x64/README.i18n.yaml +6 -0
  97. package/node_modules/@deepseek-ai/node-addon-system-darwin-x64/README.md +9 -0
  98. package/node_modules/@deepseek-ai/node-addon-system-darwin-x64/README.zh.md +9 -0
  99. package/node_modules/@deepseek-ai/node-addon-system-darwin-x64/bin/system.node +0 -0
  100. package/node_modules/@deepseek-ai/node-addon-system-darwin-x64/package.json +31 -0
  101. package/node_modules/@deepseek-ai/node-addon-system-darwin-x64/prebuilds.json +6 -0
  102. package/node_modules/@deepseek-ai/node-addon-system-linux-arm64/LICENSE +28 -0
  103. package/node_modules/@deepseek-ai/node-addon-system-linux-arm64/README.i18n.yaml +6 -0
  104. package/node_modules/@deepseek-ai/node-addon-system-linux-arm64/README.md +11 -0
  105. package/node_modules/@deepseek-ai/node-addon-system-linux-arm64/README.zh.md +11 -0
  106. package/node_modules/@deepseek-ai/node-addon-system-linux-arm64/bin/glibc/system.node +0 -0
  107. package/node_modules/@deepseek-ai/node-addon-system-linux-arm64/bin/landlock-run +0 -0
  108. package/node_modules/@deepseek-ai/node-addon-system-linux-arm64/bin/musl/system.node +0 -0
  109. package/node_modules/@deepseek-ai/node-addon-system-linux-arm64/package.json +31 -0
  110. package/node_modules/@deepseek-ai/node-addon-system-linux-arm64/prebuilds.json +24 -0
  111. package/node_modules/@deepseek-ai/node-addon-system-linux-x64/LICENSE +28 -0
  112. package/node_modules/@deepseek-ai/node-addon-system-linux-x64/README.i18n.yaml +6 -0
  113. package/node_modules/@deepseek-ai/node-addon-system-linux-x64/README.md +11 -0
  114. package/node_modules/@deepseek-ai/node-addon-system-linux-x64/README.zh.md +11 -0
  115. package/node_modules/@deepseek-ai/node-addon-system-linux-x64/bin/glibc/system.node +0 -0
  116. package/node_modules/@deepseek-ai/node-addon-system-linux-x64/bin/landlock-run +0 -0
  117. package/node_modules/@deepseek-ai/node-addon-system-linux-x64/bin/musl/system.node +0 -0
  118. package/node_modules/@deepseek-ai/node-addon-system-linux-x64/package.json +31 -0
  119. package/node_modules/@deepseek-ai/node-addon-system-linux-x64/prebuilds.json +24 -0
  120. package/package.json +156 -4
  121. package/plugins/account/index.mjs +178 -0
  122. package/plugins/account/state.mjs +123 -0
  123. package/plugins/auto-review/audit.mjs +24 -0
  124. package/plugins/auto-review/index.mjs +267 -0
  125. package/plugins/auto-review/policy.mjs +92 -0
  126. package/plugins/browser/SKILL.md +158 -0
  127. package/plugins/browser/access.mjs +143 -0
  128. package/plugins/browser/config.mjs +60 -0
  129. package/plugins/browser/connection.mjs +385 -0
  130. package/plugins/browser/desktop-client.mjs +282 -0
  131. package/plugins/browser/desktop-host.mjs +74 -0
  132. package/plugins/browser/document-identity.mjs +57 -0
  133. package/plugins/browser/extension-relay.mjs +66 -0
  134. package/plugins/browser/files.mjs +32 -0
  135. package/plugins/browser/index.mjs +235 -0
  136. package/plugins/browser/mcp-entry.mjs +18 -0
  137. package/plugins/browser/mcp-tools.mjs +21 -0
  138. package/plugins/browser/presentation.mjs +45 -0
  139. package/plugins/browser/preview.mjs +77 -0
  140. package/plugins/browser/review.mjs +20 -0
  141. package/plugins/browser/screenshots.mjs +35 -0
  142. package/plugins/clipboard-image/clipboard-image.swift +56 -0
  143. package/plugins/clipboard-image/index.mjs +70 -0
  144. package/plugins/code-review/baseline.mjs +143 -0
  145. package/plugins/code-review/git.mjs +163 -0
  146. package/plugins/code-review/index.mjs +236 -0
  147. package/plugins/compaction/engine.mjs +304 -0
  148. package/plugins/compaction/tetris.mjs +65 -0
  149. package/plugins/compaction/threshold.mjs +99 -0
  150. package/plugins/computer-use/DESKTOP-SKILL.md +64 -0
  151. package/plugins/computer-use/desktop-host.mjs +75 -0
  152. package/plugins/computer-use/desktop-skill.mjs +28 -0
  153. package/plugins/computer-use/desktop-upstream.mjs +3 -0
  154. package/plugins/computer-use/index.mjs +37 -0
  155. package/plugins/cordis-config/plain.mjs +31 -0
  156. package/plugins/credentials/index.mjs +73 -0
  157. package/plugins/custom/adapter.mjs +110 -0
  158. package/plugins/custom/config.mjs +85 -0
  159. package/plugins/custom/desktop-client.mjs +150 -0
  160. package/plugins/custom/desktop-host.mjs +78 -0
  161. package/plugins/custom/images.mjs +32 -0
  162. package/plugins/custom/index.mjs +145 -0
  163. package/plugins/custom/wire.mjs +179 -0
  164. package/plugins/desktop/client.mjs +1041 -0
  165. package/plugins/desktop/index.mjs +10 -0
  166. package/plugins/desktop/shell-environment.mjs +9 -0
  167. package/plugins/desktop-workspace/hooks.mjs +29 -0
  168. package/plugins/desktop-workspace/index.mjs +37 -0
  169. package/plugins/dscode/board.mjs +260 -0
  170. package/plugins/dscode/child-target.mjs +11 -0
  171. package/plugins/dscode/control.mjs +17 -0
  172. package/plugins/dscode/desktop-client.mjs +55 -0
  173. package/plugins/dscode/desktop-host.mjs +37 -0
  174. package/plugins/dscode/index.mjs +270 -0
  175. package/plugins/email/cli.mjs +32 -0
  176. package/plugins/email/contacts.mjs +36 -0
  177. package/plugins/email/desktop-client.mjs +117 -0
  178. package/plugins/email/desktop-host.mjs +54 -0
  179. package/plugins/email/desktop-service.mjs +106 -0
  180. package/plugins/email/gmail-oauth.mjs +69 -0
  181. package/plugins/email/gmail-store.mjs +2 -0
  182. package/plugins/email/gmail.mjs +194 -0
  183. package/plugins/email/imap.mjs +136 -0
  184. package/plugins/email/inbox.d.mts +25 -0
  185. package/plugins/email/inbox.mjs +76 -0
  186. package/plugins/email/smtp.mjs +70 -0
  187. package/plugins/email/store.mjs +32 -0
  188. package/plugins/email-tools/index.mjs +46 -0
  189. package/plugins/exec/cli.mjs +64 -0
  190. package/plugins/exec/index.mjs +112 -0
  191. package/plugins/grok/adapter.mjs +114 -0
  192. package/plugins/grok/auth.mjs +76 -0
  193. package/plugins/grok/billing.mjs +147 -0
  194. package/plugins/grok/index.mjs +123 -0
  195. package/plugins/grok/models.mjs +129 -0
  196. package/plugins/grok/status.mjs +29 -0
  197. package/plugins/grok/wire.mjs +268 -0
  198. package/plugins/hub/catalog.mjs +82 -0
  199. package/plugins/hub/desktop-client.mjs +114 -0
  200. package/plugins/hub/desktop-host.mjs +129 -0
  201. package/plugins/i18n/messages.d.mts +21 -0
  202. package/plugins/i18n/messages.mjs +537 -0
  203. package/plugins/jev/approval.mjs +131 -0
  204. package/plugins/jev/client.mjs +74 -0
  205. package/plugins/jev/index.mjs +100 -0
  206. package/plugins/memory/content.mjs +57 -0
  207. package/plugins/memory/desktop-host.mjs +3 -0
  208. package/plugins/memory/index.mjs +168 -0
  209. package/plugins/memory/pipeline.mjs +79 -0
  210. package/plugins/memory/store.mjs +93 -0
  211. package/plugins/message-source/kind.mjs +20 -0
  212. package/plugins/opencode-go/adapter.mjs +96 -0
  213. package/plugins/opencode-go/command.mjs +128 -0
  214. package/plugins/opencode-go/index.mjs +121 -0
  215. package/plugins/opencode-go/models.mjs +58 -0
  216. package/plugins/opencode-go/oauth.mjs +194 -0
  217. package/plugins/opencode-go/search.mjs +86 -0
  218. package/plugins/opencode-go/usage.mjs +120 -0
  219. package/plugins/opencode-go/version.mjs +10 -0
  220. package/plugins/opencode-go/wire.mjs +73 -0
  221. package/plugins/openrouter/adapter.mjs +232 -0
  222. package/plugins/openrouter/index.mjs +124 -0
  223. package/plugins/openrouter/models.mjs +157 -0
  224. package/plugins/openrouter/search.mjs +113 -0
  225. package/plugins/openrouter/wire.mjs +101 -0
  226. package/plugins/providers/catalog.mjs +160 -0
  227. package/plugins/providers/chat-messages.mjs +120 -0
  228. package/plugins/providers/chat-stream.mjs +174 -0
  229. package/plugins/providers/desktop-client.mjs +84 -0
  230. package/plugins/providers/desktop-host.mjs +63 -0
  231. package/plugins/providers/effort.mjs +35 -0
  232. package/plugins/providers/http-errors.mjs +50 -0
  233. package/plugins/providers/openrouter-account.mjs +171 -0
  234. package/plugins/session-bridge/client.mjs +106 -0
  235. package/plugins/session-bridge/communication.mjs +260 -0
  236. package/plugins/session-bridge/index.mjs +67 -0
  237. package/plugins/session-bridge/mailbox.mjs +267 -0
  238. package/plugins/session-bridge/paths.mjs +21 -0
  239. package/plugins/session-bridge/server.mjs +172 -0
  240. package/plugins/session-bridge/tasks.mjs +229 -0
  241. package/plugins/session-cards/content.mjs +64 -0
  242. package/plugins/session-cards/index.mjs +38 -0
  243. package/plugins/session-cards/manager.mjs +140 -0
  244. package/plugins/session-metrics/attribution.mjs +30 -0
  245. package/plugins/session-metrics/balance.mjs +81 -0
  246. package/plugins/session-metrics/desktop-client.mjs +62 -0
  247. package/plugins/session-metrics/desktop-host.mjs +46 -0
  248. package/plugins/session-metrics/index.mjs +97 -0
  249. package/plugins/session-metrics/pricing.mjs +84 -0
  250. package/plugins/session-metrics/rate.mjs +59 -0
  251. package/plugins/session-metrics/store.mjs +75 -0
  252. package/plugins/session-metrics/turns.mjs +134 -0
  253. package/plugins/session-metrics/view.mjs +272 -0
  254. package/plugins/time-marks/desktop-host.mjs +2 -0
  255. package/plugins/time-marks/index.mjs +116 -0
  256. package/plugins/time-marks/marks.mjs +152 -0
  257. package/plugins/triggers/cli.mjs +558 -0
  258. package/plugins/triggers/commands.mjs +143 -0
  259. package/plugins/triggers/config.mjs +264 -0
  260. package/plugins/triggers/desktop-client.mjs +155 -0
  261. package/plugins/triggers/desktop-host.mjs +88 -0
  262. package/plugins/triggers/desktop-management.mjs +97 -0
  263. package/plugins/triggers/desktop-scheduler.mjs +125 -0
  264. package/plugins/triggers/host.mjs +24 -0
  265. package/plugins/triggers/index.mjs +27 -0
  266. package/plugins/triggers/job-cli.mjs +110 -0
  267. package/plugins/triggers/jobs.mjs +156 -0
  268. package/plugins/triggers/launchd.mjs +101 -0
  269. package/plugins/triggers/lease.mjs +24 -0
  270. package/plugins/triggers/log.mjs +104 -0
  271. package/plugins/triggers/management.mjs +181 -0
  272. package/plugins/triggers/options.mjs +117 -0
  273. package/plugins/triggers/overlay.mjs +7 -0
  274. package/plugins/triggers/poll.mjs +65 -0
  275. package/plugins/triggers/run.mjs +157 -0
  276. package/plugins/triggers/schedule.mjs +40 -0
  277. package/plugins/triggers/scheduler-service.mjs +46 -0
  278. package/plugins/triggers/scheduler.mjs +92 -0
  279. package/plugins/triggers/session-run.mjs +125 -0
  280. package/plugins/triggers/session.mjs +48 -0
  281. package/plugins/triggers/source-emit.mjs +19 -0
  282. package/plugins/triggers/source-host.mjs +78 -0
  283. package/plugins/triggers/source-ingress.mjs +70 -0
  284. package/plugins/triggers/source-sandbox.mjs +35 -0
  285. package/plugins/triggers/sources.mjs +45 -0
  286. package/plugins/triggers/spool.mjs +145 -0
  287. package/plugins/triggers/tools.mjs +67 -0
  288. package/plugins/triggers/unattended.mjs +18 -0
  289. package/plugins/tui-tools/composition.mjs +28 -0
  290. package/plugins/tui-tools/desktop-host.mjs +2 -0
  291. package/plugins/tui-tools/doctor-cli.mjs +22 -0
  292. package/plugins/tui-tools/doctor.mjs +149 -0
  293. package/plugins/tui-tools/hook-sources.mjs +113 -0
  294. package/plugins/tui-tools/hooks.mjs +23 -0
  295. package/plugins/tui-tools/index.mjs +269 -0
  296. package/plugins/tui-tools/sandbox-runner.mjs +123 -0
  297. package/plugins/tui-tools/shell.mjs +27 -0
  298. package/plugins/tui-tools/update.mjs +111 -0
  299. package/plugins/tui-tools/workspace-discovery.mjs +129 -0
  300. package/plugins/ultra/policy.mjs +42 -0
  301. package/plugins/worktree-subagent/worktree.mjs +56 -0
  302. package/runtime-sources.json +94 -0
  303. package/vendor/bash/LICENSE +21 -0
  304. package/vendor/bash/index.js +717 -0
  305. package/vendor/bash/types/background.d.ts +50 -0
  306. package/vendor/bash/types/index.d.ts +38 -0
  307. package/vendor/bash/types/render.d.ts +57 -0
  308. package/vendor/command-goal/LICENSE +21 -0
  309. package/vendor/command-goal/index.js +210 -0
  310. package/vendor/command-goal/types/index.d.ts +10 -0
  311. package/vendor/computer-use/LICENSE +21 -0
  312. package/vendor/computer-use/lib/approval-policy.js +12 -0
  313. package/vendor/computer-use/lib/approval-policy.js.map +1 -0
  314. package/vendor/computer-use/lib/artifacts.js +79 -0
  315. package/vendor/computer-use/lib/artifacts.js.map +1 -0
  316. package/vendor/computer-use/lib/backend.js +3 -0
  317. package/vendor/computer-use/lib/backend.js.map +1 -0
  318. package/vendor/computer-use/lib/client.js +369 -0
  319. package/vendor/computer-use/lib/client.js.map +1 -0
  320. package/vendor/computer-use/lib/config.js +159 -0
  321. package/vendor/computer-use/lib/config.js.map +1 -0
  322. package/vendor/computer-use/lib/confirmations.js +99 -0
  323. package/vendor/computer-use/lib/confirmations.js.map +1 -0
  324. package/vendor/computer-use/lib/diff.js +58 -0
  325. package/vendor/computer-use/lib/diff.js.map +1 -0
  326. package/vendor/computer-use/lib/errors.js +22 -0
  327. package/vendor/computer-use/lib/errors.js.map +1 -0
  328. package/vendor/computer-use/lib/exposure.js +244 -0
  329. package/vendor/computer-use/lib/exposure.js.map +1 -0
  330. package/vendor/computer-use/lib/index.js +66 -0
  331. package/vendor/computer-use/lib/index.js.map +1 -0
  332. package/vendor/computer-use/lib/leases.js +262 -0
  333. package/vendor/computer-use/lib/leases.js.map +1 -0
  334. package/vendor/computer-use/lib/providers/macos.js +243 -0
  335. package/vendor/computer-use/lib/providers/macos.js.map +1 -0
  336. package/vendor/computer-use/lib/providers/native-helper.js +726 -0
  337. package/vendor/computer-use/lib/providers/native-helper.js.map +1 -0
  338. package/vendor/computer-use/lib/providers/unsupported.js +51 -0
  339. package/vendor/computer-use/lib/providers/unsupported.js.map +1 -0
  340. package/vendor/computer-use/lib/service.js +608 -0
  341. package/vendor/computer-use/lib/service.js.map +1 -0
  342. package/vendor/computer-use/lib/skill.js +136 -0
  343. package/vendor/computer-use/lib/skill.js.map +1 -0
  344. package/vendor/computer-use/lib/target-resolver.js +180 -0
  345. package/vendor/computer-use/lib/target-resolver.js.map +1 -0
  346. package/vendor/computer-use/lib/tools.js +511 -0
  347. package/vendor/computer-use/lib/tools.js.map +1 -0
  348. package/vendor/computer-use/lib/types/approval-policy.d.ts +13 -0
  349. package/vendor/computer-use/lib/types/approval-policy.d.ts.map +1 -0
  350. package/vendor/computer-use/lib/types/artifacts.d.ts +10 -0
  351. package/vendor/computer-use/lib/types/artifacts.d.ts.map +1 -0
  352. package/vendor/computer-use/lib/types/backend.d.ts +124 -0
  353. package/vendor/computer-use/lib/types/backend.d.ts.map +1 -0
  354. package/vendor/computer-use/lib/types/client/index.d.ts +83 -0
  355. package/vendor/computer-use/lib/types/client/index.d.ts.map +1 -0
  356. package/vendor/computer-use/lib/types/config.d.ts +94 -0
  357. package/vendor/computer-use/lib/types/config.d.ts.map +1 -0
  358. package/vendor/computer-use/lib/types/confirmations.d.ts +23 -0
  359. package/vendor/computer-use/lib/types/confirmations.d.ts.map +1 -0
  360. package/vendor/computer-use/lib/types/diff.d.ts +5 -0
  361. package/vendor/computer-use/lib/types/diff.d.ts.map +1 -0
  362. package/vendor/computer-use/lib/types/errors.d.ts +15 -0
  363. package/vendor/computer-use/lib/types/errors.d.ts.map +1 -0
  364. package/vendor/computer-use/lib/types/exposure.d.ts +30 -0
  365. package/vendor/computer-use/lib/types/exposure.d.ts.map +1 -0
  366. package/vendor/computer-use/lib/types/index.d.ts +21 -0
  367. package/vendor/computer-use/lib/types/index.d.ts.map +1 -0
  368. package/vendor/computer-use/lib/types/leases.d.ts +81 -0
  369. package/vendor/computer-use/lib/types/leases.d.ts.map +1 -0
  370. package/vendor/computer-use/lib/types/providers/macos.d.ts +39 -0
  371. package/vendor/computer-use/lib/types/providers/macos.d.ts.map +1 -0
  372. package/vendor/computer-use/lib/types/providers/native-helper.d.ts +48 -0
  373. package/vendor/computer-use/lib/types/providers/native-helper.d.ts.map +1 -0
  374. package/vendor/computer-use/lib/types/providers/unsupported.d.ts +23 -0
  375. package/vendor/computer-use/lib/types/providers/unsupported.d.ts.map +1 -0
  376. package/vendor/computer-use/lib/types/service.d.ts +58 -0
  377. package/vendor/computer-use/lib/types/service.d.ts.map +1 -0
  378. package/vendor/computer-use/lib/types/skill.d.ts +14 -0
  379. package/vendor/computer-use/lib/types/skill.d.ts.map +1 -0
  380. package/vendor/computer-use/lib/types/target-resolver.d.ts +38 -0
  381. package/vendor/computer-use/lib/types/target-resolver.d.ts.map +1 -0
  382. package/vendor/computer-use/lib/types/tools.d.ts +6 -0
  383. package/vendor/computer-use/lib/types/tools.d.ts.map +1 -0
  384. package/vendor/computer-use/lib/types/types.d.ts +270 -0
  385. package/vendor/computer-use/lib/types/types.d.ts.map +1 -0
  386. package/vendor/computer-use/lib/types/web.d.ts +33 -0
  387. package/vendor/computer-use/lib/types/web.d.ts.map +1 -0
  388. package/vendor/computer-use/lib/types.js +9 -0
  389. package/vendor/computer-use/lib/types.js.map +1 -0
  390. package/vendor/computer-use/lib/web.js +188 -0
  391. package/vendor/computer-use/lib/web.js.map +1 -0
  392. package/vendor/computer-use/native/macos/Sources/Fixture/main.swift +412 -0
  393. package/vendor/computer-use/native/macos/Sources/Helper/CursorImage.swift +11 -0
  394. package/vendor/computer-use/native/macos/Sources/Helper/CursorMotion.swift +67 -0
  395. package/vendor/computer-use/native/macos/Sources/Helper/CursorOverlay.swift +840 -0
  396. package/vendor/computer-use/native/macos/Sources/Helper/TargetedPointer.swift +327 -0
  397. package/vendor/computer-use/native/macos/Sources/Helper/WindowNumberMatcher.swift +29 -0
  398. package/vendor/computer-use/native/macos/Sources/Helper/main.swift +1244 -0
  399. package/vendor/computer-use/native/macos/Sources/Monitor/main.swift +153 -0
  400. package/vendor/computer-use/native/macos/bin/dsh-computer-use-helper +0 -0
  401. package/vendor/computer-use/native/macos/manifest.json +14 -0
  402. package/vendor/persistent/LICENSE +21 -0
  403. package/vendor/persistent/index.js +411 -0
  404. package/vendor/persistent/types/index.d.ts +24 -0
  405. package/vendor/subagent/LICENSE +21 -0
  406. package/vendor/subagent/index.js +694 -0
  407. package/vendor/subagent/invariant.js +52 -0
  408. package/vendor/subagent/model-selection-settings.js +68 -0
  409. package/vendor/subagent/types/index.d.ts +82 -0
  410. package/vendor/subagent/types/invariant.d.ts +16 -0
  411. package/vendor/subagent/types/list-models.d.ts +10 -0
  412. package/vendor/subagent/types/model-selection-settings.d.ts +45 -0
  413. package/vendor/subagent/types/model-selection-state.d.ts +48 -0
  414. package/vendor/subagent/types/model-selection.d.ts +81 -0
  415. package/vendor/subagent-core/LICENSE +21 -0
  416. package/vendor/subagent-core/index.js +3202 -0
  417. package/vendor/subagent-core/invariant.js +76 -0
  418. package/vendor/subagent-core/typert.host.d.ts +3 -0
  419. package/vendor/subagent-core/typert.host.js +978 -0
  420. package/vendor/subagent-core/typert.remote-client.d.ts +25 -0
  421. package/vendor/subagent-core/typert.remote-client.js +114 -0
  422. package/vendor/subagent-core/types/archive-admission.d.ts +16 -0
  423. package/vendor/subagent-core/types/archive-admission.js +151 -0
  424. package/vendor/subagent-core/types/assistant-output.d.ts +48 -0
  425. package/vendor/subagent-core/types/assistant-output.js +73 -0
  426. package/vendor/subagent-core/types/catalog.d.ts +84 -0
  427. package/vendor/subagent-core/types/catalog.js +109 -0
  428. package/vendor/subagent-core/types/child-agent.d.ts +156 -0
  429. package/vendor/subagent-core/types/child-agent.js +213 -0
  430. package/vendor/subagent-core/types/client.d.ts +8 -0
  431. package/vendor/subagent-core/types/client.js +7 -0
  432. package/vendor/subagent-core/types/continuation-activation.d.ts +267 -0
  433. package/vendor/subagent-core/types/continuation-activation.js +700 -0
  434. package/vendor/subagent-core/types/continuation-messages.d.ts +62 -0
  435. package/vendor/subagent-core/types/continuation-messages.js +106 -0
  436. package/vendor/subagent-core/types/continuation.d.ts +124 -0
  437. package/vendor/subagent-core/types/continuation.js +455 -0
  438. package/vendor/subagent-core/types/control-types.d.ts +135 -0
  439. package/vendor/subagent-core/types/control-types.js +8 -0
  440. package/vendor/subagent-core/types/control.d.ts +45 -0
  441. package/vendor/subagent-core/types/control.js +79 -0
  442. package/vendor/subagent-core/types/depth.d.ts +31 -0
  443. package/vendor/subagent-core/types/depth.js +39 -0
  444. package/vendor/subagent-core/types/descriptor.d.ts +144 -0
  445. package/vendor/subagent-core/types/descriptor.js +193 -0
  446. package/vendor/subagent-core/types/error.d.ts +11 -0
  447. package/vendor/subagent-core/types/error.js +14 -0
  448. package/vendor/subagent-core/types/inbox.d.ts +43 -0
  449. package/vendor/subagent-core/types/inbox.js +61 -0
  450. package/vendor/subagent-core/types/index.d.ts +320 -0
  451. package/vendor/subagent-core/types/index.js +475 -0
  452. package/vendor/subagent-core/types/internal.d.ts +58 -0
  453. package/vendor/subagent-core/types/internal.js +58 -0
  454. package/vendor/subagent-core/types/invariant.d.ts +13 -0
  455. package/vendor/subagent-core/types/invariant.js +91 -0
  456. package/vendor/subagent-core/types/lifecycle.d.ts +113 -0
  457. package/vendor/subagent-core/types/lifecycle.js +180 -0
  458. package/vendor/subagent-core/types/list-children.d.ts +36 -0
  459. package/vendor/subagent-core/types/list-children.js +166 -0
  460. package/vendor/subagent-core/types/out-of-process.d.ts +118 -0
  461. package/vendor/subagent-core/types/out-of-process.js +219 -0
  462. package/vendor/subagent-core/types/projection-types.d.ts +82 -0
  463. package/vendor/subagent-core/types/projection-types.js +7 -0
  464. package/vendor/subagent-core/types/projection.d.ts +100 -0
  465. package/vendor/subagent-core/types/projection.js +157 -0
  466. package/vendor/subagent-core/types/run-settlement.d.ts +17 -0
  467. package/vendor/subagent-core/types/run-settlement.js +70 -0
  468. package/vendor/subagent-core/types/types.d.ts +377 -0
  469. package/vendor/subagent-core/types/types.js +19 -0
  470. package/vendor/subagent-driver/LICENSE +21 -0
  471. package/vendor/subagent-driver/index.js +255 -0
  472. package/vendor/subagent-driver/types/index.d.ts +33 -0
  473. package/vendor/subagent-driver/types/structured.d.ts +42 -0
  474. package/vendor/subagent-fork/LICENSE +21 -0
  475. package/vendor/subagent-fork/index.js +61 -0
  476. package/vendor/subagent-fork/types/index.d.ts +20 -0
  477. package/vendor/subagent-spawn/LICENSE +21 -0
  478. package/vendor/subagent-spawn/index.js +45 -0
  479. package/vendor/subagent-spawn/types/index.d.ts +19 -0
  480. package/vendor/terminal/LICENSE +21 -0
  481. package/vendor/terminal/index.js +1093 -0
  482. package/vendor/terminal/types/config.d.ts +82 -0
  483. package/vendor/terminal/types/index.d.ts +36 -0
  484. package/vendor/terminal/types/sanitize.d.ts +47 -0
  485. package/vendor/terminal/types/session.d.ts +87 -0
@@ -0,0 +1,1244 @@
1
+ import AppKit
2
+ import ApplicationServices
3
+ import CoreGraphics
4
+ import CryptoKit
5
+ import Darwin
6
+ import Foundation
7
+ import ScreenCaptureKit
8
+
9
+ private let helperVersion = "0.3.3"
10
+
11
+ private struct HelperError: Error {
12
+ let code: String
13
+ let message: String
14
+ }
15
+
16
+ private struct ElementRecord {
17
+ let index: Int
18
+ let locator: [Int]
19
+ let element: AXUIElement
20
+ let json: [String: Any]
21
+ let hashLine: String
22
+ }
23
+
24
+ private struct ObservationSnapshot {
25
+ let app: NSRunningApplication
26
+ let appJSON: [String: Any]
27
+ let frontmost: Bool
28
+ let window: AXUIElement?
29
+ let windowJSON: [String: Any]?
30
+ let stateHash: String
31
+ let treeText: String
32
+ let truncated: Bool
33
+ let elements: [ElementRecord]
34
+ }
35
+
36
+ private func fail(_ code: String, _ message: String) -> HelperError {
37
+ HelperError(code: code, message: message)
38
+ }
39
+
40
+ private func string(_ object: Any?, _ field: String) throws -> String {
41
+ guard let value = object as? String, !value.isEmpty else {
42
+ throw fail("COMPUTER_PROVIDER_FAILURE", "missing or invalid \(field)")
43
+ }
44
+ return value
45
+ }
46
+
47
+ private func stringAllowingEmpty(_ object: Any?, _ field: String) throws -> String {
48
+ guard let value = object as? String else {
49
+ throw fail("COMPUTER_PROVIDER_FAILURE", "missing or invalid \(field)")
50
+ }
51
+ return value
52
+ }
53
+
54
+ private func int(_ object: Any?, _ field: String) throws -> Int {
55
+ guard let number = object as? NSNumber else {
56
+ throw fail("COMPUTER_PROVIDER_FAILURE", "missing or invalid \(field)")
57
+ }
58
+ return number.intValue
59
+ }
60
+
61
+ private func double(_ object: Any?, _ field: String) throws -> Double {
62
+ guard let number = object as? NSNumber else {
63
+ throw fail("COMPUTER_PROVIDER_FAILURE", "missing or invalid \(field)")
64
+ }
65
+ return number.doubleValue
66
+ }
67
+
68
+ private func bool(_ object: Any?, default fallback: Bool = false) -> Bool {
69
+ (object as? NSNumber)?.boolValue ?? fallback
70
+ }
71
+
72
+ private func dictionary(_ object: Any?, _ field: String) throws -> [String: Any] {
73
+ guard let value = object as? [String: Any] else {
74
+ throw fail("COMPUTER_PROVIDER_FAILURE", "missing or invalid \(field)")
75
+ }
76
+ return value
77
+ }
78
+
79
+ private func dictionaries(_ object: Any?) -> [[String: Any]] {
80
+ object as? [[String: Any]] ?? []
81
+ }
82
+
83
+ private enum TransportEndpoint: Hashable {
84
+ case pipe(handle: UInt64, peer: UInt64)
85
+ case unixSocket(handle: UInt64, peer: UInt64)
86
+ }
87
+
88
+ private func processDescriptors(_ pid: Int32) -> [proc_fdinfo] {
89
+ let capacity = Int(max(proc_pidinfo(pid, PROC_PIDLISTFDS, 0, nil, 0), 0))
90
+ guard capacity >= MemoryLayout<proc_fdinfo>.size else { return [] }
91
+ var descriptors = Array(repeating: proc_fdinfo(), count: capacity / MemoryLayout<proc_fdinfo>.size)
92
+ let bytes = descriptors.withUnsafeMutableBytes { buffer in
93
+ proc_pidinfo(pid, PROC_PIDLISTFDS, 0, buffer.baseAddress, Int32(buffer.count))
94
+ }
95
+ guard bytes > 0 else { return [] }
96
+ return Array(descriptors.prefix(Int(bytes) / MemoryLayout<proc_fdinfo>.size))
97
+ }
98
+
99
+ private func transportEndpoint(_ pid: Int32, _ fd: Int32) -> TransportEndpoint? {
100
+ var pipe = pipe_fdinfo()
101
+ let pipeSize = MemoryLayout<pipe_fdinfo>.size
102
+ if proc_pidfdinfo(pid, fd, PROC_PIDFDPIPEINFO, &pipe, Int32(pipeSize)) == pipeSize {
103
+ return .pipe(handle: pipe.pipeinfo.pipe_handle, peer: pipe.pipeinfo.pipe_peerhandle)
104
+ }
105
+
106
+ var socket = socket_fdinfo()
107
+ let socketSize = MemoryLayout<socket_fdinfo>.size
108
+ guard proc_pidfdinfo(pid, fd, PROC_PIDFDSOCKETINFO, &socket, Int32(socketSize)) == socketSize,
109
+ socket.psi.soi_family == AF_UNIX,
110
+ socket.psi.soi_type == SOCK_STREAM,
111
+ socket.psi.soi_kind == SOCKINFO_UN else { return nil }
112
+ return .unixSocket(
113
+ handle: socket.psi.soi_so,
114
+ peer: socket.psi.soi_proto.pri_un.unsi_conn_so
115
+ )
116
+ }
117
+
118
+ private func transportEndpointsMatch(_ child: TransportEndpoint, _ parent: TransportEndpoint) -> Bool {
119
+ switch (child, parent) {
120
+ case let (.pipe(childHandle, childPeer), .pipe(parentHandle, parentPeer)),
121
+ let (.unixSocket(childHandle, childPeer), .unixSocket(parentHandle, parentPeer)):
122
+ return childPeer != 0
123
+ && childHandle == parentPeer
124
+ && childPeer == parentHandle
125
+ default:
126
+ return false
127
+ }
128
+ }
129
+
130
+ private func disconnectedUnixSocket(_ endpoint: TransportEndpoint) -> Bool {
131
+ if case let .unixSocket(_, peer) = endpoint { return peer == 0 }
132
+ return false
133
+ }
134
+
135
+ private func parentOwnsStandardTransport() -> Bool {
136
+ let childEndpoints = [STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO].compactMap { fd in
137
+ transportEndpoint(getpid(), fd).map { (fd, $0) }
138
+ }
139
+ guard childEndpoints.count == 3,
140
+ Set(childEndpoints.map(\.1)).count == 3 else { return false }
141
+
142
+ let parentEndpoints = processDescriptors(getppid()).compactMap { descriptor in
143
+ transportEndpoint(getppid(), descriptor.proc_fd)
144
+ }
145
+ return childEndpoints.allSatisfy { fd, child in
146
+ let matches = parentEndpoints.filter { transportEndpointsMatch(child, $0) }
147
+ if matches.count == 1 { return true }
148
+ // The batch stdin writer may close its parent endpoint before the helper
149
+ // reaches main. Node represents that standard stream as a disconnected
150
+ // Unix socket, while stdout and stderr remain provably parent-owned.
151
+ return fd == STDIN_FILENO && matches.isEmpty && disconnectedUnixSocket(child)
152
+ }
153
+ }
154
+
155
+ private func requireHostTransport() throws {
156
+ guard getpgrp() == getpid(),
157
+ parentOwnsStandardTransport() else {
158
+ throw fail("COMPUTER_ACTION_BLOCKED", "native helper requires managed parent transport; use the registered Computer Use Tools")
159
+ }
160
+ }
161
+
162
+ private func permissionAccessibility() -> String {
163
+ AXIsProcessTrusted() ? "granted" : "denied"
164
+ }
165
+
166
+ private func permissionScreenRecording() -> String {
167
+ CGPreflightScreenCaptureAccess() ? "granted" : "denied"
168
+ }
169
+
170
+ private func runningApps() -> [NSRunningApplication] {
171
+ NSWorkspace.shared.runningApplications
172
+ .filter { !$0.isTerminated && $0.activationPolicy == .regular && $0.bundleIdentifier != nil }
173
+ .sorted {
174
+ let lhs = $0.localizedName ?? $0.bundleIdentifier ?? ""
175
+ let rhs = $1.localizedName ?? $1.bundleIdentifier ?? ""
176
+ return lhs.localizedCaseInsensitiveCompare(rhs) == .orderedAscending
177
+ }
178
+ }
179
+
180
+ private func appJSON(_ app: NSRunningApplication) throws -> [String: Any] {
181
+ guard let bundleId = app.bundleIdentifier else {
182
+ throw fail("COMPUTER_APP_NOT_FOUND", "running application has no bundle identifier")
183
+ }
184
+ return [
185
+ "bundleId": bundleId,
186
+ "pid": Int(app.processIdentifier),
187
+ "name": app.localizedName ?? bundleId,
188
+ ]
189
+ }
190
+
191
+ private func resolveApp(_ selector: [String: Any]) throws -> NSRunningApplication {
192
+ let bundleId = selector["bundleId"] as? String
193
+ let pid = (selector["pid"] as? NSNumber)?.int32Value
194
+ let name = selector["name"] as? String
195
+ if bundleId == nil && pid == nil && name == nil {
196
+ throw fail("COMPUTER_APP_NOT_FOUND", "app selector needs bundleId, pid, or unique name")
197
+ }
198
+ let matches = runningApps().filter { app in
199
+ if let bundleId, app.bundleIdentifier != bundleId { return false }
200
+ if let pid, app.processIdentifier != pid { return false }
201
+ if let name {
202
+ let display = app.localizedName ?? ""
203
+ if display.compare(name, options: [.caseInsensitive, .diacriticInsensitive]) != .orderedSame { return false }
204
+ }
205
+ return true
206
+ }
207
+ guard matches.count == 1, let app = matches.first else {
208
+ if matches.isEmpty { throw fail("COMPUTER_APP_NOT_FOUND", "no running application uniquely matches the selector") }
209
+ if bundleId != nil {
210
+ throw fail("COMPUTER_APP_NOT_FOUND", "multiple running processes match the bundleId; add the exact pid from computer_list_apps")
211
+ }
212
+ throw fail("COMPUTER_APP_NOT_FOUND", "application name is ambiguous; use bundleId and, if necessary, pid")
213
+ }
214
+ return app
215
+ }
216
+
217
+ private func axCopy(_ element: AXUIElement, _ attribute: CFString) -> Any? {
218
+ var value: CFTypeRef?
219
+ let result = AXUIElementCopyAttributeValue(element, attribute, &value)
220
+ return result == .success ? value : nil
221
+ }
222
+
223
+ private func axString(_ element: AXUIElement, _ attribute: CFString) -> String? {
224
+ if let value = axCopy(element, attribute) as? String, !value.isEmpty { return value }
225
+ if let value = axCopy(element, attribute) as? NSAttributedString, !value.string.isEmpty { return value.string }
226
+ return nil
227
+ }
228
+
229
+ private func axBool(_ element: AXUIElement, _ attribute: CFString) -> Bool? {
230
+ (axCopy(element, attribute) as? NSNumber)?.boolValue
231
+ }
232
+
233
+ private func axPoint(_ element: AXUIElement, _ attribute: CFString) -> CGPoint? {
234
+ guard let value = axCopy(element, attribute), CFGetTypeID(value as CFTypeRef) == AXValueGetTypeID() else { return nil }
235
+ let axValue = value as! AXValue
236
+ guard AXValueGetType(axValue) == .cgPoint else { return nil }
237
+ var point = CGPoint.zero
238
+ return AXValueGetValue(axValue, .cgPoint, &point) ? point : nil
239
+ }
240
+
241
+ private func axSize(_ element: AXUIElement, _ attribute: CFString) -> CGSize? {
242
+ guard let value = axCopy(element, attribute), CFGetTypeID(value as CFTypeRef) == AXValueGetTypeID() else { return nil }
243
+ let axValue = value as! AXValue
244
+ guard AXValueGetType(axValue) == .cgSize else { return nil }
245
+ var size = CGSize.zero
246
+ return AXValueGetValue(axValue, .cgSize, &size) ? size : nil
247
+ }
248
+
249
+ private func axFrame(_ element: AXUIElement) -> CGRect? {
250
+ guard let position = axPoint(element, kAXPositionAttribute as CFString),
251
+ let size = axSize(element, kAXSizeAttribute as CFString),
252
+ size.width >= 0, size.height >= 0 else { return nil }
253
+ return CGRect(origin: position, size: size)
254
+ }
255
+
256
+ private func rectJSON(_ rect: CGRect) -> [String: Any] {
257
+ ["x": rect.origin.x, "y": rect.origin.y, "width": rect.size.width, "height": rect.size.height]
258
+ }
259
+
260
+ private func axActions(_ element: AXUIElement) -> [String] {
261
+ var names: CFArray?
262
+ guard AXUIElementCopyActionNames(element, &names) == .success, let names else { return [] }
263
+ return (names as NSArray).compactMap { $0 as? String }.sorted()
264
+ }
265
+
266
+ private func axChildren(_ element: AXUIElement) -> [AXUIElement] {
267
+ guard let values = axCopy(element, kAXChildrenAttribute as CFString) as? [AnyObject] else { return [] }
268
+ return values.compactMap { value in
269
+ guard CFGetTypeID(value) == AXUIElementGetTypeID() else { return nil }
270
+ return (value as! AXUIElement)
271
+ }
272
+ }
273
+
274
+ private func pressWithDescendantFallback(_ element: AXUIElement) -> Bool {
275
+ // Only descend when the selected element advertised AXPress; otherwise the
276
+ // element/coordinate fallback below owns the click decision.
277
+ guard axActions(element).contains(kAXPressAction as String) else { return false }
278
+ var visited = Set<CFHashCode>()
279
+ return pressRecursive(element, remainingDepth: 4, visited: &visited)
280
+ }
281
+
282
+ private func pressRecursive(_ element: AXUIElement, remainingDepth: Int, visited: inout Set<CFHashCode>) -> Bool {
283
+ guard remainingDepth >= 0 else { return false }
284
+ let identity = CFHash(element)
285
+ guard visited.insert(identity).inserted else { return false }
286
+ if axActions(element).contains(kAXPressAction as String),
287
+ AXUIElementPerformAction(element, kAXPressAction as CFString) == .success {
288
+ return true
289
+ }
290
+ for child in axChildren(element) {
291
+ if pressRecursive(child, remainingDepth: remainingDepth - 1, visited: &visited) { return true }
292
+ }
293
+ return false
294
+ }
295
+
296
+ private func sanitize(_ value: String, max: Int = 240) -> String {
297
+ let collapsed = value.replacingOccurrences(of: "\\s+", with: " ", options: .regularExpression).trimmingCharacters(in: .whitespacesAndNewlines)
298
+ guard collapsed.count > max else { return collapsed }
299
+ return String(collapsed.prefix(max)) + "..."
300
+ }
301
+
302
+ private func jsonString(_ value: String) -> String {
303
+ let data = try? JSONSerialization.data(withJSONObject: [value])
304
+ let encoded = data.flatMap { String(data: $0, encoding: .utf8) } ?? "[\"\"]"
305
+ return String(encoded.dropFirst().dropLast())
306
+ }
307
+
308
+ private func chosenWindow(_ appElement: AXUIElement) -> AXUIElement? {
309
+ if let focused = axCopy(appElement, kAXFocusedWindowAttribute as CFString) as AnyObject?,
310
+ CFGetTypeID(focused) == AXUIElementGetTypeID() {
311
+ return (focused as! AXUIElement)
312
+ }
313
+ if let main = axCopy(appElement, kAXMainWindowAttribute as CFString) as AnyObject?,
314
+ CFGetTypeID(main) == AXUIElementGetTypeID() {
315
+ return (main as! AXUIElement)
316
+ }
317
+ return axChildren(appElement).first
318
+ }
319
+
320
+ private func windowNumber(_ element: AXUIElement) -> Int? {
321
+ (axCopy(element, "AXWindowNumber" as CFString) as? NSNumber)?.intValue
322
+ }
323
+
324
+ private func windowNumber(
325
+ app: NSRunningApplication,
326
+ frame: CGRect?,
327
+ title: String?
328
+ ) -> Int? {
329
+ guard let windows = CGWindowListCopyWindowInfo(
330
+ [.optionOnScreenOnly, .excludeDesktopElements],
331
+ kCGNullWindowID
332
+ ) as? [[String: Any]] else { return nil }
333
+ let candidates = windows.filter { window in
334
+ guard (window[kCGWindowOwnerPID as String] as? NSNumber)?.int32Value == app.processIdentifier,
335
+ (window[kCGWindowLayer as String] as? NSNumber)?.intValue == 0 else { return false }
336
+ if let frame {
337
+ guard let bounds = window[kCGWindowBounds as String] as? [String: Any],
338
+ let candidate = CGRect(dictionaryRepresentation: bounds as CFDictionary) else { return false }
339
+ let tolerance: CGFloat = 2
340
+ guard abs(candidate.minX - frame.minX) <= tolerance,
341
+ abs(candidate.minY - frame.minY) <= tolerance,
342
+ abs(candidate.width - frame.width) <= tolerance,
343
+ abs(candidate.height - frame.height) <= tolerance else { return false }
344
+ }
345
+ return true
346
+ }
347
+ // Frame plus owner already identifies one window in the common case, and
348
+ // requiring the titles to be equal on top of that loses more than it
349
+ // gains: Notes reports "备忘录 – 5个备忘录" through accessibility while the
350
+ // window server calls the same window "备忘录". The mismatch silently cost
351
+ // the window id, and without an id the agent cursor is skipped entirely
352
+ // with nothing reported — the caller cannot even tell it lost the cursor.
353
+ //
354
+ // So the title only has to disambiguate, and only when the frame did not.
355
+ // Empty WindowServer titles carry no identity and must not participate in
356
+ // prefix matching: every string has an empty prefix.
357
+ return matchedWindowNumber(
358
+ candidates: candidates.map { window in
359
+ WindowNumberCandidate(
360
+ number: (window[kCGWindowNumber as String] as? NSNumber)?.intValue,
361
+ title: window[kCGWindowName as String] as? String
362
+ )
363
+ },
364
+ observedTitle: title
365
+ )
366
+ }
367
+
368
+ private func sha256(_ value: String) -> String {
369
+ SHA256.hash(data: Data(value.utf8)).map { String(format: "%02x", $0) }.joined()
370
+ }
371
+
372
+ private func observeSnapshot(app: NSRunningApplication, limits: [String: Any]) throws -> ObservationSnapshot {
373
+ guard AXIsProcessTrusted() else {
374
+ throw fail("COMPUTER_PERMISSION_REQUIRED", "macOS Accessibility permission is required for the DSH helper")
375
+ }
376
+ let maxNodes = try int(limits["maxNodes"], "limits.maxNodes")
377
+ let maxDepth = try int(limits["maxDepth"], "limits.maxDepth")
378
+ let maxTextBytes = try int(limits["maxTextBytes"], "limits.maxTextBytes")
379
+ guard maxNodes > 0, maxDepth > 0, maxTextBytes > 0 else {
380
+ throw fail("COMPUTER_PROVIDER_FAILURE", "observation limits must be positive")
381
+ }
382
+ let appElement = AXUIElementCreateApplication(app.processIdentifier)
383
+ let rootWindow = chosenWindow(appElement)
384
+ let root = rootWindow ?? appElement
385
+ let frame = rootWindow.flatMap(axFrame)
386
+ let title = rootWindow.flatMap { axString($0, kAXTitleAttribute as CFString) }
387
+ let windowId = rootWindow.flatMap(windowNumber)
388
+ ?? windowNumber(app: app, frame: frame, title: title)
389
+ var windowJSON: [String: Any]?
390
+ if let frame {
391
+ var json: [String: Any] = ["frame": rectJSON(frame)]
392
+ if let title { json["title"] = title }
393
+ if let windowId { json["id"] = windowId }
394
+ windowJSON = json
395
+ }
396
+
397
+ var queue: [(AXUIElement, [Int], Int)] = [(root, [], 0)]
398
+ var cursor = 0
399
+ var seen = Set<CFHashCode>()
400
+ var records: [ElementRecord] = []
401
+ var lines: [String] = []
402
+ var textBytes = 0
403
+ var truncated = false
404
+
405
+ while cursor < queue.count {
406
+ if records.count >= maxNodes { truncated = true; break }
407
+ let (element, locator, depth) = queue[cursor]
408
+ cursor += 1
409
+ let identity = CFHash(element)
410
+ if seen.contains(identity) { continue }
411
+ seen.insert(identity)
412
+ let role = axString(element, kAXRoleAttribute as CFString) ?? "AXUnknown"
413
+ let subrole = axString(element, kAXSubroleAttribute as CFString)
414
+ let nativeIdentifier = axString(element, kAXIdentifierAttribute as CFString)
415
+ let titleValue = axString(element, kAXTitleAttribute as CFString)
416
+ let descriptionValue = axString(element, kAXDescriptionAttribute as CFString)
417
+ let labelValue = titleValue ?? descriptionValue
418
+ let secure = role == "AXSecureTextField" || subrole == "AXSecureTextField"
419
+ let valueValue: String? = secure ? "[secure]" : {
420
+ guard let raw = axCopy(element, kAXValueAttribute as CFString) else { return nil }
421
+ if let string = raw as? String { return sanitize(string) }
422
+ if let number = raw as? NSNumber { return number.stringValue }
423
+ return nil
424
+ }()
425
+ let enabled = axBool(element, kAXEnabledAttribute as CFString)
426
+ let focused = axBool(element, kAXFocusedAttribute as CFString)
427
+ let selected = axBool(element, kAXSelectedAttribute as CFString)
428
+ let elementFrame = axFrame(element)
429
+ let actions = axActions(element)
430
+ let index = records.count
431
+ var json: [String: Any] = [
432
+ "index": index,
433
+ "role": role,
434
+ "actions": actions,
435
+ "locator": locator,
436
+ ]
437
+ if let subrole { json["subrole"] = subrole }
438
+ if let nativeIdentifier { json["nativeIdentifier"] = sanitize(nativeIdentifier) }
439
+ if let titleValue { json["title"] = sanitize(titleValue) }
440
+ if let labelValue { json["label"] = sanitize(labelValue) }
441
+ if let valueValue { json["value"] = valueValue }
442
+ if let enabled { json["enabled"] = enabled }
443
+ if let focused { json["focused"] = focused }
444
+ if let selected { json["selected"] = selected }
445
+ if let elementFrame { json["frame"] = rectJSON(elementFrame) }
446
+ let frameKey = elementFrame.map { "\(Int($0.origin.x)),\(Int($0.origin.y)),\(Int($0.width)),\(Int($0.height))" } ?? ""
447
+ let hashLine = [locator.map(String.init).joined(separator: "."), nativeIdentifier ?? "", role, subrole ?? "", titleValue ?? "", labelValue ?? "", valueValue ?? "", String(enabled ?? true), String(focused ?? false), String(selected ?? false), frameKey, actions.joined(separator: ",")].joined(separator: "|")
448
+ var line = String(repeating: " ", count: min(depth, 20)) + "[\(index)] \(role)"
449
+ if let titleValue { line += " \(jsonString(sanitize(titleValue)))" }
450
+ else if let labelValue { line += " \(jsonString(sanitize(labelValue)))" }
451
+ if let valueValue { line += " value=\(jsonString(valueValue))" }
452
+ if enabled == false { line += " disabled" }
453
+ if focused == true { line += " focused" }
454
+ if selected == true { line += " selected" }
455
+ let lineBytes = line.lengthOfBytes(using: .utf8) + (lines.isEmpty ? 0 : 1)
456
+ if textBytes + lineBytes <= maxTextBytes {
457
+ lines.append(line)
458
+ textBytes += lineBytes
459
+ } else {
460
+ truncated = true
461
+ }
462
+ records.append(ElementRecord(index: index, locator: locator, element: element, json: json, hashLine: hashLine))
463
+ if depth >= maxDepth {
464
+ if !axChildren(element).isEmpty { truncated = true }
465
+ continue
466
+ }
467
+ for (childIndex, child) in axChildren(element).enumerated() {
468
+ queue.append((child, locator + [childIndex], depth + 1))
469
+ }
470
+ }
471
+ let appData = try appJSON(app)
472
+ let windowHash = [
473
+ title ?? "",
474
+ windowId.map(String.init) ?? "",
475
+ frame.map { "\(Int($0.origin.x)),\(Int($0.origin.y)),\(Int($0.width)),\(Int($0.height))" } ?? "",
476
+ ].joined(separator: "|")
477
+ let state = [appData["bundleId"] as? String ?? "", String(app.processIdentifier), windowHash, records.map(\.hashLine).joined(separator: "\n")].joined(separator: "\n")
478
+ return ObservationSnapshot(
479
+ app: app,
480
+ appJSON: appData,
481
+ frontmost: app.isActive,
482
+ window: rootWindow,
483
+ windowJSON: windowJSON,
484
+ stateHash: sha256(state),
485
+ treeText: lines.joined(separator: "\n"),
486
+ truncated: truncated,
487
+ elements: records
488
+ )
489
+ }
490
+
491
+ private func captureWindow(_ snapshot: ObservationSnapshot, path: String, required: Bool) async throws -> [String: Any]? {
492
+ guard CGPreflightScreenCaptureAccess() else {
493
+ if required { throw fail("COMPUTER_PERMISSION_REQUIRED", "macOS Screen Recording permission is required for screenshots") }
494
+ return nil
495
+ }
496
+ let content: SCShareableContent
497
+ do {
498
+ content = try await SCShareableContent.excludingDesktopWindows(false, onScreenWindowsOnly: true)
499
+ } catch {
500
+ if required { throw fail("COMPUTER_PROVIDER_FAILURE", "ScreenCaptureKit could not enumerate windows") }
501
+ return nil
502
+ }
503
+ let expectedId = (snapshot.windowJSON?["id"] as? NSNumber)?.uint32Value
504
+ let expectedFrame = (snapshot.windowJSON?["frame"] as? [String: Any]).flatMap { try? cgRect($0) }
505
+ let windows = content.windows.filter { $0.owningApplication?.processID == snapshot.app.processIdentifier }
506
+ let selected = windows.first { window in
507
+ if let expectedId { return window.windowID == expectedId }
508
+ guard let expectedFrame else { return false }
509
+ let tolerance: CGFloat = 2
510
+ return abs(window.frame.minX - expectedFrame.minX) <= tolerance
511
+ && abs(window.frame.minY - expectedFrame.minY) <= tolerance
512
+ && abs(window.frame.width - expectedFrame.width) <= tolerance
513
+ && abs(window.frame.height - expectedFrame.height) <= tolerance
514
+ }
515
+ guard let selected else {
516
+ if required { throw fail("COMPUTER_TARGET_UNAVAILABLE", "the observed window is not available for capture") }
517
+ return nil
518
+ }
519
+ let filter = SCContentFilter(desktopIndependentWindow: selected)
520
+ let configuration = SCStreamConfiguration()
521
+ let pixelScale = CGFloat(filter.pointPixelScale)
522
+ configuration.width = max(1, Int((selected.frame.width * pixelScale).rounded()))
523
+ configuration.height = max(1, Int((selected.frame.height * pixelScale).rounded()))
524
+ configuration.showsCursor = false
525
+ let image: CGImage
526
+ do {
527
+ image = try await SCScreenshotManager.captureImage(contentFilter: filter, configuration: configuration)
528
+ } catch {
529
+ if required { throw fail("COMPUTER_PROVIDER_FAILURE", "ScreenCaptureKit could not capture the selected window") }
530
+ return nil
531
+ }
532
+ let representation = NSBitmapImageRep(cgImage: image)
533
+ guard let data = representation.representation(using: .png, properties: [:]) else {
534
+ throw fail("COMPUTER_PROVIDER_FAILURE", "captured window could not be encoded as PNG")
535
+ }
536
+ do {
537
+ try data.write(to: URL(fileURLWithPath: path), options: .atomic)
538
+ } catch {
539
+ throw fail("COMPUTER_PROVIDER_FAILURE", "captured PNG could not be written")
540
+ }
541
+ return ["path": path, "width": image.width, "height": image.height]
542
+ }
543
+
544
+ private func observationJSON(_ snapshot: ObservationSnapshot, screenshot: [String: Any]?) -> [String: Any] {
545
+ var result: [String: Any] = [
546
+ "app": snapshot.appJSON,
547
+ "stateHash": snapshot.stateHash,
548
+ "frontmost": snapshot.frontmost,
549
+ "treeText": snapshot.treeText,
550
+ "truncated": snapshot.truncated,
551
+ "elements": snapshot.elements.map(\.json),
552
+ "permissions": [
553
+ "accessibility": permissionAccessibility(),
554
+ "screenRecording": permissionScreenRecording(),
555
+ ],
556
+ ]
557
+ if let window = snapshot.windowJSON { result["window"] = window }
558
+ if let screenshot { result["screenshot"] = screenshot }
559
+ return result
560
+ }
561
+
562
+ private func activate(_ app: NSRunningApplication, timeoutMs: Int) throws {
563
+ _ = app.activate(options: [.activateAllWindows])
564
+ let deadline = DispatchTime.now().uptimeNanoseconds + UInt64(timeoutMs) * 1_000_000
565
+ while !app.isActive {
566
+ if DispatchTime.now().uptimeNanoseconds >= deadline {
567
+ throw fail("COMPUTER_ACTION_BLOCKED", "the selected application did not become frontmost before the action timeout")
568
+ }
569
+ usleep(10_000)
570
+ }
571
+ }
572
+
573
+ private func targetedEventSource() throws -> CGEventSource {
574
+ guard let source = CGEventSource(stateID: .privateState) else {
575
+ throw fail("COMPUTER_ACTION_BLOCKED", "CoreGraphics input event source is unavailable")
576
+ }
577
+ return source
578
+ }
579
+
580
+ private func cgRect(_ frame: [String: Any]) throws -> CGRect {
581
+ CGRect(
582
+ x: try double(frame["x"], "window.x"),
583
+ y: try double(frame["y"], "window.y"),
584
+ width: try double(frame["width"], "window.width"),
585
+ height: try double(frame["height"], "window.height")
586
+ )
587
+ }
588
+
589
+ private func windowAtPoint(app: NSRunningApplication, point: CGPoint) throws -> (windowNumber: Int64, frame: CGRect) {
590
+ guard let windows = CGWindowListCopyWindowInfo(
591
+ [.optionAll, .excludeDesktopElements],
592
+ kCGNullWindowID
593
+ ) as? [[String: Any]] else {
594
+ throw fail("COMPUTER_TARGET_UNAVAILABLE", "CoreGraphics window list is unavailable")
595
+ }
596
+ let appWindows = windows.filter { candidate in
597
+ guard (candidate[kCGWindowOwnerPID as String] as? NSNumber)?.int32Value == app.processIdentifier,
598
+ (candidate[kCGWindowLayer as String] as? NSNumber)?.intValue == 0 else { return false }
599
+ return true
600
+ }
601
+ let containing = appWindows.filter { candidate in
602
+ guard let bounds = candidate[kCGWindowBounds as String] as? [String: Any],
603
+ let frame = CGRect(dictionaryRepresentation: bounds as CFDictionary) else { return false }
604
+ return frame.contains(point)
605
+ }
606
+ // optionAll can recover windows omitted by optionOnScreenOnly on some
607
+ // multi-display layouts. Never route pointer input to a hidden window.
608
+ guard let window = containing.first(where: {
609
+ ($0[kCGWindowIsOnscreen as String] as? NSNumber)?.boolValue == true
610
+ }),
611
+ let windowNumber = (window[kCGWindowNumber as String] as? NSNumber)?.int64Value,
612
+ let bounds = window[kCGWindowBounds as String] as? [String: Any],
613
+ let frame = CGRect(dictionaryRepresentation: bounds as CFDictionary) else {
614
+ if !containing.isEmpty {
615
+ throw fail("COMPUTER_TARGET_UNAVAILABLE", "the selected app window contains the coordinate but CoreGraphics does not mark it on-screen")
616
+ }
617
+ if appWindows.isEmpty {
618
+ throw fail("COMPUTER_TARGET_UNAVAILABLE", "the selected app has no enumerable layer-zero window in CoreGraphics")
619
+ }
620
+ throw fail("COMPUTER_TARGET_UNAVAILABLE", "coordinate is outside the selected app's enumerable windows")
621
+ }
622
+ return (windowNumber, frame)
623
+ }
624
+
625
+ private func pointerTarget(app: NSRunningApplication, window: [String: Any]?, at point: CGPoint) throws -> TargetedPointerTarget {
626
+ if let windowNumber = (window?["id"] as? NSNumber)?.int64Value,
627
+ let frame = window?["frame"] as? [String: Any],
628
+ let windowFrame = try? cgRect(frame),
629
+ windowFrame.contains(point) {
630
+ return TargetedPointerTarget(
631
+ pid: app.processIdentifier,
632
+ windowNumber: windowNumber,
633
+ windowFrame: windowFrame
634
+ )
635
+ }
636
+ let resolved = try windowAtPoint(app: app, point: point)
637
+ return TargetedPointerTarget(
638
+ pid: app.processIdentifier,
639
+ windowNumber: resolved.windowNumber,
640
+ windowFrame: resolved.frame
641
+ )
642
+ }
643
+
644
+ private func pointerAction(_ body: () throws -> Void) throws {
645
+ do {
646
+ try body()
647
+ } catch let error as TargetedPointerError {
648
+ throw fail("COMPUTER_ACTION_BLOCKED", error.message)
649
+ }
650
+ }
651
+
652
+ private func windowPoint(_ action: [String: Any], window: [String: Any]?, xKey: String, yKey: String, coordinateSpace: String = "window") throws -> CGPoint {
653
+ let x = try double(action[xKey], "action.\(xKey)")
654
+ let y = try double(action[yKey], "action.\(yKey)")
655
+ if coordinateSpace == "screen" {
656
+ return CGPoint(x: x, y: y)
657
+ }
658
+ guard coordinateSpace == "window" else {
659
+ throw fail("COMPUTER_PROVIDER_FAILURE", "action coordinateSpace must be one of window, screen")
660
+ }
661
+ guard let frame = window?["frame"] as? [String: Any] else {
662
+ throw fail("COMPUTER_TARGET_UNAVAILABLE", "the observation has no current window coordinate space")
663
+ }
664
+ let width = try double(frame["width"], "window.width")
665
+ let height = try double(frame["height"], "window.height")
666
+ guard x >= 0, y >= 0, x <= width, y <= height else {
667
+ throw fail("COMPUTER_TARGET_UNAVAILABLE", "coordinate is outside the observed window")
668
+ }
669
+ let origin = try cgRect(frame)
670
+ return CGPoint(x: origin.origin.x + x, y: origin.origin.y + y)
671
+ }
672
+
673
+ private func mouseButton(_ value: String?) throws -> CGMouseButton {
674
+ switch value ?? "left" {
675
+ case "left": return .left
676
+ case "right": return .right
677
+ case "middle": return .center
678
+ default: throw fail("COMPUTER_PROVIDER_FAILURE", "unsupported mouse button")
679
+ }
680
+ }
681
+
682
+ private let keyCodes: [String: CGKeyCode] = [
683
+ "a": 0, "s": 1, "d": 2, "f": 3, "h": 4, "g": 5, "z": 6, "x": 7, "c": 8, "v": 9,
684
+ "b": 11, "q": 12, "w": 13, "e": 14, "r": 15, "y": 16, "t": 17, "1": 18, "2": 19,
685
+ "3": 20, "4": 21, "6": 22, "5": 23, "=": 24, "9": 25, "7": 26, "-": 27, "8": 28,
686
+ "0": 29, "]": 30, "o": 31, "u": 32, "[": 33, "i": 34, "p": 35, "return": 36,
687
+ "l": 37, "j": 38, "'": 39, "k": 40, ";": 41, "\\": 42, ",": 43, "/": 44, "n": 45,
688
+ "m": 46, ".": 47, "tab": 48, "space": 49, "delete": 51, "escape": 53,
689
+ "home": 115, "pageup": 116, "forwarddelete": 117, "end": 119, "pagedown": 121,
690
+ "left": 123, "right": 124, "down": 125, "up": 126,
691
+ ]
692
+
693
+ private func flags(_ modifiers: [String]) throws -> CGEventFlags {
694
+ var result: CGEventFlags = []
695
+ for modifier in modifiers {
696
+ switch modifier {
697
+ case "command": result.insert(.maskCommand)
698
+ case "control": result.insert(.maskControl)
699
+ case "option": result.insert(.maskAlternate)
700
+ case "shift": result.insert(.maskShift)
701
+ default: throw fail("COMPUTER_PROVIDER_FAILURE", "unsupported key modifier")
702
+ }
703
+ }
704
+ return result
705
+ }
706
+
707
+ private func pressKey(_ key: String, modifiers: [String], app: NSRunningApplication) throws {
708
+ let normalized = key.lowercased()
709
+ guard let code = keyCodes[normalized] else {
710
+ throw fail("COMPUTER_PROVIDER_FAILURE", "unsupported key; use the documented key vocabulary")
711
+ }
712
+ let source = try targetedEventSource()
713
+ guard let down = CGEvent(keyboardEventSource: source, virtualKey: code, keyDown: true),
714
+ let up = CGEvent(keyboardEventSource: source, virtualKey: code, keyDown: false) else {
715
+ throw fail("COMPUTER_ACTION_BLOCKED", "CoreGraphics could not create keyboard events")
716
+ }
717
+ let eventFlags = try flags(modifiers)
718
+ down.flags = eventFlags
719
+ up.flags = eventFlags
720
+ down.postToPid(app.processIdentifier)
721
+ usleep(5_000)
722
+ up.postToPid(app.processIdentifier)
723
+ }
724
+
725
+ private func focusedElement(_ app: NSRunningApplication) -> AXUIElement? {
726
+ let appElement = AXUIElementCreateApplication(app.processIdentifier)
727
+ guard let focused = axCopy(appElement, kAXFocusedUIElementAttribute as CFString) as AnyObject?,
728
+ CFGetTypeID(focused) == AXUIElementGetTypeID() else { return nil }
729
+ return (focused as! AXUIElement)
730
+ }
731
+
732
+ private func setSelectedText(_ text: String, app: NSRunningApplication) -> Bool {
733
+ guard let focused = focusedElement(app) else { return false }
734
+ return AXUIElementSetAttributeValue(focused, kAXSelectedTextAttribute as CFString, text as CFTypeRef) == .success
735
+ }
736
+
737
+ private func typeTextWithKeyboard(_ text: String, app: NSRunningApplication) throws {
738
+ let source = try targetedEventSource()
739
+ guard let down = CGEvent(keyboardEventSource: source, virtualKey: 0, keyDown: true),
740
+ let up = CGEvent(keyboardEventSource: source, virtualKey: 0, keyDown: false) else {
741
+ throw fail("COMPUTER_ACTION_BLOCKED", "CoreGraphics could not create Unicode keyboard events")
742
+ }
743
+ let units = Array(text.utf16)
744
+ units.withUnsafeBufferPointer { buffer in
745
+ down.keyboardSetUnicodeString(stringLength: units.count, unicodeString: buffer.baseAddress!)
746
+ up.keyboardSetUnicodeString(stringLength: units.count, unicodeString: buffer.baseAddress!)
747
+ }
748
+ down.postToPid(app.processIdentifier)
749
+ usleep(5_000)
750
+ up.postToPid(app.processIdentifier)
751
+ }
752
+
753
+ private func targetRecord(_ request: [String: Any], snapshot: ObservationSnapshot) throws -> ElementRecord? {
754
+ guard let element = request["element"] as? [String: Any] else { return nil }
755
+ let locator = (element["locator"] as? [NSNumber])?.map(\.intValue) ?? []
756
+ guard let record = snapshot.elements.first(where: { $0.locator == locator }) else {
757
+ throw fail("COMPUTER_STALE_OBSERVATION", "the target element locator is no longer present")
758
+ }
759
+ return record
760
+ }
761
+
762
+ private func selectedIdentity(_ source: [String: Any], keys: [String]) -> [AnyHashable: Any] {
763
+ var identity: [AnyHashable: Any] = [:]
764
+ for key in keys {
765
+ if let value = source[key] { identity[key] = value }
766
+ }
767
+ return identity
768
+ }
769
+
770
+ private func targetMatches(_ expected: [String: Any], current: ElementRecord) -> Bool {
771
+ let keys = ["nativeIdentifier", "role", "subrole", "title", "label", "value", "enabled", "selected", "frame", "actions"]
772
+ return NSDictionary(dictionary: selectedIdentity(expected, keys: keys))
773
+ .isEqual(to: selectedIdentity(current.json, keys: keys))
774
+ }
775
+
776
+ private func windowMatches(_ expected: [String: Any]?, current: [String: Any]?) -> Bool {
777
+ guard let expected else { return current == nil }
778
+ guard let current else { return false }
779
+ let keys = ["id", "title", "frame"]
780
+ return NSDictionary(dictionary: selectedIdentity(expected, keys: keys))
781
+ .isEqual(to: selectedIdentity(current, keys: keys))
782
+ }
783
+
784
+ private func activationStateMatches(_ before: ObservationSnapshot, current: ObservationSnapshot) -> Bool {
785
+ guard windowMatches(before.windowJSON, current: current.windowJSON),
786
+ before.elements.count == current.elements.count else { return false }
787
+ return zip(before.elements, current.elements).allSatisfy { expected, actual in
788
+ expected.locator == actual.locator && targetMatches(expected.json, current: actual)
789
+ }
790
+ }
791
+
792
+ private func validateTarget(_ request: [String: Any], snapshot: ObservationSnapshot) throws -> ElementRecord? {
793
+ let record = try targetRecord(request, snapshot: snapshot)
794
+ if let record, let expectedElement = request["element"] as? [String: Any] {
795
+ guard windowMatches(request["window"] as? [String: Any], current: snapshot.windowJSON),
796
+ targetMatches(expectedElement, current: record) else {
797
+ throw fail("COMPUTER_STALE_OBSERVATION", "the target element or window changed after the referenced observation")
798
+ }
799
+ } else {
800
+ let expected = try string(request["expectedStateHash"], "request.expectedStateHash")
801
+ guard snapshot.stateHash == expected else {
802
+ throw fail("COMPUTER_STALE_OBSERVATION", "the application UI changed after the referenced observation")
803
+ }
804
+ }
805
+ return record
806
+ }
807
+
808
+ private func interactionValue(_ interaction: [String: Any], _ key: String, _ allowed: [String]) throws -> String {
809
+ let value = try string(interaction[key], "request.interaction.\(key)")
810
+ guard allowed.contains(value) else {
811
+ throw fail("COMPUTER_PROVIDER_FAILURE", "request.interaction.\(key) must be one of \(allowed.joined(separator: ", "))")
812
+ }
813
+ return value
814
+ }
815
+
816
+ private func requirePointerInput(_ policy: String) throws {
817
+ guard policy == "targeted" else {
818
+ throw fail("COMPUTER_ACTION_BLOCKED", "this action needs target-process pointer input, but interaction.pointerInputPolicy is deny")
819
+ }
820
+ }
821
+
822
+ private func requiresForegroundPermission(_ action: String) -> Bool {
823
+ action == (kAXRaiseAction as String)
824
+ }
825
+
826
+ private func inputContext(
827
+ app: NSRunningApplication,
828
+ request: [String: Any],
829
+ limits: [String: Any],
830
+ snapshot: ObservationSnapshot,
831
+ record: ElementRecord?,
832
+ focusPolicy: String,
833
+ keyboardPolicy: String,
834
+ actionKind: String,
835
+ timeoutMs: Int,
836
+ ) throws -> (snapshot: ObservationSnapshot, record: ElementRecord?, activation: String) {
837
+ let keyboardAction = actionKind == "type-text" || actionKind == "press-key"
838
+ let effectiveFocusPolicy = keyboardAction && keyboardPolicy == "activate" ? "activate" : focusPolicy
839
+ guard effectiveFocusPolicy == "activate" else {
840
+ return (snapshot, record, "not-requested")
841
+ }
842
+ if app.isActive {
843
+ return (snapshot, record, "already-frontmost")
844
+ }
845
+ try activate(app, timeoutMs: timeoutMs)
846
+ let refreshed = try observeSnapshot(app: app, limits: limits)
847
+ let refreshedRecord: ElementRecord?
848
+ if request["element"] != nil {
849
+ refreshedRecord = try validateTarget(request, snapshot: refreshed)
850
+ } else if keyboardAction {
851
+ // Activation may move focus to the app's default control; typing targets
852
+ // the refreshed focused element. The app identity is already revalidated
853
+ // by the fresh observation, so full pre-activation state equality would
854
+ // only make reliable keyboard input fail.
855
+ refreshedRecord = nil
856
+ } else {
857
+ guard activationStateMatches(snapshot, current: refreshed) else {
858
+ throw fail("COMPUTER_STALE_OBSERVATION", "the application UI changed while the target application was activated")
859
+ }
860
+ refreshedRecord = nil
861
+ }
862
+ return (refreshed, refreshedRecord, "activated")
863
+ }
864
+
865
+ private func actionResult(
866
+ channel: String,
867
+ activation: String,
868
+ pointerInput: Bool,
869
+ pointerRouting: String = "none"
870
+ ) -> [String: Any] {
871
+ [
872
+ "channel": channel,
873
+ "activation": activation,
874
+ "pointerInput": pointerInput,
875
+ "pointerRouting": pointerRouting,
876
+ ]
877
+ }
878
+
879
+ private func performAction(
880
+ _ request: [String: Any],
881
+ dragStartBarrier: (() throws -> Void)? = nil
882
+ ) throws -> [String: Any] {
883
+ let appIdentity = try dictionary(request["app"], "request.app")
884
+ let selector: [String: Any] = [
885
+ "bundleId": try string(appIdentity["bundleId"], "app.bundleId"),
886
+ "pid": try int(appIdentity["pid"], "app.pid"),
887
+ ]
888
+ let app = try resolveApp(selector)
889
+ let limits = try dictionary(request["limits"], "request.limits")
890
+ let snapshot = try observeSnapshot(app: app, limits: limits)
891
+ let action = try dictionary(request["action"], "request.action")
892
+ let kind = try string(action["kind"], "action.kind")
893
+ let actionTimeoutMs = try int(request["actionTimeoutMs"], "request.actionTimeoutMs")
894
+ guard actionTimeoutMs >= 1_000 && actionTimeoutMs <= 120_000 else {
895
+ throw fail("COMPUTER_PROVIDER_FAILURE", "request.actionTimeoutMs must be between 1000 and 120000")
896
+ }
897
+ let interaction = try dictionary(request["interaction"], "request.interaction")
898
+ let focusPolicy = try interactionValue(interaction, "focusPolicy", ["preserve", "activate"])
899
+ let keyboardPolicy = try interactionValue(interaction, "keyboardPolicy", ["preserve", "activate"])
900
+ let pointerInputPolicy = try interactionValue(interaction, "pointerInputPolicy", ["deny", "targeted"])
901
+ let record = try validateTarget(request, snapshot: snapshot)
902
+
903
+ switch kind {
904
+ case "click":
905
+ if let record, pressWithDescendantFallback(record.element) {
906
+ return actionResult(channel: "accessibility", activation: "not-requested", pointerInput: false)
907
+ }
908
+ let elementFallback = record != nil && bool(action["allowCoordinateFallback"])
909
+ let coordinateFallback = action["x"] != nil && action["y"] != nil
910
+ guard elementFallback || coordinateFallback else {
911
+ throw fail("COMPUTER_ELEMENT_UNAVAILABLE", "element does not support an actionable AXPress and coordinate fallback was not requested")
912
+ }
913
+ try requirePointerInput(pointerInputPolicy)
914
+ let current = try inputContext(
915
+ app: app,
916
+ request: request,
917
+ limits: limits,
918
+ snapshot: snapshot,
919
+ record: record,
920
+ focusPolicy: focusPolicy,
921
+ keyboardPolicy: keyboardPolicy,
922
+ actionKind: kind,
923
+ timeoutMs: actionTimeoutMs,
924
+ )
925
+ if let record = current.record, elementFallback, let frame = axFrame(record.element) {
926
+ let point = CGPoint(x: frame.midX, y: frame.midY)
927
+ let target = try pointerTarget(app: app, window: current.snapshot.windowJSON, at: point)
928
+ try pointerAction {
929
+ try targetedClick(
930
+ at: point,
931
+ button: try mouseButton(action["button"] as? String),
932
+ count: min(max((action["clickCount"] as? NSNumber)?.intValue ?? 1, 1), 3),
933
+ target: target
934
+ )
935
+ }
936
+ return actionResult(channel: "coordinates", activation: current.activation, pointerInput: true, pointerRouting: "target-process")
937
+ }
938
+ if action["x"] != nil, action["y"] != nil {
939
+ let coordinateSpace = action["coordinateSpace"] as? String ?? "window"
940
+ let point = try windowPoint(action, window: current.snapshot.windowJSON, xKey: "x", yKey: "y", coordinateSpace: coordinateSpace)
941
+ let target = try pointerTarget(app: app, window: current.snapshot.windowJSON, at: point)
942
+ try pointerAction {
943
+ try targetedClick(
944
+ at: point,
945
+ button: try mouseButton(action["button"] as? String),
946
+ count: min(max((action["clickCount"] as? NSNumber)?.intValue ?? 1, 1), 3),
947
+ target: target
948
+ )
949
+ }
950
+ return actionResult(channel: "coordinates", activation: current.activation, pointerInput: true, pointerRouting: "target-process")
951
+ }
952
+ throw fail("COMPUTER_ELEMENT_UNAVAILABLE", "coordinate fallback was requested but no current target coordinates were available")
953
+ case "set-value":
954
+ guard let record else { throw fail("COMPUTER_ELEMENT_UNAVAILABLE", "set-value requires an observed element") }
955
+ let value = try stringAllowingEmpty(action["value"], "action.value")
956
+ let result = AXUIElementSetAttributeValue(record.element, kAXValueAttribute as CFString, value as CFTypeRef)
957
+ guard result == .success else { throw fail("COMPUTER_ACTION_BLOCKED", "Accessibility value assignment was rejected") }
958
+ return actionResult(channel: "accessibility", activation: "not-requested", pointerInput: false)
959
+ case "type-text":
960
+ let text = try string(action["text"], "action.text")
961
+ if setSelectedText(text, app: app) {
962
+ return actionResult(channel: "accessibility", activation: "not-requested", pointerInput: false)
963
+ }
964
+ let current = try inputContext(
965
+ app: app,
966
+ request: request,
967
+ limits: limits,
968
+ snapshot: snapshot,
969
+ record: record,
970
+ focusPolicy: focusPolicy,
971
+ keyboardPolicy: keyboardPolicy,
972
+ actionKind: kind,
973
+ timeoutMs: actionTimeoutMs,
974
+ )
975
+ if current.activation == "activated", setSelectedText(text, app: app) {
976
+ return actionResult(channel: "accessibility", activation: current.activation, pointerInput: false)
977
+ }
978
+ try typeTextWithKeyboard(text, app: app)
979
+ return actionResult(channel: "keyboard", activation: current.activation, pointerInput: false)
980
+ case "press-key":
981
+ let current = try inputContext(
982
+ app: app,
983
+ request: request,
984
+ limits: limits,
985
+ snapshot: snapshot,
986
+ record: record,
987
+ focusPolicy: focusPolicy,
988
+ keyboardPolicy: keyboardPolicy,
989
+ actionKind: kind,
990
+ timeoutMs: actionTimeoutMs,
991
+ )
992
+ let modifiers = action["modifiers"] as? [String] ?? []
993
+ try pressKey(try string(action["key"], "action.key"), modifiers: modifiers, app: app)
994
+ return actionResult(channel: "keyboard", activation: current.activation, pointerInput: false)
995
+ case "scroll":
996
+ try requirePointerInput(pointerInputPolicy)
997
+ let current = try inputContext(
998
+ app: app,
999
+ request: request,
1000
+ limits: limits,
1001
+ snapshot: snapshot,
1002
+ record: record,
1003
+ focusPolicy: focusPolicy,
1004
+ keyboardPolicy: keyboardPolicy,
1005
+ actionKind: kind,
1006
+ timeoutMs: actionTimeoutMs,
1007
+ )
1008
+ let point: CGPoint
1009
+ if let record = current.record, let frame = axFrame(record.element) { point = CGPoint(x: frame.midX, y: frame.midY) }
1010
+ else {
1011
+ let coordinateSpace = action["coordinateSpace"] as? String ?? "window"
1012
+ point = try windowPoint(action, window: current.snapshot.windowJSON, xKey: "x", yKey: "y", coordinateSpace: coordinateSpace)
1013
+ }
1014
+ let direction = try string(action["direction"], "action.direction")
1015
+ let pages = min(max((action["pages"] as? NSNumber)?.intValue ?? 1, 1), 10)
1016
+ let vertical = direction == "up" ? 10 * pages : direction == "down" ? -10 * pages : 0
1017
+ let horizontal = direction == "left" ? 10 * pages : direction == "right" ? -10 * pages : 0
1018
+ guard vertical != 0 || horizontal != 0 else { throw fail("COMPUTER_PROVIDER_FAILURE", "unsupported scroll direction") }
1019
+ let target = try pointerTarget(app: app, window: current.snapshot.windowJSON, at: point)
1020
+ try pointerAction {
1021
+ try targetedScroll(
1022
+ at: point,
1023
+ vertical: Int32(vertical),
1024
+ horizontal: Int32(horizontal),
1025
+ target: target
1026
+ )
1027
+ }
1028
+ return actionResult(channel: "coordinates", activation: current.activation, pointerInput: true, pointerRouting: "target-process")
1029
+ case "drag":
1030
+ try requirePointerInput(pointerInputPolicy)
1031
+ let current = try inputContext(
1032
+ app: app,
1033
+ request: request,
1034
+ limits: limits,
1035
+ snapshot: snapshot,
1036
+ record: record,
1037
+ focusPolicy: focusPolicy,
1038
+ keyboardPolicy: keyboardPolicy,
1039
+ actionKind: kind,
1040
+ timeoutMs: actionTimeoutMs,
1041
+ )
1042
+ let coordinateSpace = action["coordinateSpace"] as? String ?? "window"
1043
+ let from = try windowPoint(action, window: current.snapshot.windowJSON, xKey: "fromX", yKey: "fromY", coordinateSpace: coordinateSpace)
1044
+ let to = try windowPoint(action, window: current.snapshot.windowJSON, xKey: "toX", yKey: "toY", coordinateSpace: coordinateSpace)
1045
+ let cursorSpeed = try double(interaction["cursorSpeedPxPerSecond"], "request.interaction.cursorSpeedPxPerSecond")
1046
+ let cursorAcceleration = try double(
1047
+ interaction["cursorAccelerationPxPerSecondSquared"],
1048
+ "request.interaction.cursorAccelerationPxPerSecondSquared"
1049
+ )
1050
+ guard cursorSpeed.isFinite, cursorSpeed >= 100, cursorSpeed <= 50_000,
1051
+ cursorAcceleration.isFinite, cursorAcceleration >= 100, cursorAcceleration <= 500_000 else {
1052
+ throw fail("COMPUTER_PROVIDER_FAILURE", "request.interaction cursor motion is outside the supported range")
1053
+ }
1054
+ let target = try pointerTarget(app: app, window: current.snapshot.windowJSON, at: from)
1055
+ try dragStartBarrier?()
1056
+ try pointerAction {
1057
+ try targetedDrag(
1058
+ from: from,
1059
+ to: to,
1060
+ target: target,
1061
+ speedPxPerSecond: cursorSpeed,
1062
+ accelerationPxPerSecondSquared: cursorAcceleration
1063
+ )
1064
+ }
1065
+ return actionResult(channel: "coordinates", activation: current.activation, pointerInput: true, pointerRouting: "target-process")
1066
+ case "perform-action":
1067
+ guard let record else { throw fail("COMPUTER_ELEMENT_UNAVAILABLE", "perform-action requires an observed element") }
1068
+ let actionName = try string(action["action"], "action.action")
1069
+ let available = record.json["actions"] as? [String] ?? []
1070
+ guard available.contains(actionName) else { throw fail("COMPUTER_ELEMENT_UNAVAILABLE", "the element did not advertise the requested Accessibility action") }
1071
+ if requiresForegroundPermission(actionName), focusPolicy != "activate" {
1072
+ throw fail("COMPUTER_ACTION_BLOCKED", "the requested Accessibility action may raise the target window, but interaction.focusPolicy is preserve")
1073
+ }
1074
+ let current = requiresForegroundPermission(actionName)
1075
+ ? try inputContext(
1076
+ app: app,
1077
+ request: request,
1078
+ limits: limits,
1079
+ snapshot: snapshot,
1080
+ record: record,
1081
+ focusPolicy: focusPolicy,
1082
+ keyboardPolicy: keyboardPolicy,
1083
+ actionKind: kind,
1084
+ timeoutMs: actionTimeoutMs
1085
+ )
1086
+ : (snapshot, record, "not-requested")
1087
+ guard let currentRecord = current.record else {
1088
+ throw fail("COMPUTER_ELEMENT_UNAVAILABLE", "perform-action target is unavailable after foreground validation")
1089
+ }
1090
+ let result = AXUIElementPerformAction(currentRecord.element, actionName as CFString)
1091
+ guard result == .success else { throw fail("COMPUTER_ACTION_BLOCKED", "Accessibility action was rejected") }
1092
+ return actionResult(channel: "accessibility", activation: current.activation, pointerInput: false)
1093
+ default:
1094
+ throw fail("COMPUTER_PROVIDER_FAILURE", "unsupported action kind")
1095
+ }
1096
+ }
1097
+
1098
+ private func handle(
1099
+ _ request: [String: Any],
1100
+ dragStartBarrier: (() throws -> Void)? = nil
1101
+ ) async throws -> Any {
1102
+ guard (request["protocolVersion"] as? NSNumber)?.intValue == 1 else {
1103
+ throw fail("COMPUTER_PROVIDER_FAILURE", "unsupported helper protocol version")
1104
+ }
1105
+ let command = try string(request["command"], "command")
1106
+ switch command {
1107
+ case "health":
1108
+ return [
1109
+ "helperVersion": helperVersion,
1110
+ "accessibility": permissionAccessibility(),
1111
+ "screenRecording": permissionScreenRecording(),
1112
+ ]
1113
+ case "list-apps":
1114
+ return try runningApps().map { app -> [String: Any] in
1115
+ var json = try appJSON(app)
1116
+ json["frontmost"] = app.isActive
1117
+ json["accessibility"] = permissionAccessibility()
1118
+ json["screenRecording"] = permissionScreenRecording()
1119
+ return json
1120
+ }
1121
+ case "resolve-app":
1122
+ return try appJSON(resolveApp(try dictionary(request["selector"], "selector")))
1123
+ case "open-settings":
1124
+ let kind = try string(request["kind"], "kind")
1125
+ let target: String
1126
+ switch kind {
1127
+ case "accessibility":
1128
+ target = "x-apple.systempreferences:com.apple.preference.security?Privacy_Accessibility"
1129
+ case "screen-recording":
1130
+ target = "x-apple.systempreferences:com.apple.preference.security?Privacy_ScreenCapture"
1131
+ default:
1132
+ throw fail("COMPUTER_PROVIDER_FAILURE", "unknown permission Settings pane")
1133
+ }
1134
+ guard let url = URL(string: target), NSWorkspace.shared.open(url) else {
1135
+ throw fail("COMPUTER_ACTION_BLOCKED", "macOS Settings could not be opened")
1136
+ }
1137
+ return NSNull()
1138
+ case "observe":
1139
+ let appIdentity = try dictionary(request["app"], "app")
1140
+ let app = try resolveApp([
1141
+ "bundleId": try string(appIdentity["bundleId"], "app.bundleId"),
1142
+ "pid": try int(appIdentity["pid"], "app.pid"),
1143
+ ])
1144
+ let options = try dictionary(request["options"], "options")
1145
+ let snapshot = try observeSnapshot(app: app, limits: options)
1146
+ let mode = options["screenshot"] as? String ?? "optional"
1147
+ var screenshot: [String: Any]?
1148
+ if mode != "none" {
1149
+ let path = try string(options["screenshotPath"], "options.screenshotPath")
1150
+ screenshot = try await captureWindow(snapshot, path: path, required: mode == "required")
1151
+ }
1152
+ return observationJSON(snapshot, screenshot: screenshot)
1153
+ case "activate-for-cursor":
1154
+ let appIdentity = try dictionary(request["app"], "app")
1155
+ let app = try resolveApp([
1156
+ "bundleId": try string(appIdentity["bundleId"], "app.bundleId"),
1157
+ "pid": try int(appIdentity["pid"], "app.pid"),
1158
+ ])
1159
+ let options = try dictionary(request["options"], "options")
1160
+ let snapshot = try observeSnapshot(app: app, limits: options)
1161
+ let expected = try string(request["expectedStateHash"], "expectedStateHash")
1162
+ guard snapshot.stateHash == expected else {
1163
+ throw fail("COMPUTER_STALE_OBSERVATION", "the application UI changed before foreground activation")
1164
+ }
1165
+ if app.isActive {
1166
+ return ["observation": observationJSON(snapshot, screenshot: nil), "activation": "already-frontmost"]
1167
+ }
1168
+ let actionTimeoutMs = try int(request["actionTimeoutMs"], "actionTimeoutMs")
1169
+ guard actionTimeoutMs >= 1_000 && actionTimeoutMs <= 120_000 else {
1170
+ throw fail("COMPUTER_PROVIDER_FAILURE", "actionTimeoutMs must be between 1000 and 120000")
1171
+ }
1172
+ try activate(app, timeoutMs: actionTimeoutMs)
1173
+ let refreshed = try observeSnapshot(app: app, limits: options)
1174
+ guard activationStateMatches(snapshot, current: refreshed) else {
1175
+ throw fail("COMPUTER_STALE_OBSERVATION", "the application UI changed while the target application was activated")
1176
+ }
1177
+ return ["observation": observationJSON(refreshed, screenshot: nil), "activation": "activated"]
1178
+ case "act":
1179
+ return try performAction(
1180
+ try dictionary(request["request"], "request"),
1181
+ dragStartBarrier: dragStartBarrier
1182
+ )
1183
+ default:
1184
+ throw fail("COMPUTER_PROVIDER_FAILURE", "unknown helper command")
1185
+ }
1186
+ }
1187
+
1188
+ private func emit(_ payload: [String: Any], exitCode: Int32) -> Never {
1189
+ let data = (try? JSONSerialization.data(withJSONObject: payload, options: [.sortedKeys])) ?? Data("{\"ok\":false,\"error\":{\"code\":\"COMPUTER_PROVIDER_FAILURE\",\"message\":\"response serialization failed\"}}".utf8)
1190
+ FileHandle.standardOutput.write(data)
1191
+ FileHandle.standardOutput.write(Data("\n".utf8))
1192
+ Darwin.exit(exitCode)
1193
+ }
1194
+
1195
+ private func emitFrame(_ payload: [String: Any]) {
1196
+ let data = (try? JSONSerialization.data(withJSONObject: payload, options: [.sortedKeys]))
1197
+ ?? Data("{\"ok\":false,\"error\":{\"code\":\"COMPUTER_PROVIDER_FAILURE\",\"message\":\"response serialization failed\"}}".utf8)
1198
+ FileHandle.standardOutput.write(data)
1199
+ FileHandle.standardOutput.write(Data("\n".utf8))
1200
+ }
1201
+
1202
+ @main
1203
+ private struct HelperMain {
1204
+ @MainActor
1205
+ static func main() async {
1206
+ do {
1207
+ _ = NSApplication.shared
1208
+ NSApplication.shared.setActivationPolicy(.prohibited)
1209
+ try requireHostTransport()
1210
+ if ProcessInfo.processInfo.arguments.contains("--cursor-overlay") {
1211
+ CursorOverlayRuntime.run()
1212
+ return
1213
+ }
1214
+ if ProcessInfo.processInfo.arguments.contains("--drag-action") {
1215
+ guard let line = readLine(),
1216
+ let data = line.data(using: .utf8),
1217
+ let request = try JSONSerialization.jsonObject(with: data) as? [String: Any],
1218
+ (request["command"] as? String) == "act",
1219
+ let actionRequest = request["request"] as? [String: Any],
1220
+ let action = actionRequest["action"] as? [String: Any],
1221
+ (action["kind"] as? String) == "drag" else {
1222
+ throw fail("COMPUTER_PROVIDER_FAILURE", "drag helper stdin must contain one drag action request")
1223
+ }
1224
+ let value = try await handle(request, dragStartBarrier: {
1225
+ emitFrame(["ok": true, "event": "drag-ready"])
1226
+ guard readLine() == "START" else {
1227
+ throw fail("COMPUTER_CANCELLED", "drag action was cancelled before native mouse-down")
1228
+ }
1229
+ })
1230
+ emit(["ok": true, "value": value], exitCode: 0)
1231
+ }
1232
+ guard let data = try FileHandle.standardInput.readToEnd(), !data.isEmpty,
1233
+ let request = try JSONSerialization.jsonObject(with: data) as? [String: Any] else {
1234
+ throw fail("COMPUTER_PROVIDER_FAILURE", "stdin must contain one JSON request")
1235
+ }
1236
+ let value = try await handle(request)
1237
+ emit(["ok": true, "value": value], exitCode: 0)
1238
+ } catch let error as HelperError {
1239
+ emit(["ok": false, "error": ["code": error.code, "message": error.message]], exitCode: 2)
1240
+ } catch {
1241
+ emit(["ok": false, "error": ["code": "COMPUTER_PROVIDER_FAILURE", "message": String(describing: error).prefix(1000).description]], exitCode: 2)
1242
+ }
1243
+ }
1244
+ }