@namzu/sdk 45.1.0 → 48.0.0

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 (767) hide show
  1. package/CHANGELOG.md +436 -8
  2. package/README.md +7 -8
  3. package/dist/advisory/history.d.ts.map +1 -1
  4. package/dist/advisory/history.js +21 -1
  5. package/dist/advisory/history.js.map +1 -1
  6. package/dist/advisory/index.d.ts +1 -1
  7. package/dist/advisory/index.d.ts.map +1 -1
  8. package/dist/advisory/index.js +1 -1
  9. package/dist/advisory/index.js.map +1 -1
  10. package/dist/advisory/registry.d.ts +17 -0
  11. package/dist/advisory/registry.d.ts.map +1 -1
  12. package/dist/advisory/registry.js +26 -0
  13. package/dist/advisory/registry.js.map +1 -1
  14. package/dist/agents/PipelineAgent.d.ts +2 -46
  15. package/dist/agents/PipelineAgent.d.ts.map +1 -1
  16. package/dist/agents/PipelineAgent.js +2 -242
  17. package/dist/agents/PipelineAgent.js.map +1 -1
  18. package/dist/agents/QueryAgent.d.ts +21 -0
  19. package/dist/agents/QueryAgent.d.ts.map +1 -0
  20. package/dist/agents/QueryAgent.js +214 -0
  21. package/dist/agents/QueryAgent.js.map +1 -0
  22. package/dist/agents/ReactiveAgent.d.ts +4 -16
  23. package/dist/agents/ReactiveAgent.d.ts.map +1 -1
  24. package/dist/agents/ReactiveAgent.js +4 -183
  25. package/dist/agents/ReactiveAgent.js.map +1 -1
  26. package/dist/agents/RouterAgent.d.ts +2 -20
  27. package/dist/agents/RouterAgent.d.ts.map +1 -1
  28. package/dist/agents/RouterAgent.js +2 -266
  29. package/dist/agents/RouterAgent.js.map +1 -1
  30. package/dist/agents/SupervisorAgent.d.ts +3 -43
  31. package/dist/agents/SupervisorAgent.d.ts.map +1 -1
  32. package/dist/agents/SupervisorAgent.js +3 -418
  33. package/dist/agents/SupervisorAgent.js.map +1 -1
  34. package/dist/agents/defineAgent.d.ts +2 -1
  35. package/dist/agents/defineAgent.d.ts.map +1 -1
  36. package/dist/agents/defineAgent.js +6 -1
  37. package/dist/agents/defineAgent.js.map +1 -1
  38. package/dist/agents/examples/PipelineAgent.d.ts +46 -0
  39. package/dist/agents/examples/PipelineAgent.d.ts.map +1 -0
  40. package/dist/agents/examples/PipelineAgent.js +242 -0
  41. package/dist/agents/examples/PipelineAgent.js.map +1 -0
  42. package/dist/agents/examples/RouterAgent.d.ts +22 -0
  43. package/dist/agents/examples/RouterAgent.d.ts.map +1 -0
  44. package/dist/agents/examples/RouterAgent.js +268 -0
  45. package/dist/agents/examples/RouterAgent.js.map +1 -0
  46. package/dist/agents/examples/SupervisorAgent.d.ts +45 -0
  47. package/dist/agents/examples/SupervisorAgent.d.ts.map +1 -0
  48. package/dist/agents/examples/SupervisorAgent.js +406 -0
  49. package/dist/agents/examples/SupervisorAgent.js.map +1 -0
  50. package/dist/agents/explore.d.ts +6 -3
  51. package/dist/agents/explore.d.ts.map +1 -1
  52. package/dist/agents/explore.js +6 -3
  53. package/dist/agents/explore.js.map +1 -1
  54. package/dist/agents/forward-options.d.ts +11 -0
  55. package/dist/agents/forward-options.d.ts.map +1 -0
  56. package/dist/agents/forward-options.js +15 -0
  57. package/dist/agents/forward-options.js.map +1 -0
  58. package/dist/agents/index.d.ts +1 -0
  59. package/dist/agents/index.d.ts.map +1 -1
  60. package/dist/agents/index.js +2 -0
  61. package/dist/agents/index.js.map +1 -1
  62. package/dist/agents/runAgent.d.ts +19 -8
  63. package/dist/agents/runAgent.d.ts.map +1 -1
  64. package/dist/agents/runAgent.js +99 -29
  65. package/dist/agents/runAgent.js.map +1 -1
  66. package/dist/authorization/command-line.d.ts +2 -0
  67. package/dist/authorization/command-line.d.ts.map +1 -1
  68. package/dist/authorization/command-line.js +2 -2
  69. package/dist/authorization/command-line.js.map +1 -1
  70. package/dist/authorization/gate.d.ts +13 -2
  71. package/dist/authorization/gate.d.ts.map +1 -1
  72. package/dist/authorization/gate.js +37 -5
  73. package/dist/authorization/gate.js.map +1 -1
  74. package/dist/authorization/program.d.ts +151 -0
  75. package/dist/authorization/program.d.ts.map +1 -0
  76. package/dist/authorization/program.js +713 -0
  77. package/dist/authorization/program.js.map +1 -0
  78. package/dist/authorization/reexec-wrapper.d.ts +106 -0
  79. package/dist/authorization/reexec-wrapper.d.ts.map +1 -0
  80. package/dist/authorization/reexec-wrapper.js +532 -0
  81. package/dist/authorization/reexec-wrapper.js.map +1 -0
  82. package/dist/authorization/rules.d.ts +9 -0
  83. package/dist/authorization/rules.d.ts.map +1 -1
  84. package/dist/authorization/rules.js +27 -1
  85. package/dist/authorization/rules.js.map +1 -1
  86. package/dist/authorization/shell-lexer.d.ts +39 -0
  87. package/dist/authorization/shell-lexer.d.ts.map +1 -1
  88. package/dist/authorization/shell-lexer.js +113 -69
  89. package/dist/authorization/shell-lexer.js.map +1 -1
  90. package/dist/bridge/sse/mapper.d.ts.map +1 -1
  91. package/dist/bridge/sse/mapper.js +6 -0
  92. package/dist/bridge/sse/mapper.js.map +1 -1
  93. package/dist/capabilities/index.d.ts +54 -0
  94. package/dist/capabilities/index.d.ts.map +1 -0
  95. package/dist/capabilities/index.js +92 -0
  96. package/dist/capabilities/index.js.map +1 -0
  97. package/dist/config/registry.d.ts +2 -1
  98. package/dist/config/registry.d.ts.map +1 -1
  99. package/dist/config/registry.js +3 -2
  100. package/dist/config/registry.js.map +1 -1
  101. package/dist/connector/index.d.ts +4 -4
  102. package/dist/connector/index.d.ts.map +1 -1
  103. package/dist/connector/index.js +3 -3
  104. package/dist/connector/index.js.map +1 -1
  105. package/dist/connector/mcp/adapter.d.ts +16 -1
  106. package/dist/connector/mcp/adapter.d.ts.map +1 -1
  107. package/dist/connector/mcp/adapter.js +60 -4
  108. package/dist/connector/mcp/adapter.js.map +1 -1
  109. package/dist/connector/mcp/client.d.ts +13 -4
  110. package/dist/connector/mcp/client.d.ts.map +1 -1
  111. package/dist/connector/mcp/client.js +20 -3
  112. package/dist/connector/mcp/client.js.map +1 -1
  113. package/dist/connector/mcp/discovery.d.ts +24 -1
  114. package/dist/connector/mcp/discovery.d.ts.map +1 -1
  115. package/dist/connector/mcp/discovery.js +44 -0
  116. package/dist/connector/mcp/discovery.js.map +1 -1
  117. package/dist/connector/mcp/index.d.ts +2 -0
  118. package/dist/connector/mcp/index.d.ts.map +1 -1
  119. package/dist/connector/mcp/index.js +11 -0
  120. package/dist/connector/mcp/index.js.map +1 -1
  121. package/dist/connector/mcp/mcp-toolset.d.ts +133 -0
  122. package/dist/connector/mcp/mcp-toolset.d.ts.map +1 -0
  123. package/dist/connector/mcp/mcp-toolset.js +399 -0
  124. package/dist/connector/mcp/mcp-toolset.js.map +1 -0
  125. package/dist/connector/tools/index.d.ts +2 -2
  126. package/dist/connector/tools/index.d.ts.map +1 -1
  127. package/dist/connector/tools/index.js +1 -1
  128. package/dist/connector/tools/index.js.map +1 -1
  129. package/dist/connector/tools/router.d.ts +15 -13
  130. package/dist/connector/tools/router.d.ts.map +1 -1
  131. package/dist/connector/tools/router.js +30 -51
  132. package/dist/connector/tools/router.js.map +1 -1
  133. package/dist/contracts/a2a.d.ts +2 -2
  134. package/dist/directory/derive-supervisor.d.ts +2 -2
  135. package/dist/directory/derive-supervisor.d.ts.map +1 -1
  136. package/dist/directory/derive-supervisor.js +6 -7
  137. package/dist/directory/derive-supervisor.js.map +1 -1
  138. package/dist/directory/derive.d.ts.map +1 -1
  139. package/dist/directory/derive.js +5 -5
  140. package/dist/directory/derive.js.map +1 -1
  141. package/dist/execution/code-runtime/types.d.ts +2 -2
  142. package/dist/execution/code-runtime/types.js +1 -1
  143. package/dist/invariants/index.d.ts +2 -1
  144. package/dist/invariants/index.d.ts.map +1 -1
  145. package/dist/invariants/index.js +3 -2
  146. package/dist/invariants/index.js.map +1 -1
  147. package/dist/manager/agent/lifecycle.d.ts.map +1 -1
  148. package/dist/manager/agent/lifecycle.js +9 -0
  149. package/dist/manager/agent/lifecycle.js.map +1 -1
  150. package/dist/manager/connector/environment.d.ts +6 -0
  151. package/dist/manager/connector/environment.d.ts.map +1 -1
  152. package/dist/manager/connector/environment.js +11 -4
  153. package/dist/manager/connector/environment.js.map +1 -1
  154. package/dist/manager/connector/index.d.ts +2 -2
  155. package/dist/manager/connector/index.d.ts.map +1 -1
  156. package/dist/manager/connector/index.js +2 -2
  157. package/dist/manager/connector/index.js.map +1 -1
  158. package/dist/manager/connector/tenant.d.ts +6 -0
  159. package/dist/manager/connector/tenant.d.ts.map +1 -1
  160. package/dist/manager/connector/tenant.js +11 -4
  161. package/dist/manager/connector/tenant.js.map +1 -1
  162. package/dist/manager/index.d.ts +2 -2
  163. package/dist/manager/index.d.ts.map +1 -1
  164. package/dist/manager/index.js +2 -2
  165. package/dist/manager/index.js.map +1 -1
  166. package/dist/manager/resident/learning-observation.d.ts +4 -4
  167. package/dist/manager/resident/learning.d.ts +36 -36
  168. package/dist/manager/session/attribution.d.ts +22 -0
  169. package/dist/manager/session/attribution.d.ts.map +1 -0
  170. package/dist/manager/session/attribution.js +64 -0
  171. package/dist/manager/session/attribution.js.map +1 -0
  172. package/dist/manager/session/turn-recorder.d.ts +14 -0
  173. package/dist/manager/session/turn-recorder.d.ts.map +1 -1
  174. package/dist/manager/session/turn-recorder.js +208 -55
  175. package/dist/manager/session/turn-recorder.js.map +1 -1
  176. package/dist/peers/address.d.ts +36 -0
  177. package/dist/peers/address.d.ts.map +1 -0
  178. package/dist/peers/address.js +52 -0
  179. package/dist/peers/address.js.map +1 -0
  180. package/dist/peers/client.d.ts +47 -0
  181. package/dist/peers/client.d.ts.map +1 -0
  182. package/dist/peers/client.js +132 -0
  183. package/dist/peers/client.js.map +1 -0
  184. package/dist/peers/dir.d.ts +88 -0
  185. package/dist/peers/dir.d.ts.map +1 -0
  186. package/dist/peers/dir.js +139 -0
  187. package/dist/peers/dir.js.map +1 -0
  188. package/dist/peers/endpoint.d.ts +144 -0
  189. package/dist/peers/endpoint.d.ts.map +1 -0
  190. package/dist/peers/endpoint.js +420 -0
  191. package/dist/peers/endpoint.js.map +1 -0
  192. package/dist/peers/envelope.d.ts +42 -0
  193. package/dist/peers/envelope.d.ts.map +1 -0
  194. package/dist/peers/envelope.js +110 -0
  195. package/dist/peers/envelope.js.map +1 -0
  196. package/dist/peers/index.d.ts +30 -0
  197. package/dist/peers/index.d.ts.map +1 -0
  198. package/dist/peers/index.js +22 -0
  199. package/dist/peers/index.js.map +1 -0
  200. package/dist/peers/protocol.d.ts +645 -0
  201. package/dist/peers/protocol.d.ts.map +1 -0
  202. package/dist/peers/protocol.js +151 -0
  203. package/dist/peers/protocol.js.map +1 -0
  204. package/dist/peers/record.d.ts +71 -0
  205. package/dist/peers/record.d.ts.map +1 -0
  206. package/dist/peers/record.js +45 -0
  207. package/dist/peers/record.js.map +1 -0
  208. package/dist/peers/registry.d.ts +54 -0
  209. package/dist/peers/registry.d.ts.map +1 -0
  210. package/dist/peers/registry.js +213 -0
  211. package/dist/peers/registry.js.map +1 -0
  212. package/dist/plugin/define.d.ts +24 -0
  213. package/dist/plugin/define.d.ts.map +1 -0
  214. package/dist/plugin/define.js +42 -0
  215. package/dist/plugin/define.js.map +1 -0
  216. package/dist/plugin/index.d.ts +2 -0
  217. package/dist/plugin/index.d.ts.map +1 -1
  218. package/dist/plugin/index.js +1 -0
  219. package/dist/plugin/index.js.map +1 -1
  220. package/dist/plugin/lifecycle.d.ts +27 -3
  221. package/dist/plugin/lifecycle.d.ts.map +1 -1
  222. package/dist/plugin/lifecycle.js +242 -92
  223. package/dist/plugin/lifecycle.js.map +1 -1
  224. package/dist/plugin/resolver.d.ts +2 -2
  225. package/dist/plugin/resolver.d.ts.map +1 -1
  226. package/dist/plugin/resolver.js.map +1 -1
  227. package/dist/plugin/shell-hook.d.ts.map +1 -1
  228. package/dist/plugin/shell-hook.js +17 -3
  229. package/dist/plugin/shell-hook.js.map +1 -1
  230. package/dist/pricing/catalogue.generated.d.ts.map +1 -1
  231. package/dist/pricing/catalogue.generated.js +28 -4
  232. package/dist/pricing/catalogue.generated.js.map +1 -1
  233. package/dist/probe/errors.d.ts +2 -1
  234. package/dist/probe/errors.d.ts.map +1 -1
  235. package/dist/probe/errors.js +3 -2
  236. package/dist/probe/errors.js.map +1 -1
  237. package/dist/prompt/coding-agent-doctrine.d.ts +18 -7
  238. package/dist/prompt/coding-agent-doctrine.d.ts.map +1 -1
  239. package/dist/prompt/coding-agent-doctrine.js +11 -6
  240. package/dist/prompt/coding-agent-doctrine.js.map +1 -1
  241. package/dist/prompt/contributions.d.ts +4 -1
  242. package/dist/prompt/contributions.d.ts.map +1 -1
  243. package/dist/prompt/contributions.js +7 -2
  244. package/dist/prompt/contributions.js.map +1 -1
  245. package/dist/prompt/index.d.ts +1 -1
  246. package/dist/prompt/index.d.ts.map +1 -1
  247. package/dist/prompt/index.js +1 -1
  248. package/dist/prompt/index.js.map +1 -1
  249. package/dist/provider/collect-chat-completion.d.ts +6 -1
  250. package/dist/provider/collect-chat-completion.d.ts.map +1 -1
  251. package/dist/provider/collect-chat-completion.js +88 -10
  252. package/dist/provider/collect-chat-completion.js.map +1 -1
  253. package/dist/provider/mock.d.ts.map +1 -1
  254. package/dist/provider/mock.js +23 -6
  255. package/dist/provider/mock.js.map +1 -1
  256. package/dist/provider/registry.d.ts +2 -1
  257. package/dist/provider/registry.d.ts.map +1 -1
  258. package/dist/provider/registry.js +3 -2
  259. package/dist/provider/registry.js.map +1 -1
  260. package/dist/provider/tool-call-framing.d.ts +164 -0
  261. package/dist/provider/tool-call-framing.d.ts.map +1 -0
  262. package/dist/provider/tool-call-framing.js +163 -0
  263. package/dist/provider/tool-call-framing.js.map +1 -0
  264. package/dist/public-runtime.d.ts +27 -14
  265. package/dist/public-runtime.d.ts.map +1 -1
  266. package/dist/public-runtime.js +49 -11
  267. package/dist/public-runtime.js.map +1 -1
  268. package/dist/public-tools.d.ts +9 -6
  269. package/dist/public-tools.d.ts.map +1 -1
  270. package/dist/public-tools.js +13 -5
  271. package/dist/public-tools.js.map +1 -1
  272. package/dist/public-types.d.ts +16 -5
  273. package/dist/public-types.d.ts.map +1 -1
  274. package/dist/read-model/registry.d.ts +2 -1
  275. package/dist/read-model/registry.d.ts.map +1 -1
  276. package/dist/read-model/registry.js +3 -2
  277. package/dist/read-model/registry.js.map +1 -1
  278. package/dist/registry/ManagedRegistry.d.ts +30 -0
  279. package/dist/registry/ManagedRegistry.d.ts.map +1 -1
  280. package/dist/registry/ManagedRegistry.js +45 -6
  281. package/dist/registry/ManagedRegistry.js.map +1 -1
  282. package/dist/registry/collision.d.ts +57 -0
  283. package/dist/registry/collision.d.ts.map +1 -0
  284. package/dist/registry/collision.js +41 -0
  285. package/dist/registry/collision.js.map +1 -0
  286. package/dist/registry/command/index.d.ts +5 -3
  287. package/dist/registry/command/index.d.ts.map +1 -1
  288. package/dist/registry/command/index.js +6 -4
  289. package/dist/registry/command/index.js.map +1 -1
  290. package/dist/registry/index.d.ts +3 -4
  291. package/dist/registry/index.d.ts.map +1 -1
  292. package/dist/registry/index.js +2 -2
  293. package/dist/registry/index.js.map +1 -1
  294. package/dist/registry/plugin/index.d.ts.map +1 -1
  295. package/dist/registry/plugin/index.js +14 -1
  296. package/dist/registry/plugin/index.js.map +1 -1
  297. package/dist/registry/tool/callable.d.ts +22 -0
  298. package/dist/registry/tool/callable.d.ts.map +1 -0
  299. package/dist/registry/tool/callable.js +28 -0
  300. package/dist/registry/tool/callable.js.map +1 -0
  301. package/dist/registry/tool/execute.d.ts +14 -77
  302. package/dist/registry/tool/execute.d.ts.map +1 -1
  303. package/dist/registry/tool/execute.js +15 -720
  304. package/dist/registry/tool/execute.js.map +1 -1
  305. package/dist/registry/tool/portable.d.ts +25 -0
  306. package/dist/registry/tool/portable.d.ts.map +1 -1
  307. package/dist/registry/tool/portable.js +50 -0
  308. package/dist/registry/tool/portable.js.map +1 -1
  309. package/dist/registry/tool/presentation.d.ts +6 -5
  310. package/dist/registry/tool/presentation.d.ts.map +1 -1
  311. package/dist/registry/tool/presentation.js +3 -3
  312. package/dist/registry/tool/presentation.js.map +1 -1
  313. package/dist/runtime/bidi/session.d.ts +5 -4
  314. package/dist/runtime/bidi/session.d.ts.map +1 -1
  315. package/dist/runtime/bidi/session.js +18 -1
  316. package/dist/runtime/bidi/session.js.map +1 -1
  317. package/dist/runtime/query/executor/tool-call-admission.d.ts +82 -13
  318. package/dist/runtime/query/executor/tool-call-admission.d.ts.map +1 -1
  319. package/dist/runtime/query/executor/tool-call-admission.js +227 -17
  320. package/dist/runtime/query/executor/tool-call-admission.js.map +1 -1
  321. package/dist/runtime/query/executor.d.ts +37 -5
  322. package/dist/runtime/query/executor.d.ts.map +1 -1
  323. package/dist/runtime/query/executor.js +103 -14
  324. package/dist/runtime/query/executor.js.map +1 -1
  325. package/dist/runtime/query/file-evidence-replay.d.ts +1 -1
  326. package/dist/runtime/query/file-evidence-replay.d.ts.map +1 -1
  327. package/dist/runtime/query/file-evidence-replay.js +8 -7
  328. package/dist/runtime/query/file-evidence-replay.js.map +1 -1
  329. package/dist/runtime/query/fork/prepare.d.ts +4 -1
  330. package/dist/runtime/query/fork/prepare.d.ts.map +1 -1
  331. package/dist/runtime/query/fork/prepare.js +4 -0
  332. package/dist/runtime/query/fork/prepare.js.map +1 -1
  333. package/dist/runtime/query/guardrail-presets.d.ts +4 -3
  334. package/dist/runtime/query/guardrail-presets.d.ts.map +1 -1
  335. package/dist/runtime/query/guardrail-presets.js +4 -3
  336. package/dist/runtime/query/guardrail-presets.js.map +1 -1
  337. package/dist/runtime/query/index.d.ts +23 -10
  338. package/dist/runtime/query/index.d.ts.map +1 -1
  339. package/dist/runtime/query/index.js +149 -70
  340. package/dist/runtime/query/index.js.map +1 -1
  341. package/dist/runtime/query/iteration/index.d.ts.map +1 -1
  342. package/dist/runtime/query/iteration/index.js +8 -1
  343. package/dist/runtime/query/iteration/index.js.map +1 -1
  344. package/dist/runtime/query/iteration/phases/compaction.js +1 -1
  345. package/dist/runtime/query/iteration/phases/compaction.js.map +1 -1
  346. package/dist/runtime/query/iteration/phases/context.d.ts +2 -2
  347. package/dist/runtime/query/iteration/phases/context.d.ts.map +1 -1
  348. package/dist/runtime/query/iteration/phases/tool-review.d.ts.map +1 -1
  349. package/dist/runtime/query/iteration/phases/tool-review.js +55 -2
  350. package/dist/runtime/query/iteration/phases/tool-review.js.map +1 -1
  351. package/dist/runtime/query/iteration/stream-turn.d.ts.map +1 -1
  352. package/dist/runtime/query/iteration/stream-turn.js +342 -116
  353. package/dist/runtime/query/iteration/stream-turn.js.map +1 -1
  354. package/dist/runtime/query/iteration/tool-input.d.ts +79 -0
  355. package/dist/runtime/query/iteration/tool-input.d.ts.map +1 -0
  356. package/dist/runtime/query/iteration/tool-input.js +291 -0
  357. package/dist/runtime/query/iteration/tool-input.js.map +1 -0
  358. package/dist/runtime/query/observation-context.d.ts +2 -2
  359. package/dist/runtime/query/observation-context.d.ts.map +1 -1
  360. package/dist/runtime/query/observation-context.js.map +1 -1
  361. package/dist/runtime/query/prelude-lease.d.ts +18 -0
  362. package/dist/runtime/query/prelude-lease.d.ts.map +1 -0
  363. package/dist/runtime/query/prelude-lease.js +121 -0
  364. package/dist/runtime/query/prelude-lease.js.map +1 -0
  365. package/dist/runtime/query/prepare-turn.d.ts.map +1 -1
  366. package/dist/runtime/query/prepare-turn.js +386 -187
  367. package/dist/runtime/query/prepare-turn.js.map +1 -1
  368. package/dist/runtime/query/prompt-cache.d.ts +2 -2
  369. package/dist/runtime/query/prompt-cache.d.ts.map +1 -1
  370. package/dist/runtime/query/prompt.d.ts +2 -2
  371. package/dist/runtime/query/prompt.d.ts.map +1 -1
  372. package/dist/runtime/query/prompt.js +1 -1
  373. package/dist/runtime/query/prompt.js.map +1 -1
  374. package/dist/runtime/query/resume-session.d.ts.map +1 -1
  375. package/dist/runtime/query/resume-session.js +37 -2
  376. package/dist/runtime/query/resume-session.js.map +1 -1
  377. package/dist/runtime/query/review-policy.d.ts +65 -3
  378. package/dist/runtime/query/review-policy.d.ts.map +1 -1
  379. package/dist/runtime/query/review-policy.js +143 -22
  380. package/dist/runtime/query/review-policy.js.map +1 -1
  381. package/dist/runtime/query/tooling.d.ts +3 -2
  382. package/dist/runtime/query/tooling.d.ts.map +1 -1
  383. package/dist/runtime/query/tooling.js.map +1 -1
  384. package/dist/runtime/query/turn-state.d.ts.map +1 -1
  385. package/dist/runtime/query/turn-state.js +5 -0
  386. package/dist/runtime/query/turn-state.js.map +1 -1
  387. package/dist/runtime/system-events.d.ts +112 -0
  388. package/dist/runtime/system-events.d.ts.map +1 -0
  389. package/dist/runtime/system-events.js +183 -0
  390. package/dist/runtime/system-events.js.map +1 -0
  391. package/dist/scheduler/delegating.d.ts +2 -1
  392. package/dist/scheduler/delegating.d.ts.map +1 -1
  393. package/dist/scheduler/delegating.js +3 -2
  394. package/dist/scheduler/delegating.js.map +1 -1
  395. package/dist/skills/index.d.ts +1 -1
  396. package/dist/skills/index.d.ts.map +1 -1
  397. package/dist/skills/index.js +1 -1
  398. package/dist/skills/index.js.map +1 -1
  399. package/dist/skills/registry.d.ts +18 -0
  400. package/dist/skills/registry.d.ts.map +1 -1
  401. package/dist/skills/registry.js +20 -0
  402. package/dist/skills/registry.js.map +1 -1
  403. package/dist/store/evidence/index-page.d.ts +2 -2
  404. package/dist/store/session-log/conformance.d.ts +1 -1
  405. package/dist/store/session-log/conformance.d.ts.map +1 -1
  406. package/dist/store/session-log/conformance.js +49 -8
  407. package/dist/store/session-log/conformance.js.map +1 -1
  408. package/dist/store/session-log/core.d.ts +8 -3
  409. package/dist/store/session-log/core.d.ts.map +1 -1
  410. package/dist/store/session-log/core.js +2 -1
  411. package/dist/store/session-log/core.js.map +1 -1
  412. package/dist/store/session-log/fold.d.ts +12 -0
  413. package/dist/store/session-log/fold.d.ts.map +1 -1
  414. package/dist/store/session-log/fold.js +57 -7
  415. package/dist/store/session-log/fold.js.map +1 -1
  416. package/dist/store/session-log/index.d.ts +2 -2
  417. package/dist/store/session-log/index.d.ts.map +1 -1
  418. package/dist/store/session-log/index.js +1 -1
  419. package/dist/store/session-log/index.js.map +1 -1
  420. package/dist/test-support/toolset.d.ts +19 -0
  421. package/dist/test-support/toolset.d.ts.map +1 -0
  422. package/dist/test-support/toolset.js +20 -0
  423. package/dist/test-support/toolset.js.map +1 -0
  424. package/dist/tools/builtins/bash.d.ts +29 -0
  425. package/dist/tools/builtins/bash.d.ts.map +1 -1
  426. package/dist/tools/builtins/bash.js +19 -9
  427. package/dist/tools/builtins/bash.js.map +1 -1
  428. package/dist/tools/builtins/browser-url.d.ts +1 -1
  429. package/dist/tools/builtins/browser-url.js +1 -1
  430. package/dist/tools/builtins/browser.d.ts +6 -6
  431. package/dist/tools/builtins/computer-use-coordinates.d.ts +65 -0
  432. package/dist/tools/builtins/computer-use-coordinates.d.ts.map +1 -0
  433. package/dist/tools/builtins/computer-use-coordinates.js +123 -0
  434. package/dist/tools/builtins/computer-use-coordinates.js.map +1 -0
  435. package/dist/tools/builtins/computer-use-image.d.ts +77 -0
  436. package/dist/tools/builtins/computer-use-image.d.ts.map +1 -0
  437. package/dist/tools/builtins/computer-use-image.js +223 -0
  438. package/dist/tools/builtins/computer-use-image.js.map +1 -0
  439. package/dist/tools/builtins/computer-use.d.ts +519 -14
  440. package/dist/tools/builtins/computer-use.d.ts.map +1 -1
  441. package/dist/tools/builtins/computer-use.js +1196 -183
  442. package/dist/tools/builtins/computer-use.js.map +1 -1
  443. package/dist/tools/builtins/edit.d.ts.map +1 -1
  444. package/dist/tools/builtins/edit.js +9 -1
  445. package/dist/tools/builtins/edit.js.map +1 -1
  446. package/dist/tools/builtins/index.d.ts +2 -1
  447. package/dist/tools/builtins/index.d.ts.map +1 -1
  448. package/dist/tools/builtins/index.js +1 -1
  449. package/dist/tools/builtins/index.js.map +1 -1
  450. package/dist/tools/builtins/json-string-hint.d.ts +15 -0
  451. package/dist/tools/builtins/json-string-hint.d.ts.map +1 -0
  452. package/dist/tools/builtins/json-string-hint.js +17 -0
  453. package/dist/tools/builtins/json-string-hint.js.map +1 -0
  454. package/dist/tools/builtins/search-tools.d.ts.map +1 -1
  455. package/dist/tools/builtins/search-tools.js +7 -15
  456. package/dist/tools/builtins/search-tools.js.map +1 -1
  457. package/dist/tools/builtins/skill.d.ts +74 -0
  458. package/dist/tools/builtins/skill.d.ts.map +1 -1
  459. package/dist/tools/builtins/skill.js +214 -174
  460. package/dist/tools/builtins/skill.js.map +1 -1
  461. package/dist/tools/builtins/write-file.d.ts.map +1 -1
  462. package/dist/tools/builtins/write-file.js +11 -0
  463. package/dist/tools/builtins/write-file.js.map +1 -1
  464. package/dist/tools/command-shell.d.ts +10 -0
  465. package/dist/tools/command-shell.d.ts.map +1 -1
  466. package/dist/tools/command-shell.js +45 -1
  467. package/dist/tools/command-shell.js.map +1 -1
  468. package/dist/tools/coordinator/agent.d.ts.map +1 -1
  469. package/dist/tools/coordinator/agent.js +5 -0
  470. package/dist/tools/coordinator/agent.js.map +1 -1
  471. package/dist/tools/coordinator/ask-user-question.d.ts.map +1 -1
  472. package/dist/tools/coordinator/ask-user-question.js +26 -13
  473. package/dist/tools/coordinator/ask-user-question.js.map +1 -1
  474. package/dist/tools/coordinator/index.d.ts.map +1 -1
  475. package/dist/tools/coordinator/index.js +5 -0
  476. package/dist/tools/coordinator/index.js.map +1 -1
  477. package/dist/tools/coordinator/question-options.d.ts +78 -0
  478. package/dist/tools/coordinator/question-options.d.ts.map +1 -0
  479. package/dist/tools/coordinator/question-options.js +153 -0
  480. package/dist/tools/coordinator/question-options.js.map +1 -0
  481. package/dist/tools/defineTool.d.ts +12 -0
  482. package/dist/tools/defineTool.d.ts.map +1 -1
  483. package/dist/tools/defineTool.js +24 -0
  484. package/dist/tools/defineTool.js.map +1 -1
  485. package/dist/tools/memory/search.d.ts.map +1 -1
  486. package/dist/tools/memory/search.js +4 -1
  487. package/dist/tools/memory/search.js.map +1 -1
  488. package/dist/tools/memory/update.d.ts.map +1 -1
  489. package/dist/tools/memory/update.js +8 -5
  490. package/dist/tools/memory/update.js.map +1 -1
  491. package/dist/tools/render-nonce.d.ts +47 -0
  492. package/dist/tools/render-nonce.d.ts.map +1 -0
  493. package/dist/tools/render-nonce.js +63 -0
  494. package/dist/tools/render-nonce.js.map +1 -0
  495. package/dist/tools/roster.d.ts +46 -19
  496. package/dist/tools/roster.d.ts.map +1 -1
  497. package/dist/tools/roster.js +63 -33
  498. package/dist/tools/roster.js.map +1 -1
  499. package/dist/tools/schedules/present.d.ts.map +1 -1
  500. package/dist/tools/schedules/present.js +2 -0
  501. package/dist/tools/schedules/present.js.map +1 -1
  502. package/dist/tools/schedules/schedule-tool.d.ts +6 -5
  503. package/dist/tools/schedules/schedule-tool.d.ts.map +1 -1
  504. package/dist/tools/schedules/schedule-tool.js +208 -37
  505. package/dist/tools/schedules/schedule-tool.js.map +1 -1
  506. package/dist/tools/schedules/types.d.ts +120 -6
  507. package/dist/tools/schedules/types.d.ts.map +1 -1
  508. package/dist/tools/trusted-read-only.d.ts +9 -5
  509. package/dist/tools/trusted-read-only.d.ts.map +1 -1
  510. package/dist/tools/trusted-read-only.js +14 -10
  511. package/dist/tools/trusted-read-only.js.map +1 -1
  512. package/dist/tools/untrusted-envelope.d.ts +67 -28
  513. package/dist/tools/untrusted-envelope.d.ts.map +1 -1
  514. package/dist/tools/untrusted-envelope.js +108 -59
  515. package/dist/tools/untrusted-envelope.js.map +1 -1
  516. package/dist/toolsets/combine.d.ts +44 -0
  517. package/dist/toolsets/combine.d.ts.map +1 -0
  518. package/dist/toolsets/combine.js +153 -0
  519. package/dist/toolsets/combine.js.map +1 -0
  520. package/dist/toolsets/manager.d.ts +201 -0
  521. package/dist/toolsets/manager.d.ts.map +1 -0
  522. package/dist/toolsets/manager.js +837 -0
  523. package/dist/toolsets/manager.js.map +1 -0
  524. package/dist/toolsets/source-glob.d.ts +15 -0
  525. package/dist/toolsets/source-glob.d.ts.map +1 -0
  526. package/dist/toolsets/source-glob.js +23 -0
  527. package/dist/toolsets/source-glob.js.map +1 -0
  528. package/dist/toolsets/toolset.d.ts +20 -0
  529. package/dist/toolsets/toolset.d.ts.map +1 -0
  530. package/dist/toolsets/toolset.js +28 -0
  531. package/dist/toolsets/toolset.js.map +1 -0
  532. package/dist/toolsets/types.d.ts +128 -0
  533. package/dist/toolsets/types.d.ts.map +1 -0
  534. package/dist/toolsets/types.js +33 -0
  535. package/dist/toolsets/types.js.map +1 -0
  536. package/dist/toolsets/wrappers.d.ts +59 -0
  537. package/dist/toolsets/wrappers.d.ts.map +1 -0
  538. package/dist/toolsets/wrappers.js +165 -0
  539. package/dist/toolsets/wrappers.js.map +1 -0
  540. package/dist/types/agent/base.d.ts +27 -16
  541. package/dist/types/agent/base.d.ts.map +1 -1
  542. package/dist/types/agent/index.d.ts +1 -0
  543. package/dist/types/agent/index.d.ts.map +1 -1
  544. package/dist/types/agent/index.js +1 -0
  545. package/dist/types/agent/index.js.map +1 -1
  546. package/dist/types/agent/pipeline.d.ts +2 -0
  547. package/dist/types/agent/pipeline.d.ts.map +1 -1
  548. package/dist/types/agent/query.d.ts +117 -0
  549. package/dist/types/agent/query.d.ts.map +1 -0
  550. package/dist/types/agent/query.js +2 -0
  551. package/dist/types/agent/query.js.map +1 -0
  552. package/dist/types/agent/reactive.d.ts +2 -129
  553. package/dist/types/agent/reactive.d.ts.map +1 -1
  554. package/dist/types/agent/router.d.ts +2 -0
  555. package/dist/types/agent/router.d.ts.map +1 -1
  556. package/dist/types/agent/supervisor.d.ts +5 -2
  557. package/dist/types/agent/supervisor.d.ts.map +1 -1
  558. package/dist/types/authorization/index.d.ts +70 -12
  559. package/dist/types/authorization/index.d.ts.map +1 -1
  560. package/dist/types/authorization/index.js +11 -0
  561. package/dist/types/authorization/index.js.map +1 -1
  562. package/dist/types/computer-use/index.d.ts +176 -0
  563. package/dist/types/computer-use/index.d.ts.map +1 -1
  564. package/dist/types/computer-use/index.js.map +1 -1
  565. package/dist/types/connector/mcp.d.ts +32 -0
  566. package/dist/types/connector/mcp.d.ts.map +1 -1
  567. package/dist/types/errors/index.d.ts +15 -0
  568. package/dist/types/errors/index.d.ts.map +1 -1
  569. package/dist/types/errors/index.js.map +1 -1
  570. package/dist/types/hitl/index.d.ts +57 -0
  571. package/dist/types/hitl/index.d.ts.map +1 -1
  572. package/dist/types/hitl/index.js.map +1 -1
  573. package/dist/types/message/index.d.ts +130 -9
  574. package/dist/types/message/index.d.ts.map +1 -1
  575. package/dist/types/message/index.js +4 -1
  576. package/dist/types/message/index.js.map +1 -1
  577. package/dist/types/plugin/index.d.ts +22 -4
  578. package/dist/types/plugin/index.d.ts.map +1 -1
  579. package/dist/types/plugin/index.js +1 -0
  580. package/dist/types/plugin/index.js.map +1 -1
  581. package/dist/types/provider/chat.d.ts +5 -0
  582. package/dist/types/provider/chat.d.ts.map +1 -1
  583. package/dist/types/provider/config.d.ts +8 -3
  584. package/dist/types/provider/config.d.ts.map +1 -1
  585. package/dist/types/provider/stream.d.ts +39 -0
  586. package/dist/types/provider/stream.d.ts.map +1 -1
  587. package/dist/types/sandbox/index.d.ts +1 -1
  588. package/dist/types/session/events.d.ts +14 -3
  589. package/dist/types/session/events.d.ts.map +1 -1
  590. package/dist/types/session/events.js.map +1 -1
  591. package/dist/types/tool/index.d.ts +171 -91
  592. package/dist/types/tool/index.d.ts.map +1 -1
  593. package/dist/types/tool/index.js.map +1 -1
  594. package/dist/types/toolset/index.d.ts +10 -40
  595. package/dist/types/toolset/index.d.ts.map +1 -1
  596. package/package.json +3 -1
  597. package/src/advisory/history.ts +27 -2
  598. package/src/advisory/index.ts +1 -1
  599. package/src/advisory/registry.ts +33 -0
  600. package/src/agents/AGENTS.md +14 -7
  601. package/src/agents/PipelineAgent.ts +2 -303
  602. package/src/agents/QueryAgent.ts +269 -0
  603. package/src/agents/ReactiveAgent.ts +6 -216
  604. package/src/agents/RouterAgent.ts +2 -337
  605. package/src/agents/SupervisorAgent.ts +3 -476
  606. package/src/agents/defineAgent.ts +13 -2
  607. package/src/agents/examples/PipelineAgent.ts +302 -0
  608. package/src/agents/examples/RouterAgent.ts +338 -0
  609. package/src/agents/examples/SupervisorAgent.ts +472 -0
  610. package/src/agents/explore.ts +6 -3
  611. package/src/agents/forward-options.ts +37 -0
  612. package/src/agents/index.ts +2 -0
  613. package/src/agents/runAgent.ts +144 -37
  614. package/src/authorization/command-line.ts +2 -2
  615. package/src/authorization/gate.ts +46 -5
  616. package/src/authorization/program.ts +773 -0
  617. package/src/authorization/reexec-wrapper.ts +589 -0
  618. package/src/authorization/rules.ts +36 -1
  619. package/src/authorization/shell-lexer.ts +136 -63
  620. package/src/bridge/sse/mapper.ts +6 -0
  621. package/src/capabilities/index.ts +157 -0
  622. package/src/config/registry.ts +4 -1
  623. package/src/connector/index.ts +9 -2
  624. package/src/connector/mcp/adapter.ts +58 -3
  625. package/src/connector/mcp/client.ts +20 -4
  626. package/src/connector/mcp/discovery.ts +60 -0
  627. package/src/connector/mcp/index.ts +13 -0
  628. package/src/connector/mcp/mcp-toolset.ts +515 -0
  629. package/src/connector/tools/index.ts +2 -2
  630. package/src/connector/tools/router.ts +35 -58
  631. package/src/directory/derive-supervisor.ts +12 -8
  632. package/src/directory/derive.ts +8 -5
  633. package/src/execution/code-runtime/types.ts +2 -2
  634. package/src/invariants/index.ts +4 -1
  635. package/src/manager/agent/lifecycle.ts +9 -0
  636. package/src/manager/connector/environment.ts +17 -4
  637. package/src/manager/connector/index.ts +2 -2
  638. package/src/manager/connector/tenant.ts +17 -4
  639. package/src/manager/index.ts +2 -2
  640. package/src/manager/session/attribution.ts +79 -0
  641. package/src/manager/session/turn-recorder.ts +211 -55
  642. package/src/peers/address.ts +58 -0
  643. package/src/peers/client.ts +202 -0
  644. package/src/peers/dir.ts +176 -0
  645. package/src/peers/endpoint.ts +564 -0
  646. package/src/peers/envelope.ts +121 -0
  647. package/src/peers/index.ts +96 -0
  648. package/src/peers/protocol.ts +196 -0
  649. package/src/peers/record.ts +56 -0
  650. package/src/peers/registry.ts +241 -0
  651. package/src/plugin/define.ts +68 -0
  652. package/src/plugin/index.ts +2 -0
  653. package/src/plugin/lifecycle.ts +286 -106
  654. package/src/plugin/resolver.ts +6 -3
  655. package/src/plugin/shell-hook.ts +21 -5
  656. package/src/pricing/catalogue.generated.ts +28 -4
  657. package/src/pricing/rates.source.json +27 -6
  658. package/src/probe/errors.ts +4 -1
  659. package/src/prompt/coding-agent-doctrine.ts +22 -10
  660. package/src/prompt/contributions.ts +12 -2
  661. package/src/prompt/index.ts +1 -0
  662. package/src/provider/collect-chat-completion.ts +96 -11
  663. package/src/provider/mock.ts +24 -6
  664. package/src/provider/registry.ts +4 -1
  665. package/src/provider/tool-call-framing.ts +226 -0
  666. package/src/public-runtime.ts +152 -9
  667. package/src/public-tools.ts +43 -5
  668. package/src/public-types.ts +65 -6
  669. package/src/read-model/registry.ts +7 -2
  670. package/src/registry/ManagedRegistry.ts +57 -6
  671. package/src/registry/collision.ts +64 -0
  672. package/src/registry/command/index.ts +7 -3
  673. package/src/registry/index.ts +3 -9
  674. package/src/registry/plugin/index.ts +14 -1
  675. package/src/registry/tool/callable.ts +36 -0
  676. package/src/registry/tool/execute.ts +16 -839
  677. package/src/registry/tool/portable.ts +59 -0
  678. package/src/registry/tool/presentation.ts +6 -5
  679. package/src/runtime/bidi/session.ts +24 -5
  680. package/src/runtime/query/executor/tool-call-admission.ts +288 -22
  681. package/src/runtime/query/executor.ts +129 -16
  682. package/src/runtime/query/file-evidence-replay.ts +8 -7
  683. package/src/runtime/query/fork/prepare.ts +6 -1
  684. package/src/runtime/query/guardrail-presets.ts +4 -3
  685. package/src/runtime/query/index.ts +199 -101
  686. package/src/runtime/query/iteration/index.ts +13 -3
  687. package/src/runtime/query/iteration/phases/compaction.ts +1 -1
  688. package/src/runtime/query/iteration/phases/context.ts +2 -2
  689. package/src/runtime/query/iteration/phases/tool-review.ts +59 -2
  690. package/src/runtime/query/iteration/stream-turn.ts +390 -136
  691. package/src/runtime/query/iteration/tool-input.ts +284 -0
  692. package/src/runtime/query/observation-context.ts +2 -2
  693. package/src/runtime/query/prelude-lease.ts +132 -0
  694. package/src/runtime/query/prepare-turn.ts +477 -198
  695. package/src/runtime/query/prompt-cache.ts +2 -2
  696. package/src/runtime/query/prompt.ts +4 -4
  697. package/src/runtime/query/resume-session.ts +42 -3
  698. package/src/runtime/query/review-policy.ts +190 -24
  699. package/src/runtime/query/tooling.ts +3 -6
  700. package/src/runtime/query/turn-state.ts +4 -0
  701. package/src/runtime/system-events.ts +246 -0
  702. package/src/scheduler/delegating.ts +7 -2
  703. package/src/skills/index.ts +1 -1
  704. package/src/skills/registry.ts +33 -0
  705. package/src/store/session-log/conformance.ts +51 -8
  706. package/src/store/session-log/core.ts +10 -4
  707. package/src/store/session-log/fold.ts +33 -7
  708. package/src/store/session-log/index.ts +2 -0
  709. package/src/test-support/toolset.ts +23 -0
  710. package/src/tools/builtins/bash.ts +27 -10
  711. package/src/tools/builtins/browser-url.ts +1 -1
  712. package/src/tools/builtins/computer-use-coordinates.ts +144 -0
  713. package/src/tools/builtins/computer-use-image.ts +278 -0
  714. package/src/tools/builtins/computer-use.ts +1495 -191
  715. package/src/tools/builtins/edit.ts +12 -2
  716. package/src/tools/builtins/index.ts +7 -1
  717. package/src/tools/builtins/json-string-hint.ts +16 -0
  718. package/src/tools/builtins/search-tools.ts +7 -20
  719. package/src/tools/builtins/skill.ts +304 -174
  720. package/src/tools/builtins/write-file.ts +12 -0
  721. package/src/tools/command-shell.ts +46 -1
  722. package/src/tools/coordinator/agent.ts +6 -0
  723. package/src/tools/coordinator/ask-user-question.ts +29 -15
  724. package/src/tools/coordinator/index.ts +6 -0
  725. package/src/tools/coordinator/question-options.ts +195 -0
  726. package/src/tools/defineTool.ts +38 -0
  727. package/src/tools/memory/search.ts +4 -1
  728. package/src/tools/memory/update.ts +8 -5
  729. package/src/tools/render-nonce.ts +69 -0
  730. package/src/tools/roster.ts +84 -33
  731. package/src/tools/schedules/present.ts +2 -0
  732. package/src/tools/schedules/schedule-tool.ts +238 -39
  733. package/src/tools/schedules/types.ts +120 -6
  734. package/src/tools/trusted-read-only.ts +19 -10
  735. package/src/tools/untrusted-envelope.ts +121 -57
  736. package/src/toolsets/combine.ts +181 -0
  737. package/src/toolsets/manager.ts +996 -0
  738. package/src/toolsets/source-glob.ts +22 -0
  739. package/src/toolsets/toolset.ts +31 -0
  740. package/src/toolsets/types.ts +154 -0
  741. package/src/toolsets/wrappers.ts +191 -0
  742. package/src/types/agent/base.ts +29 -16
  743. package/src/types/agent/index.ts +1 -0
  744. package/src/types/agent/pipeline.ts +2 -0
  745. package/src/types/agent/query.ts +129 -0
  746. package/src/types/agent/reactive.ts +5 -142
  747. package/src/types/agent/router.ts +2 -0
  748. package/src/types/agent/supervisor.ts +5 -2
  749. package/src/types/authorization/index.ts +33 -0
  750. package/src/types/computer-use/index.ts +202 -0
  751. package/src/types/connector/mcp.ts +32 -0
  752. package/src/types/errors/index.ts +15 -0
  753. package/src/types/hitl/index.ts +57 -0
  754. package/src/types/message/index.ts +135 -7
  755. package/src/types/plugin/index.ts +44 -21
  756. package/src/types/provider/chat.ts +5 -0
  757. package/src/types/provider/config.ts +8 -3
  758. package/src/types/provider/stream.ts +39 -0
  759. package/src/types/sandbox/index.ts +1 -1
  760. package/src/types/session/events.ts +14 -3
  761. package/src/types/tool/index.ts +173 -105
  762. package/src/types/toolset/index.ts +10 -47
  763. package/dist/registry/toolset/catalog.d.ts +0 -42
  764. package/dist/registry/toolset/catalog.d.ts.map +0 -1
  765. package/dist/registry/toolset/catalog.js +0 -234
  766. package/dist/registry/toolset/catalog.js.map +0 -1
  767. package/src/registry/toolset/catalog.ts +0 -308
@@ -1,18 +1,116 @@
1
1
  import { z } from 'zod'
2
+ import { resolveProviderCapabilities } from '../../provider/capabilities.js'
2
3
  import type {
3
4
  ComputerUseAction,
4
5
  ComputerUseCapabilities,
5
6
  ComputerUseHost,
6
7
  ComputerUseOutcomeUnknown,
7
- ComputerUseResult,
8
+ Point,
9
+ Rect,
10
+ ScreenshotResult,
11
+ UiActResult,
12
+ UiElement,
13
+ UiElementAction,
14
+ UiSnapshot,
15
+ WindowInfo,
8
16
  } from '../../types/computer-use/index.js'
9
- import type { ToolDefinition, ToolResult } from '../../types/tool/index.js'
17
+ import type { ToolResultBlock } from '../../types/message/index.js'
18
+ import type { LLMProvider } from '../../types/provider/index.js'
19
+ import type { ToolContext, ToolDefinition, ToolResult } from '../../types/tool/index.js'
20
+ import { sleep } from '../../utils/backoff.js'
10
21
  import { defineTool } from '../defineTool.js'
22
+ import { neutralizeEnvelopeDelimiter, wrapUntrusted } from '../untrusted-envelope.js'
23
+ import {
24
+ type ScreenshotFrame,
25
+ ScreenshotFrames,
26
+ assumedDisplay,
27
+ desktopRectOnImage,
28
+ pointOnImage,
29
+ toDisplayPoint,
30
+ toDisplayRect,
31
+ toImagePoint,
32
+ } from './computer-use-coordinates.js'
33
+ import {
34
+ type FittedImage,
35
+ STANDARD_SCREENSHOT_LIMITS,
36
+ type ScreenshotLimits,
37
+ cropAndFitPng,
38
+ fitPng,
39
+ pngSize,
40
+ } from './computer-use-image.js'
41
+
42
+ export {
43
+ HIGH_RES_SCREENSHOT_LIMITS,
44
+ STANDARD_SCREENSHOT_LIMITS,
45
+ screenshotTargetSize,
46
+ } from './computer-use-image.js'
47
+ export type { ImageSize, ScreenshotLimits } from './computer-use-image.js'
11
48
 
12
49
  export const COMPUTER_USE_TOOL_NAME = 'computer_use' as const
13
50
 
51
+ const DEFAULT_SETTLE_MS = 500
52
+ const DEFAULT_MAX_BATCH_ACTIONS = 20
53
+ const DEFAULT_MAX_WAIT_MS = 10_000
54
+ const MAX_LISTED_WINDOWS = 50
55
+ /** The most of a ui_snapshot the model is shown, in characters (about 4 000 tokens). */
56
+ const MAX_UI_SNAPSHOT_CHARS = 14_000
57
+
58
+ /**
59
+ * How `createComputerUseTool` sizes screenshots, paces actions and bounds a
60
+ * batch. Every field is optional.
61
+ */
62
+ export interface ComputerUseToolOptions {
63
+ /**
64
+ * What every screenshot and zoom image is fitted to before the model sees
65
+ * it. Default {@link STANDARD_SCREENSHOT_LIMITS}, which every current vision
66
+ * model takes without a further resize; {@link HIGH_RES_SCREENSHOT_LIMITS}
67
+ * only when every model the session can reach is on that tier.
68
+ */
69
+ readonly screenshotLimits?: ScreenshotLimits
70
+ /**
71
+ * Milliseconds to wait after an action before the screenshot it returns,
72
+ * so a menu has opened or a page has started to paint. Default 500.
73
+ */
74
+ readonly settleMs?: number
75
+ /**
76
+ * Return a fresh screenshot after every call that changed something.
77
+ * Default true. Off, a model has to ask for one — an extra round trip per
78
+ * action, which is what this exists to remove.
79
+ */
80
+ readonly screenshotAfterActions?: boolean
81
+ /** Most actions one `batch` may carry. Default 20. */
82
+ readonly maxBatchActions?: number
83
+ /** Longest single `wait`, in milliseconds. Default 10 000. */
84
+ readonly maxWaitMs?: number
85
+ /**
86
+ * Why computer use cannot work in this session although the host could —
87
+ * typically that the provider cannot put an image in a tool result, so the
88
+ * model would never see a screenshot (see
89
+ * {@link computerUseUnavailableReason}). Set, the tool mounts as a
90
+ * diagnostic: its description says why and every call is refused without
91
+ * touching the host.
92
+ */
93
+ readonly unavailableReason?: string
94
+ }
95
+
96
+ /**
97
+ * Why `provider` cannot drive `computer_use`, or undefined when it can.
98
+ *
99
+ * The tool's only way to show the model the screen is an image in a tool
100
+ * result. A driver that declares `supportsToolResultImages: false` replaces
101
+ * that image with a line of text, so the model acts on a screen it has never
102
+ * seen while every call reports success. Pass the answer to
103
+ * {@link ComputerUseToolOptions.unavailableReason}.
104
+ */
105
+ export function computerUseUnavailableReason(
106
+ provider: Pick<LLMProvider, 'id' | 'capabilities'>,
107
+ ): string | undefined {
108
+ if (resolveProviderCapabilities(provider).supportsToolResultImages) return undefined
109
+ return `The ${provider.id} provider cannot return images in tool results, so the model would never see a screenshot. Use a provider that can (for example Anthropic, Codex or Google) for computer use.`
110
+ }
111
+
14
112
  // ---------------------------------------------------------------------------
15
- // Input schema — discriminated union matching ComputerUseAction
113
+ // Input schema
16
114
  // ---------------------------------------------------------------------------
17
115
 
18
116
  const pointSchema = z.object({
@@ -20,104 +118,218 @@ const pointSchema = z.object({
20
118
  y: z.number().int(),
21
119
  })
22
120
 
23
- const mouseButtonSchema = z.enum(['left', 'right', 'middle'])
121
+ const regionSchema = z.object({
122
+ x: z.number().int(),
123
+ y: z.number().int(),
124
+ width: z.number().int().positive(),
125
+ height: z.number().int().positive(),
126
+ })
127
+
128
+ // Left when omitted: a model asked to "click the Start button" often leaves
129
+ // the button out, and refusing that cost a whole round trip for nothing.
130
+ const mouseButtonSchema = z.enum(['left', 'right', 'middle']).default('left')
131
+ const screenshotIdSchema = z.string().min(1).optional()
132
+
133
+ const cursorPositionSchema = z.object({ type: z.literal('cursor_position') })
134
+ const mouseMoveSchema = z.object({ type: z.literal('mouse_move'), to: pointSchema })
135
+ const mouseClickSchema = z.object({
136
+ type: z.literal('mouse_click'),
137
+ at: pointSchema,
138
+ button: mouseButtonSchema,
139
+ })
140
+ const mouseDragSchema = z.object({
141
+ type: z.literal('mouse_drag'),
142
+ from: pointSchema,
143
+ to: pointSchema,
144
+ button: mouseButtonSchema,
145
+ })
146
+ const scrollSchema = z.object({
147
+ type: z.literal('scroll'),
148
+ at: pointSchema,
149
+ direction: z.enum(['up', 'down', 'left', 'right']),
150
+ amount: z.number().int().positive(),
151
+ })
152
+ const typeTextSchema = z.object({ type: z.literal('type_text'), text: z.string() })
153
+ const keySchema = z.object({ type: z.literal('key'), keys: z.string() })
154
+ const waitSchema = z.object({ type: z.literal('wait'), ms: z.number().int().nonnegative() })
155
+ const listWindowsSchema = z.object({ type: z.literal('list_windows') })
156
+ const focusWindowSchema = z.object({
157
+ type: z.literal('focus_window'),
158
+ window_id: z.string().min(1),
159
+ })
160
+ const UI_ACTIONS = [
161
+ 'invoke',
162
+ 'set_value',
163
+ 'toggle',
164
+ 'select',
165
+ 'expand',
166
+ 'collapse',
167
+ 'focus',
168
+ 'scroll_into_view',
169
+ ] as const satisfies readonly UiElementAction[]
170
+ const uiSnapshotSchema = z.object({
171
+ type: z.literal('ui_snapshot'),
172
+ window_id: z.string().min(1).optional(),
173
+ })
174
+ const uiActSchema = z.object({
175
+ type: z.literal('ui_act'),
176
+ ref: z.string().min(1),
177
+ action: z.enum(UI_ACTIONS),
178
+ value: z.string().optional(),
179
+ })
180
+
181
+ /** What a batch may carry: everything but the image-returning actions and another batch. */
182
+ const batchItemSchema = z.discriminatedUnion('type', [
183
+ cursorPositionSchema,
184
+ mouseMoveSchema,
185
+ mouseClickSchema,
186
+ mouseDragSchema,
187
+ scrollSchema,
188
+ typeTextSchema,
189
+ keySchema,
190
+ waitSchema,
191
+ listWindowsSchema,
192
+ focusWindowSchema,
193
+ uiActSchema,
194
+ ])
195
+
196
+ const withFrame = { screenshot_id: screenshotIdSchema }
24
197
 
25
198
  const actionSchema = z.discriminatedUnion('type', [
26
199
  z.object({ type: z.literal('screenshot') }),
27
- z.object({ type: z.literal('cursor_position') }),
28
- z.object({ type: z.literal('mouse_move'), to: pointSchema }),
29
- z.object({ type: z.literal('mouse_click'), at: pointSchema, button: mouseButtonSchema }),
200
+ z.object({ type: z.literal('zoom'), region: regionSchema, ...withFrame }),
201
+ cursorPositionSchema.extend(withFrame),
202
+ mouseMoveSchema.extend(withFrame),
203
+ mouseClickSchema.extend(withFrame),
204
+ mouseDragSchema.extend(withFrame),
205
+ scrollSchema.extend(withFrame),
206
+ typeTextSchema.extend(withFrame),
207
+ keySchema.extend(withFrame),
208
+ waitSchema.extend(withFrame),
209
+ listWindowsSchema.extend(withFrame),
210
+ focusWindowSchema.extend(withFrame),
211
+ uiSnapshotSchema,
212
+ uiActSchema.extend(withFrame),
30
213
  z.object({
31
- type: z.literal('mouse_drag'),
32
- from: pointSchema,
33
- to: pointSchema,
34
- button: mouseButtonSchema,
214
+ type: z.literal('batch'),
215
+ actions: z.array(batchItemSchema).min(1),
216
+ ...withFrame,
35
217
  }),
36
- z.object({
37
- type: z.literal('scroll'),
38
- at: pointSchema,
39
- direction: z.enum(['up', 'down', 'left', 'right']),
40
- amount: z.number().int().positive(),
41
- }),
42
- z.object({ type: z.literal('type_text'), text: z.string() }),
43
- z.object({ type: z.literal('key'), keys: z.string() }),
44
218
  ])
45
219
 
46
220
  /**
47
- * The provider-facing shape is deliberately flat.
221
+ * The tool's input, inferred from its schema: one action, or
222
+ * `{ type: 'batch', actions: [...] }`.
48
223
  *
49
- * The runtime schema above is the authoritative contract: it knows which
50
- * fields each action requires. Rendering that discriminated union produces a
51
- * root `anyOf`, however, and some custom-tool wires reject root combinators
52
- * even when every branch is an object. A model can still see every field and
53
- * every action here; incomplete combinations are rejected by `actionSchema`
54
- * before the host is called, with the recovery hint below.
55
- */
56
- const pointModelInputSchema = {
57
- type: 'object',
58
- properties: {
59
- x: { type: 'integer' },
60
- y: { type: 'integer' },
61
- },
62
- required: ['x', 'y'],
63
- additionalProperties: false,
64
- } as const
65
-
66
- const modelInputSchema = {
67
- type: 'object',
68
- properties: {
69
- type: {
70
- type: 'string',
71
- enum: [
72
- 'screenshot',
73
- 'cursor_position',
74
- 'mouse_move',
75
- 'mouse_click',
76
- 'mouse_drag',
77
- 'scroll',
78
- 'type_text',
79
- 'key',
80
- ],
81
- description:
82
- 'Desktop action. screenshot and cursor_position need no other fields; mouse_move needs to; mouse_click needs at and button; mouse_drag needs from, to, and button; scroll needs at, direction, and amount; type_text needs text; key needs keys.',
83
- },
84
- to: pointModelInputSchema,
85
- at: pointModelInputSchema,
86
- from: pointModelInputSchema,
87
- button: { type: 'string', enum: ['left', 'right', 'middle'] },
88
- direction: { type: 'string', enum: ['up', 'down', 'left', 'right'] },
89
- amount: { type: 'integer', description: 'Positive integer scroll distance.' },
90
- text: { type: 'string', description: 'Literal text to type.' },
91
- keys: {
92
- type: 'string',
93
- description: 'Key or key chord to press, for example ENTER or CTRL+R.',
94
- },
95
- },
96
- required: ['type'],
97
- additionalProperties: false,
98
- }
99
-
100
- /**
101
- * The tool's input, inferred from its schema.
102
- *
103
- * Exported because `createComputerUseTool` returns a `ToolDefinition<ActionInput>`
224
+ * Exported because `createComputerUseTool` returns a `ToolDefinition<ActionInput>` (a `ComputerUseTool`)
104
225
  * and a consumer typing that variable, or writing a wrapper around it, had no
105
226
  * name for the parameter — the type was module-private while the function
106
227
  * carrying it was public.
107
228
  */
108
229
  export type ActionInput = z.infer<typeof actionSchema>
230
+ type BatchItem = z.infer<typeof batchItemSchema>
231
+ type ToolActionType = ActionInput['type']
232
+
233
+ const HOST_ACTIONS: readonly ComputerUseAction['type'][] = [
234
+ 'screenshot',
235
+ 'cursor_position',
236
+ 'mouse_move',
237
+ 'mouse_click',
238
+ 'mouse_drag',
239
+ 'scroll',
240
+ 'type_text',
241
+ 'key',
242
+ ]
109
243
 
110
- const DESTRUCTIVE_ACTION_TYPES = new Set<ComputerUseAction['type']>([
244
+ const ALL_ACTIONS: readonly ToolActionType[] = [
245
+ 'screenshot',
246
+ 'zoom',
247
+ 'cursor_position',
248
+ 'mouse_move',
249
+ 'mouse_click',
250
+ 'mouse_drag',
251
+ 'scroll',
252
+ 'type_text',
253
+ 'key',
254
+ 'wait',
255
+ 'list_windows',
256
+ 'focus_window',
257
+ 'ui_snapshot',
258
+ 'ui_act',
259
+ 'batch',
260
+ ]
261
+
262
+ /** Observations: they change nothing on the desktop. */
263
+ const READ_ONLY_ACTIONS = new Set<string>([
264
+ 'screenshot',
265
+ 'zoom',
266
+ 'cursor_position',
267
+ 'wait',
268
+ 'list_windows',
269
+ 'ui_snapshot',
270
+ ])
271
+
272
+ const DESTRUCTIVE_ACTIONS = new Set<string>([
111
273
  'mouse_click',
112
274
  'mouse_drag',
113
275
  'type_text',
114
276
  'key',
115
277
  'scroll',
278
+ 'ui_act',
116
279
  ])
117
280
 
118
- function requiredCapability(type: ComputerUseAction['type']): keyof ComputerUseCapabilities | null {
281
+ /** Observations that show the model what is on the screen, on their own. */
282
+ const SCREEN_OBSERVATIONS = new Set<string>(['screenshot', 'zoom', 'list_windows', 'ui_snapshot'])
283
+
284
+ /**
285
+ * Terminal applications, by the process name a host reports in
286
+ * `WindowInfo.app` (on Windows without `.exe`). Keys are not typed into these.
287
+ */
288
+ const TERMINAL_APPS =
289
+ /^(WindowsTerminal|OpenConsole|conhost|cmd|powershell|pwsh|wsl|mintty|wezterm(-gui)?|alacritty|Hyper|Tabby|kitty|ConEmu(64)?|putty|Terminal|iTerm2?|gnome-terminal(-server)?|konsole|xterm|xfce4-terminal|tilix|terminator|foot|ghostty|Warp)$/i
290
+
291
+ /** Actions a batch cannot carry: the ones that return an image or a tree, and batch itself. */
292
+ const OUTSIDE_BATCH = new Set<string>(['screenshot', 'zoom', 'ui_snapshot', 'batch'])
293
+
294
+ /** A single action's whole result when it only acted: `<label>: done`, then its screenshot. */
295
+ const ACKNOWLEDGEMENT = /^[^\n]*: done(\nScreenshot s\d+ \(\d+x\d+\)\.)?$/
296
+
297
+ function isRecord(value: unknown): value is Record<string, unknown> {
298
+ return typeof value === 'object' && value !== null && !Array.isArray(value)
299
+ }
300
+
301
+ function batchActions(input: unknown): readonly unknown[] | null {
302
+ if (!isRecord(input) || input.type !== 'batch') return null
303
+ return Array.isArray(input.actions) ? input.actions : []
304
+ }
305
+
306
+ /** Read-only when every action is: a batch with one click in it is not. Unknown shapes are not. */
307
+ function isReadOnlyInput(input: unknown): boolean {
308
+ const actions = batchActions(input)
309
+ if (actions)
310
+ return (
311
+ actions.length > 0 &&
312
+ actions.every((item) => isRecord(item) && READ_ONLY_ACTIONS.has(String(item.type)))
313
+ )
314
+ return isRecord(input) && READ_ONLY_ACTIONS.has(String(input.type))
315
+ }
316
+
317
+ function isDestructiveInput(input: unknown): boolean {
318
+ const actions = batchActions(input)
319
+ if (actions)
320
+ return actions.some((item) => !isRecord(item) || DESTRUCTIVE_ACTIONS.has(String(item.type)))
321
+ return isRecord(input) && DESTRUCTIVE_ACTIONS.has(String(input.type))
322
+ }
323
+
324
+ // ---------------------------------------------------------------------------
325
+ // Capabilities
326
+ // ---------------------------------------------------------------------------
327
+
328
+ function requiredCapability(type: string): keyof ComputerUseCapabilities | null {
119
329
  switch (type) {
120
330
  case 'screenshot':
331
+ case 'zoom':
332
+ case 'wait':
121
333
  return 'screenshot'
122
334
  case 'cursor_position':
123
335
  return 'cursorPosition'
@@ -129,27 +341,89 @@ function requiredCapability(type: ComputerUseAction['type']): keyof ComputerUseC
129
341
  case 'type_text':
130
342
  case 'key':
131
343
  return 'keyboard'
344
+ case 'list_windows':
345
+ case 'focus_window':
346
+ return 'windows'
347
+ case 'ui_snapshot':
348
+ case 'ui_act':
349
+ return 'uiTree'
132
350
  default:
133
351
  return null
134
352
  }
135
353
  }
136
354
 
137
- function buildDescription(host: ComputerUseHost): string {
138
- const caps = host.capabilities
139
- const available = availableActions(caps)
355
+ function hostActionAvailable(
356
+ caps: ComputerUseCapabilities,
357
+ action: ComputerUseAction['type'],
358
+ ): boolean {
359
+ const required = requiredCapability(action)
360
+ return (
361
+ (required === null || caps[required] === true) &&
362
+ (caps.supportedActions === undefined || caps.supportedActions.includes(action)) &&
363
+ (action !== 'mouse_click' || caps.mouseClickButtons?.length !== 0) &&
364
+ (action !== 'mouse_drag' || caps.mouseDragButtons?.length !== 0)
365
+ )
366
+ }
367
+
368
+ function availableActions(host: ComputerUseHost, caps: ComputerUseCapabilities): ToolActionType[] {
369
+ const screenshot = hostActionAvailable(caps, 'screenshot')
370
+ const windows =
371
+ caps.windows === true &&
372
+ typeof host.listWindows === 'function' &&
373
+ typeof host.focusWindow === 'function'
374
+ const uiTree =
375
+ caps.uiTree === true &&
376
+ typeof host.uiSnapshot === 'function' &&
377
+ typeof host.uiAct === 'function'
378
+ const available = ALL_ACTIONS.filter((action) => {
379
+ switch (action) {
380
+ case 'zoom':
381
+ case 'wait':
382
+ return screenshot
383
+ case 'list_windows':
384
+ case 'focus_window':
385
+ return windows
386
+ case 'ui_snapshot':
387
+ case 'ui_act':
388
+ return uiTree
389
+ case 'batch':
390
+ return false
391
+ default:
392
+ return hostActionAvailable(caps, action)
393
+ }
394
+ })
395
+ if (available.some((action) => !OUTSIDE_BATCH.has(action))) available.push('batch')
396
+ return available
397
+ }
398
+
399
+ function unavailableHostActions(caps: ComputerUseCapabilities): string[] {
140
400
  const unavailable: string[] = []
141
401
  if (!caps.screenshot) unavailable.push('screenshot')
142
402
  if (!caps.cursorPosition) unavailable.push('cursor_position')
143
403
  if (!caps.mouse) unavailable.push('mouse')
144
404
  if (!caps.keyboard) unavailable.push('keyboard')
145
405
  if (caps.supportedActions) {
146
- for (const action of actionSchema.options.map((option) => option.shape.type.value)) {
147
- if (!available.includes(action)) unavailable.push(action)
406
+ for (const action of HOST_ACTIONS) {
407
+ if (!hostActionAvailable(caps, action) && !unavailable.includes(action))
408
+ unavailable.push(action)
148
409
  }
149
410
  }
411
+ return unavailable
412
+ }
413
+
414
+ // ---------------------------------------------------------------------------
415
+ // Model-facing description and schema
416
+ // ---------------------------------------------------------------------------
150
417
 
418
+ function buildDescription(
419
+ host: ComputerUseHost,
420
+ caps: ComputerUseCapabilities,
421
+ settings: ResolvedSettings,
422
+ ): string {
423
+ const available = availableActions(host, caps)
424
+ const unavailable = unavailableHostActions(caps)
151
425
  const lines = [
152
- `Controls the user's desktop on a ${caps.displayServer} host. Use to take screenshots and drive mouse/keyboard input for GUI tasks.`,
426
+ `Controls the user's desktop on a ${caps.displayServer} host: screenshots, mouse and keyboard, for GUI tasks.`,
153
427
  `Available actions: ${available.join('; ') || 'none'}.`,
154
428
  ]
155
429
  if (unavailable.length > 0) {
@@ -159,9 +433,54 @@ function buildDescription(host: ComputerUseHost): string {
159
433
  : `Unavailable on this host: ${unavailable.join(', ')}.`,
160
434
  )
161
435
  }
436
+ if (!available.includes('screenshot')) return finish(lines, caps, available)
162
437
  lines.push(
163
- 'Coordinates are in logical pixels from the top-left of the primary display. Call getDisplayGeometry through screenshot output before clicking to confirm bounds.',
438
+ 'Coordinates: every x/y you send is a pixel of the most recent screenshot this tool returned — origin at its top-left, x to the right, y down — never a screen pixel. Each screenshot states its id and size (for example "s3: 1456x819"); stay inside it. The tool maps your coordinates onto the display. To aim at an earlier screenshot, pass its id as screenshot_id.',
439
+ 'Take a screenshot before your first click.',
164
440
  )
441
+ if (settings.screenshotAfterActions)
442
+ lines.push(
443
+ `After any action that changes something, the tool waits ${settings.settleMs} ms and returns a new screenshot — do not call screenshot again after acting.`,
444
+ )
445
+ lines.push(
446
+ 'zoom returns a closer, sharper view of region {x, y, width, height} of the screenshot; your coordinates still refer to the screenshot, not to the zoomed image.',
447
+ `wait pauses for ms milliseconds (at most ${settings.maxWaitMs} per call, a batch's waits together) and then shows the screen.`,
448
+ )
449
+ if (available.includes('batch'))
450
+ lines.push(
451
+ `batch: {"type":"batch","actions":[...]} runs up to ${settings.maxBatchActions} actions in order, stops at the first one that fails, and returns one screenshot at the end. Every call costs a model round trip of several seconds, so put the steps you can already predict into one batch (click a field, type, press ENTER) rather than one call each — but end the batch at any step that should bring up a new window, and look before typing into it. A batch cannot contain screenshot${available.includes('ui_snapshot') ? ', zoom or ui_snapshot' : ' or zoom'}.`,
452
+ )
453
+ if (
454
+ caps.displayServer === 'win32' &&
455
+ available.includes('key') &&
456
+ available.includes('type_text')
457
+ )
458
+ lines.push(
459
+ 'To start a Windows program, a shell command (Start-Process notepad, or cmd.exe /c start calc) is surest when you have a shell tool. The Start menu searches by display names in the system language, and ENTER on a name it does not find opens a web search in the browser.',
460
+ )
461
+ if (available.includes('list_windows'))
462
+ lines.push(
463
+ 'list_windows names the open windows with their ids; focus_window brings one to the front.',
464
+ )
465
+ if (available.includes('ui_snapshot'))
466
+ lines.push(
467
+ `ui_snapshot {window_id} reads a window's controls (its accessibility tree) as text, each control you can act on with a ref such as e12; without window_id it reads the window in front. ui_act {ref, action, value} acts on one control: invoke (press a button, open a menu item), set_value (replace a field's text with value), toggle, select, expand, collapse. Prefer ui_act to clicking pixels when the control is in the tree: it does not depend on coordinates or on which window is in front, and a batch of ui_act steps (for example pressing several buttons) runs in one call. Refs are valid only until the next ui_snapshot; take a new one after the window changes.`,
468
+ )
469
+ lines.push(
470
+ 'Typing and keys go to whichever window has focus: confirm on a screenshot that the right window is in front before type_text or key.',
471
+ )
472
+ if (available.includes('list_windows'))
473
+ lines.push(
474
+ 'type_text and key are refused while a terminal window is in front — usually the one running this agent, or the user’s own; bring the window you mean to the front first (focus_window, or click it).',
475
+ )
476
+ return finish(lines, caps, available)
477
+ }
478
+
479
+ function finish(
480
+ lines: string[],
481
+ caps: ComputerUseCapabilities,
482
+ available: readonly string[],
483
+ ): string {
165
484
  if (caps.mouseClickButtons && available.includes('mouse_click'))
166
485
  lines.push(`Click buttons: ${caps.mouseClickButtons.join(', ') || 'none'}.`)
167
486
  if (caps.mouseDragButtons && available.includes('mouse_drag'))
@@ -169,35 +488,164 @@ function buildDescription(host: ComputerUseHost): string {
169
488
  return lines.join(' ')
170
489
  }
171
490
 
172
- function availableActions(caps: ComputerUseCapabilities): ComputerUseAction['type'][] {
173
- return actionSchema.options
174
- .map((option) => option.shape.type.value)
175
- .filter((action) => {
176
- const required = requiredCapability(action)
177
- return (
178
- (required === null || caps[required] === true) &&
179
- (caps.supportedActions === undefined || caps.supportedActions.includes(action)) &&
180
- (action !== 'mouse_click' || caps.mouseClickButtons?.length !== 0) &&
181
- (action !== 'mouse_drag' || caps.mouseDragButtons?.length !== 0)
182
- )
183
- })
491
+ /**
492
+ * The provider-facing shape is deliberately flat.
493
+ *
494
+ * The runtime schema above is the authoritative contract: it knows which
495
+ * fields each action requires. Rendering that discriminated union produces a
496
+ * root `anyOf`, however, and some custom-tool wires reject root combinators
497
+ * even when every branch is an object. A model can still see every field and
498
+ * every action here; incomplete combinations are rejected by `actionSchema`
499
+ * before the host is called, with the recovery hint below.
500
+ */
501
+ function pointModelSchema(description?: string): Record<string, unknown> {
502
+ return {
503
+ type: 'object',
504
+ ...(description ? { description } : {}),
505
+ properties: {
506
+ x: { type: 'integer', description: 'Horizontal pixel coordinate, from the left edge.' },
507
+ y: { type: 'integer', description: 'Vertical pixel coordinate, from the top edge.' },
508
+ },
509
+ required: ['x', 'y'],
510
+ additionalProperties: false,
511
+ }
184
512
  }
185
513
 
186
- function hostModelSchema(caps: ComputerUseCapabilities): Record<string, unknown> {
187
- const schema = structuredClone(modelInputSchema)
188
- const actions = availableActions(caps)
514
+ const ACTION_REQUIREMENTS: Readonly<Record<ToolActionType, string>> = {
515
+ screenshot: 'screenshot needs no other fields',
516
+ zoom: 'zoom needs region',
517
+ cursor_position: 'cursor_position needs no other fields',
518
+ mouse_move: 'mouse_move needs to',
519
+ mouse_click: 'mouse_click needs at (button defaults to left)',
520
+ mouse_drag: 'mouse_drag needs from and to (button defaults to left)',
521
+ scroll: 'scroll needs at, direction, and amount',
522
+ type_text: 'type_text needs text',
523
+ key: 'key needs keys',
524
+ wait: 'wait needs ms',
525
+ list_windows: 'list_windows needs no other fields',
526
+ focus_window: 'focus_window needs window_id',
527
+ ui_snapshot: 'ui_snapshot takes an optional window_id',
528
+ ui_act: 'ui_act needs ref and action, and value for set_value',
529
+ batch: 'batch needs actions',
530
+ }
531
+
532
+ function hostModelSchema(
533
+ host: ComputerUseHost,
534
+ caps: ComputerUseCapabilities,
535
+ settings: ResolvedSettings,
536
+ ): Record<string, unknown> {
189
537
  // An unavailable host remains a diagnostic tool. Avoid invalid empty enums
190
538
  // on provider wires; every execution is still refused before host access.
191
- if (actions.length > 0) schema.properties.type.enum = actions
539
+ const offered = availableActions(host, caps)
540
+ const actions = offered.length > 0 ? offered : ALL_ACTIONS.filter((a) => a !== 'batch')
541
+ const items = actions.filter((action) => !OUTSIDE_BATCH.has(action))
192
542
  const buttons = new Set<string>()
193
543
  if (actions.includes('mouse_click'))
194
544
  for (const button of caps.mouseClickButtons ?? ['left', 'right', 'middle']) buttons.add(button)
195
545
  if (actions.includes('mouse_drag'))
196
546
  for (const button of caps.mouseDragButtons ?? ['left', 'right', 'middle']) buttons.add(button)
197
- if (buttons.size > 0) schema.properties.button.enum = [...buttons]
198
- return schema
547
+ const buttonEnum = buttons.size > 0 ? [...buttons] : ['left', 'right', 'middle']
548
+
549
+ const fieldSchemas = (forItems: boolean): Record<string, unknown> => {
550
+ const present = forItems ? items : actions
551
+ const fields: Record<string, unknown> = {
552
+ to: pointModelSchema(
553
+ 'Destination point: where mouse_move moves the cursor to, or where mouse_drag releases.',
554
+ ),
555
+ at: pointModelSchema('Point the action happens at: where to click, or where to scroll.'),
556
+ from: pointModelSchema('Drag start point, for mouse_drag.'),
557
+ button: { type: 'string', enum: buttonEnum, description: 'Mouse button; left when omitted.' },
558
+ direction: {
559
+ type: 'string',
560
+ enum: ['up', 'down', 'left', 'right'],
561
+ description: 'Which way to scroll.',
562
+ },
563
+ amount: { type: 'integer', description: 'Positive integer scroll distance.' },
564
+ text: { type: 'string', description: 'Literal text to type.' },
565
+ keys: {
566
+ type: 'string',
567
+ description: 'Key or key chord to press, for example ENTER or CTRL+R.',
568
+ },
569
+ ms: {
570
+ type: 'integer',
571
+ description: `Milliseconds to wait, 0 to ${settings.maxWaitMs}.`,
572
+ },
573
+ }
574
+ if (present.includes('focus_window') || (!forItems && present.includes('ui_snapshot')))
575
+ fields.window_id = { type: 'string', description: 'A window id from list_windows.' }
576
+ if (present.includes('ui_act')) {
577
+ fields.ref = {
578
+ type: 'string',
579
+ description: 'A ref from the latest ui_snapshot, such as e12.',
580
+ }
581
+ fields.action = {
582
+ type: 'string',
583
+ enum: [...UI_ACTIONS],
584
+ description: 'What ui_act does to the control.',
585
+ }
586
+ fields.value = { type: 'string', description: 'The text set_value puts in the control.' }
587
+ }
588
+ if (!forItems && present.includes('zoom'))
589
+ fields.region = {
590
+ type: 'object',
591
+ description: 'Area of the screenshot to zoom into, in its pixels.',
592
+ properties: {
593
+ x: { type: 'integer', description: 'Left edge of the region, in screenshot pixels.' },
594
+ y: { type: 'integer', description: 'Top edge of the region, in screenshot pixels.' },
595
+ width: { type: 'integer', description: 'Width of the region, in screenshot pixels.' },
596
+ height: { type: 'integer', description: 'Height of the region, in screenshot pixels.' },
597
+ },
598
+ required: ['x', 'y', 'width', 'height'],
599
+ additionalProperties: false,
600
+ }
601
+ return fields
602
+ }
603
+
604
+ const properties: Record<string, unknown> = {
605
+ type: {
606
+ type: 'string',
607
+ enum: actions,
608
+ description: `Desktop action. ${actions.map((action) => ACTION_REQUIREMENTS[action]).join('; ')}.`,
609
+ },
610
+ ...fieldSchemas(false),
611
+ }
612
+ if (actions.includes('batch'))
613
+ properties.actions = {
614
+ type: 'array',
615
+ minItems: 1,
616
+ maxItems: settings.maxBatchActions,
617
+ description: `For type batch: up to ${settings.maxBatchActions} actions run in order (no screenshot, zoom, ui_snapshot or batch inside).`,
618
+ items: {
619
+ type: 'object',
620
+ properties: {
621
+ type: {
622
+ type: 'string',
623
+ enum: items,
624
+ description: 'This batched action.',
625
+ },
626
+ ...fieldSchemas(true),
627
+ },
628
+ required: ['type'],
629
+ additionalProperties: false,
630
+ },
631
+ }
632
+ properties.screenshot_id = {
633
+ type: 'string',
634
+ description:
635
+ 'Optional id of the screenshot your coordinates were read from, such as s3. Defaults to the latest.',
636
+ }
637
+ return {
638
+ type: 'object',
639
+ properties,
640
+ required: ['type'],
641
+ additionalProperties: false,
642
+ }
199
643
  }
200
644
 
645
+ // ---------------------------------------------------------------------------
646
+ // Labels
647
+ // ---------------------------------------------------------------------------
648
+
201
649
  function pointLabel(point: { readonly x: number; readonly y: number }): string {
202
650
  return `(${point.x}, ${point.y})`
203
651
  }
@@ -208,11 +656,49 @@ function quotedText(value: string): string {
208
656
  return JSON.stringify(visible)
209
657
  }
210
658
 
211
- /** Human activity text; the raw action union remains the model-facing input. */
212
- function actionLabel(input: ActionInput): string {
659
+ function waitLabel(ms: number): string {
660
+ return ms >= 1000 ? `Wait ${Number((ms / 1000).toFixed(1))} s` : `Wait ${ms} ms`
661
+ }
662
+
663
+ /** What a ui_act does, as a verb phrase over the control's description. */
664
+ function uiActLabel(
665
+ input: { readonly ref: string; readonly action: UiElementAction; readonly value?: string },
666
+ describe?: (ref: string) => string | undefined,
667
+ ): string {
668
+ const target = describe?.(input.ref) ?? input.ref
669
+ switch (input.action) {
670
+ case 'invoke':
671
+ return `Press ${target}`
672
+ case 'set_value':
673
+ return `Set ${target} to ${quotedText(input.value ?? '')}`
674
+ case 'toggle':
675
+ return `Toggle ${target}`
676
+ case 'select':
677
+ return `Select ${target}`
678
+ case 'expand':
679
+ return `Expand ${target}`
680
+ case 'collapse':
681
+ return `Collapse ${target}`
682
+ case 'focus':
683
+ return `Focus ${target}`
684
+ case 'scroll_into_view':
685
+ return `Scroll to ${target}`
686
+ }
687
+ }
688
+
689
+ /**
690
+ * Human activity text for one action; the raw input remains the model-facing
691
+ * record. `describe` names a ui_act ref's control when the tool knows it.
692
+ */
693
+ function itemLabel(
694
+ input: BatchItem | ActionInput,
695
+ describe?: (ref: string) => string | undefined,
696
+ ): string {
213
697
  switch (input.type) {
214
698
  case 'screenshot':
215
699
  return 'Capture screenshot'
700
+ case 'zoom':
701
+ return `Zoom into ${input.region.width}x${input.region.height} at ${pointLabel(input.region)}`
216
702
  case 'cursor_position':
217
703
  return 'Read cursor position'
218
704
  case 'mouse_move':
@@ -227,33 +713,73 @@ function actionLabel(input: ActionInput): string {
227
713
  return `Type ${quotedText(input.text)}`
228
714
  case 'key':
229
715
  return `Press ${input.keys}`
716
+ case 'wait':
717
+ return waitLabel(input.ms)
718
+ case 'list_windows':
719
+ return 'List windows'
720
+ case 'focus_window':
721
+ return `Focus window ${input.window_id}`
722
+ case 'ui_snapshot':
723
+ return input.window_id
724
+ ? `Read the controls of window ${input.window_id}`
725
+ : 'Read the controls of the front window'
726
+ case 'ui_act':
727
+ return uiActLabel(input, describe)
728
+ case 'batch':
729
+ return batchLabel(input.actions, describe)
230
730
  }
231
731
  }
232
732
 
233
- function resultToToolResult(result: ComputerUseResult): ToolResult {
234
- switch (result.type) {
235
- case 'screenshot': {
236
- const { data, mimeType, width, height } = result.result
237
- // `output` used to BE the base64 payload, which meant the model
238
- // received 400 KB–2.7 MB of undecodable characters as text —
239
- // roughly 100k–670k tokens — and could not see the screen at
240
- // all. The image now travels as a content block; `output` keeps
241
- // the short human/transcript-facing description.
242
- return {
243
- success: true,
244
- output: `Screenshot captured (${width}x${height}, ${mimeType}).`,
245
- content: [{ type: 'image', data: data.toString('base64'), mediaType: mimeType }],
246
- data: { mimeType, width, height, encoding: 'base64' },
247
- }
248
- }
249
- case 'cursor_position':
250
- return {
251
- success: true,
252
- output: JSON.stringify(result.point),
253
- data: result.point,
254
- }
255
- case 'ok':
256
- return { success: true, output: 'ok' }
733
+ function batchLabel(
734
+ actions: readonly BatchItem[],
735
+ describe?: (ref: string) => string | undefined,
736
+ ): string {
737
+ const parts = actions.map((item) => itemLabel(item, describe))
738
+ const head = `${actions.length} desktop action${actions.length === 1 ? '' : 's'}`
739
+ const joined = parts.join(' · ')
740
+ return joined.length > 240 ? `${head}: ${joined.slice(0, 239)}…` : `${head}: ${joined}`
741
+ }
742
+
743
+ // ---------------------------------------------------------------------------
744
+ // Execution
745
+ // ---------------------------------------------------------------------------
746
+
747
+ interface ResolvedSettings {
748
+ readonly limits: ScreenshotLimits
749
+ readonly settleMs: number
750
+ readonly screenshotAfterActions: boolean
751
+ readonly maxBatchActions: number
752
+ readonly maxWaitMs: number
753
+ }
754
+
755
+ function resolveSettings(options: ComputerUseToolOptions): ResolvedSettings {
756
+ const count = (value: number | undefined, fallback: number, min: number, name: string) => {
757
+ if (value === undefined) return fallback
758
+ if (!Number.isSafeInteger(value) || value < min)
759
+ throw new RangeError(`createComputerUseTool: ${name} must be an integer ≥ ${min}`)
760
+ return value
761
+ }
762
+ const limits = options.screenshotLimits ?? STANDARD_SCREENSHOT_LIMITS
763
+ if (
764
+ !Number.isSafeInteger(limits.maxLongEdge) ||
765
+ !Number.isSafeInteger(limits.maxTiles) ||
766
+ limits.maxLongEdge < 28 ||
767
+ limits.maxTiles < 1
768
+ )
769
+ throw new RangeError(
770
+ 'createComputerUseTool: screenshotLimits needs an integer maxLongEdge ≥ 28 and maxTiles ≥ 1',
771
+ )
772
+ return {
773
+ limits,
774
+ settleMs: count(options.settleMs, DEFAULT_SETTLE_MS, 0, 'settleMs'),
775
+ screenshotAfterActions: options.screenshotAfterActions ?? true,
776
+ maxBatchActions: count(
777
+ options.maxBatchActions,
778
+ DEFAULT_MAX_BATCH_ACTIONS,
779
+ 1,
780
+ 'maxBatchActions',
781
+ ),
782
+ maxWaitMs: count(options.maxWaitMs, DEFAULT_MAX_WAIT_MS, 0, 'maxWaitMs'),
257
783
  }
258
784
  }
259
785
 
@@ -276,31 +802,133 @@ function isOutcomeUnknown(
276
802
  )
277
803
  }
278
804
 
279
- function unknownOutcomeToToolResult(error: ComputerUseOutcomeUnknown): ToolResult {
280
- return {
281
- success: false,
282
- output: '',
283
- error: error.message,
284
- data: {
285
- code: error.code,
286
- action: error.action,
287
- outcome: error.outcome,
288
- retrySafety: error.retrySafety,
289
- timedOut: error.timedOut,
290
- exitCode: error.exitCode,
291
- },
805
+ function errorText(error: unknown): string {
806
+ return error instanceof Error ? error.message : String(error)
807
+ }
808
+
809
+ /** One planned step: validated, coordinates already mapped, ready to run. */
810
+ interface PlannedStep {
811
+ readonly item: BatchItem
812
+ readonly label: string
813
+ readonly mutating: boolean
814
+ run(signal: AbortSignal | undefined): Promise<string>
815
+ }
816
+
817
+ type StepRecord =
818
+ | { readonly label: string; readonly status: 'done'; readonly note: string }
819
+ | {
820
+ readonly label: string
821
+ readonly status: 'failed'
822
+ readonly error: string
823
+ readonly unknown?: ComputerUseOutcomeUnknown
824
+ }
825
+ | { readonly label: string; readonly status: 'not run' }
826
+
827
+ class StepFailure extends Error {
828
+ constructor(
829
+ message: string,
830
+ readonly unknown?: ComputerUseOutcomeUnknown,
831
+ ) {
832
+ super(message)
292
833
  }
293
834
  }
294
835
 
836
+ interface Capture {
837
+ readonly frame: ScreenshotFrame
838
+ readonly image: FittedImage
839
+ }
840
+
841
+ /** A control of the latest ui_snapshot: the host's ref and what it was. */
842
+ interface UiRef {
843
+ readonly hostRef: string
844
+ readonly element: UiElement
845
+ }
846
+
847
+ /**
848
+ * The `computer_use` tool, with one question a host's review screen can ask
849
+ * of it.
850
+ */
851
+ export interface ComputerUseTool extends ToolDefinition<ActionInput> {
852
+ /**
853
+ * What a `ui_act` ref names in the latest `ui_snapshot` — `Button "Beş"
854
+ * (e30)` — or undefined for a ref it does not hold. For a host that shows
855
+ * a person the call before it runs; the words are the application's, so a
856
+ * host shows them as text and nothing more.
857
+ *
858
+ * @experimental Follows the UI-tree surface; see `ComputerUseCapabilities.uiTree`.
859
+ */
860
+ describeUiRef(ref: string): string | undefined
861
+ }
862
+
863
+ /** Whether a call sends the screen to the provider (see `ToolDefinition.capturesScreen`). */
864
+ function capturesScreenInput(input: unknown, screenshotAfterActions: boolean): boolean {
865
+ const items = batchActions(input) ?? [input]
866
+ return items.some((item) => {
867
+ if (!isRecord(item)) return true
868
+ const type = String(item.type)
869
+ if (SCREEN_OBSERVATIONS.has(type)) return true
870
+ if (type === 'cursor_position') return false
871
+ // Everything else is followed by a screenshot unless that is switched off.
872
+ return screenshotAfterActions
873
+ })
874
+ }
875
+
876
+ function safeRole(role: string): string {
877
+ return /^[A-Za-z][\w-]{0,39}$/.test(role) ? role : 'Element'
878
+ }
879
+
880
+ /** `Button "Beş"`: the control's role and name, one line, cut to fit. */
881
+ function uiElementText(element: UiElement): string {
882
+ const name = element.name.trim()
883
+ return name.length > 0 ? `${safeRole(element.role)} ${quotedText(name)}` : safeRole(element.role)
884
+ }
885
+
886
+ /**
887
+ * One line of a ui_snapshot: `[e12] Button "Beş" (disabled) [invoke] @(212, 488)`.
888
+ * The ref and position are the tool's; the rest is the application's, and
889
+ * sits inside the untrusted frame.
890
+ */
891
+ function uiElementLine(
892
+ element: UiElement,
893
+ ref: string | undefined,
894
+ frame: ScreenshotFrame | undefined,
895
+ ): string {
896
+ const parts = [`${ref ? `[${ref}] ` : ''}${uiElementText(element)}`]
897
+ if (element.value !== undefined && element.value !== element.name)
898
+ parts.push(`value=${quotedText(element.value)}`)
899
+ const states = (element.states ?? []).filter((state) => /^[a-z_]{1,24}$/.test(state))
900
+ if (states.length > 0) parts.push(`(${states.join(', ')})`)
901
+ if (ref) {
902
+ const actions = (element.actions ?? []).filter((action) =>
903
+ (UI_ACTIONS as readonly string[]).includes(action),
904
+ )
905
+ if (actions.length > 0) parts.push(`[${actions.join(', ')}]`)
906
+ const at = frame && element.bounds ? centreOnImage(frame, element.bounds) : undefined
907
+ if (at) parts.push(`@${pointLabel(at)}`)
908
+ }
909
+ return neutralizeEnvelopeDelimiter(parts.join(' '))
910
+ }
911
+
912
+ /** The centre of a virtual-desktop rectangle on a screenshot, or undefined when it is not on its display. */
913
+ function centreOnImage(frame: ScreenshotFrame, bounds: Rect): Point | undefined {
914
+ const x = Math.floor(bounds.x + bounds.width / 2) - frame.display.x
915
+ const y = Math.floor(bounds.y + bounds.height / 2) - frame.display.y
916
+ if (x < 0 || y < 0 || x >= frame.display.width || y >= frame.display.height) return undefined
917
+ return toImagePoint(frame, { x, y })
918
+ }
919
+
295
920
  /**
296
921
  * Factory: given a ComputerUseHost (provided by the consumer — e.g.
297
- * @namzu/computer-use's SubprocessComputerUseHost), returns a ToolDefinition
298
- * that routes the discriminated action to the host and maps results back to
299
- * the SDK's ToolResult shape.
922
+ * @namzu/computer-use's SubprocessComputerUseHost), returns the
923
+ * `computer_use` tool.
300
924
  *
301
- * The tool's description reflects the host's frozen capabilities, and any
302
- * action targeting an unavailable capability is rejected with a clear error
303
- * rather than hanging or failing silently.
925
+ * Every screenshot is fitted to the model's image limits and numbered;
926
+ * every coordinate the model sends is a pixel of one of those screenshots and
927
+ * is mapped onto the host's display. Actions that change something return a
928
+ * fresh screenshot after a short settle delay, and `batch` runs several in
929
+ * one call. The description and schema reflect the host's frozen
930
+ * capabilities, and any action targeting an unavailable capability is
931
+ * rejected before the host is touched.
304
932
  *
305
933
  * @example
306
934
  * ```ts
@@ -312,67 +940,743 @@ function unknownOutcomeToToolResult(error: ComputerUseOutcomeUnknown): ToolResul
312
940
  * registry.register(createComputerUseTool(host))
313
941
  * ```
314
942
  */
315
- export function createComputerUseTool(host: ComputerUseHost): ToolDefinition<ActionInput> {
316
- return defineTool({
943
+ export function createComputerUseTool(
944
+ host: ComputerUseHost,
945
+ options: ComputerUseToolOptions = {},
946
+ ): ComputerUseTool {
947
+ const settings = resolveSettings(options)
948
+ const caps: ComputerUseCapabilities = options.unavailableReason
949
+ ? {
950
+ ...host.capabilities,
951
+ screenshot: false,
952
+ mouse: false,
953
+ keyboard: false,
954
+ cursorPosition: false,
955
+ clipboard: false,
956
+ windows: false,
957
+ regionCapture: false,
958
+ uiTree: false,
959
+ unavailableReason: options.unavailableReason,
960
+ }
961
+ : host.capabilities
962
+ const frames = new ScreenshotFrames()
963
+ const available = new Set<string>(availableActions(host, caps))
964
+
965
+ // The controls of the latest ui_snapshot, by the ref the model was shown.
966
+ // Refs count up across snapshots, so a ref from an earlier one is never
967
+ // silently a different control of the latest: it is simply not here.
968
+ let uiRefs = new Map<string, UiRef>()
969
+ let uiSnapshots = 0
970
+ let uiRefCount = 0
971
+ const describeUiRef = (ref: string): string | undefined => {
972
+ const entry = uiRefs.get(ref)
973
+ return entry ? `${uiElementText(entry.element)} (${ref})` : undefined
974
+ }
975
+ const label = (input: BatchItem | ActionInput): string => itemLabel(input, describeUiRef)
976
+
977
+ const refusal = (type: string): string | null => {
978
+ const required = requiredCapability(type)
979
+ const why = caps.unavailableReason
980
+ ? ` ${caps.unavailableReason} Do not retry; tell the user.`
981
+ : ''
982
+ if (required !== null && caps[required] !== true)
983
+ return `computer_use: action "${type}" requires capability "${required}" which is not available on this host (displayServer=${caps.displayServer}).${why}`
984
+ if (!available.has(type))
985
+ return `computer_use: action "${type}" is not supported on this host.${why}`
986
+ return null
987
+ }
988
+
989
+ /** Capture the display, fit it, and number it. */
990
+ const capture = async (): Promise<Capture> => {
991
+ const result = await host.execute({ type: 'screenshot' })
992
+ if (result.type !== 'screenshot')
993
+ throw new Error(`computer_use: the host answered a screenshot with "${result.type}"`)
994
+ const shot = result.result
995
+ const image = await fitPng(shot.data, settings.limits)
996
+ const display = shot.display ?? assumedDisplay(image.sourceWidth, image.sourceHeight)
997
+ return { frame: frames.record(image, display), image }
998
+ }
999
+
1000
+ const frameFor = (id: string | undefined): ScreenshotFrame => {
1001
+ if (id !== undefined) {
1002
+ const frame = frames.get(id)
1003
+ if (!frame)
1004
+ throw new StepFailure(
1005
+ `there is no screenshot ${id} (the latest is ${frames.latest()?.id ?? 'none'}); take a screenshot and use its coordinates`,
1006
+ )
1007
+ return frame
1008
+ }
1009
+ const latest = frames.latest()
1010
+ if (!latest)
1011
+ throw new StepFailure(
1012
+ 'no screenshot has been taken yet, so there is nothing for coordinates to refer to; take a screenshot first',
1013
+ )
1014
+ return latest
1015
+ }
1016
+
1017
+ /**
1018
+ * Keys go to whichever window has focus, and a model that has not looked
1019
+ * does not know which one that is — the terminal running this agent is
1020
+ * the usual answer. When the host can show the screen, look first.
1021
+ */
1022
+ const requireLook = (): void => {
1023
+ if (available.has('screenshot') && !frames.latest())
1024
+ throw new StepFailure(
1025
+ 'no screenshot has been taken yet, so nothing shows which window has focus and would receive the keys; take a screenshot first',
1026
+ )
1027
+ }
1028
+
1029
+ const mapPoint = (frame: ScreenshotFrame, point: Point, field: string): Point => {
1030
+ if (!pointOnImage(frame, point))
1031
+ throw new StepFailure(
1032
+ `${field} ${pointLabel(point)} is outside screenshot ${frame.id}, which is ${frame.imageWidth}x${frame.imageHeight} (x 0–${frame.imageWidth - 1}, y 0–${frame.imageHeight - 1}); coordinates are pixels of the screenshot, not of the screen`,
1033
+ )
1034
+ return toDisplayPoint(frame, point)
1035
+ }
1036
+
1037
+ const runHost = async (action: ComputerUseAction): Promise<string> => {
1038
+ try {
1039
+ const result = await host.execute(action)
1040
+ if (result.type === 'cursor_position') {
1041
+ const frame = frames.latest()
1042
+ if (!frame) return `at display pixel ${pointLabel(result.point)}`
1043
+ return `at ${pointLabel(toImagePoint(frame, result.point))} in ${frame.id}`
1044
+ }
1045
+ return 'done'
1046
+ } catch (error) {
1047
+ if (isOutcomeUnknown(error, action.type)) throw new StepFailure(error.message, error)
1048
+ throw new StepFailure(errorText(error))
1049
+ }
1050
+ }
1051
+
1052
+ const plan = (item: BatchItem, frameId: string | undefined): PlannedStep => {
1053
+ const denied = refusal(item.type)
1054
+ if (denied) throw new StepFailure(denied)
1055
+ const mutating = !READ_ONLY_ACTIONS.has(item.type)
1056
+ const step = (run: PlannedStep['run']): PlannedStep => ({
1057
+ item,
1058
+ label: label(item),
1059
+ mutating,
1060
+ run,
1061
+ })
1062
+ switch (item.type) {
1063
+ case 'cursor_position':
1064
+ frameFor(frameId)
1065
+ return step(() => runHost({ type: 'cursor_position' }))
1066
+ case 'mouse_move': {
1067
+ const frame = frameFor(frameId)
1068
+ const to = mapPoint(frame, item.to, 'to')
1069
+ return step(() => runHost({ type: 'mouse_move', to }))
1070
+ }
1071
+ case 'mouse_click': {
1072
+ const buttons = caps.mouseClickButtons
1073
+ if (buttons && !buttons.includes(item.button))
1074
+ throw new StepFailure(
1075
+ `computer_use: action "mouse_click" does not support button "${item.button}" on this host.`,
1076
+ )
1077
+ const frame = frameFor(frameId)
1078
+ const at = mapPoint(frame, item.at, 'at')
1079
+ return step(() => runHost({ type: 'mouse_click', at, button: item.button }))
1080
+ }
1081
+ case 'mouse_drag': {
1082
+ const buttons = caps.mouseDragButtons
1083
+ if (buttons && !buttons.includes(item.button))
1084
+ throw new StepFailure(
1085
+ `computer_use: action "mouse_drag" does not support button "${item.button}" on this host.`,
1086
+ )
1087
+ const frame = frameFor(frameId)
1088
+ const from = mapPoint(frame, item.from, 'from')
1089
+ const to = mapPoint(frame, item.to, 'to')
1090
+ return step(() => runHost({ type: 'mouse_drag', from, to, button: item.button }))
1091
+ }
1092
+ case 'scroll': {
1093
+ const frame = frameFor(frameId)
1094
+ const at = mapPoint(frame, item.at, 'at')
1095
+ return step(() =>
1096
+ runHost({ type: 'scroll', at, direction: item.direction, amount: item.amount }),
1097
+ )
1098
+ }
1099
+ case 'type_text':
1100
+ requireLook()
1101
+ return step(() => runHost({ type: 'type_text', text: item.text }))
1102
+ case 'key':
1103
+ requireLook()
1104
+ return step(() => runHost({ type: 'key', keys: item.keys }))
1105
+ case 'wait':
1106
+ return step(async (signal) => {
1107
+ await sleep(item.ms, signal)
1108
+ return 'done'
1109
+ })
1110
+ case 'list_windows':
1111
+ return step(() => listWindows())
1112
+ case 'focus_window':
1113
+ return step(async () => {
1114
+ let outcome: Awaited<ReturnType<NonNullable<ComputerUseHost['focusWindow']>>>
1115
+ try {
1116
+ outcome = await (host.focusWindow as NonNullable<ComputerUseHost['focusWindow']>)(
1117
+ item.window_id,
1118
+ )
1119
+ } catch (error) {
1120
+ throw new StepFailure(errorText(error))
1121
+ }
1122
+ if (!outcome.ok)
1123
+ throw new StepFailure(
1124
+ `window ${item.window_id} could not be brought to the front; ${outcome.focusedId ? `window ${outcome.focusedId} is in front` : 'the window in front could not be read'}`,
1125
+ )
1126
+ return 'done'
1127
+ })
1128
+ case 'ui_act': {
1129
+ const entry = uiRefs.get(item.ref)
1130
+ if (!entry)
1131
+ throw new StepFailure(
1132
+ uiSnapshots === 0
1133
+ ? `there is no ${item.ref}: no ui_snapshot has been taken yet; take one and use its refs`
1134
+ : `${item.ref} is not a control of the latest ui_snapshot (u${uiSnapshots}); refs are valid only until the next ui_snapshot, so use the refs it showed`,
1135
+ )
1136
+ const offered = entry.element.actions
1137
+ if (offered && offered.length > 0 && !offered.includes(item.action))
1138
+ throw new StepFailure(
1139
+ `${describeUiRef(item.ref)} offers ${offered.join(', ')}, not ${item.action}`,
1140
+ )
1141
+ if (item.action === 'set_value' && item.value === undefined)
1142
+ throw new StepFailure('set_value needs value, the text to put in the control')
1143
+ return step(async () => {
1144
+ let outcome: UiActResult
1145
+ try {
1146
+ outcome = await (host.uiAct as NonNullable<ComputerUseHost['uiAct']>)(
1147
+ entry.hostRef,
1148
+ item.action,
1149
+ item.value,
1150
+ )
1151
+ } catch (error) {
1152
+ throw new StepFailure(errorText(error))
1153
+ }
1154
+ if (!outcome.ok)
1155
+ throw new StepFailure(
1156
+ outcome.detail ?? `${item.action} did not take effect on ${describeUiRef(item.ref)}`,
1157
+ )
1158
+ return outcome.detail ? `done (${outcome.detail})` : 'done'
1159
+ })
1160
+ }
1161
+ }
1162
+ }
1163
+
1164
+ /** Read one window's controls, number the ones the model can act on, and show them. */
1165
+ const uiSnapshot = async (
1166
+ input: Extract<ActionInput, { type: 'ui_snapshot' }>,
1167
+ ): Promise<ToolResult> => {
1168
+ let snapshot: UiSnapshot
1169
+ try {
1170
+ snapshot = await (host.uiSnapshot as NonNullable<ComputerUseHost['uiSnapshot']>)(
1171
+ input.window_id,
1172
+ )
1173
+ } catch (error) {
1174
+ throw new StepFailure(errorText(error))
1175
+ }
1176
+ uiSnapshots += 1
1177
+ const id = `u${uiSnapshots}`
1178
+ const refs = new Map<string, UiRef>()
1179
+ const frame = frames.latest()
1180
+ const lines: string[] = []
1181
+ let chars = 0
1182
+ let shown = 0
1183
+ let total = 0
1184
+ let cut = false
1185
+ const visit = (element: UiElement, depth: number): void => {
1186
+ total += 1
1187
+ const actionable = element.ref.length > 0
1188
+ const children = element.children ?? []
1189
+ // A nameless control nobody can act on says nothing by itself; its
1190
+ // children still stand, one level up.
1191
+ const silent = !actionable && element.name.trim().length === 0 && element.value === undefined
1192
+ if (!silent && !cut) {
1193
+ const ref = actionable ? `e${uiRefCount + 1}` : undefined
1194
+ const line = `${' '.repeat(depth)}${uiElementLine(element, ref, frame)}`
1195
+ if (chars + line.length + 1 > MAX_UI_SNAPSHOT_CHARS) {
1196
+ cut = true
1197
+ } else {
1198
+ lines.push(line)
1199
+ chars += line.length + 1
1200
+ shown += 1
1201
+ if (ref) {
1202
+ uiRefCount += 1
1203
+ refs.set(ref, { hostRef: element.ref, element })
1204
+ }
1205
+ }
1206
+ }
1207
+ for (const child of children) visit(child, silent ? depth : depth + 1)
1208
+ }
1209
+ visit(snapshot.root, 0)
1210
+ uiRefs = refs
1211
+ const window =
1212
+ snapshot.windowId && /^[\w.:-]{1,64}$/.test(snapshot.windowId) ? snapshot.windowId : undefined
1213
+ const header = [
1214
+ `UI snapshot ${id}${window ? ` of window ${window}` : ''}: ${shown} controls shown, ${refs.size} with a ref you can pass to ui_act. Refs are valid until the next ui_snapshot.`,
1215
+ ...(frame
1216
+ ? [
1217
+ `@(x, y) is a control's centre on screenshot ${frame.id}, for a click when ui_act cannot reach it.`,
1218
+ ]
1219
+ : []),
1220
+ ]
1221
+ // The window's title and application, when the tree's own root does not
1222
+ // already say them; inside the frame, since an application sets both.
1223
+ const about = [
1224
+ ...(snapshot.app !== undefined ? [`Application ${quotedText(snapshot.app)}`] : []),
1225
+ ...(snapshot.title !== undefined && snapshot.title !== snapshot.root.name
1226
+ ? [`Window title ${quotedText(snapshot.title)}`]
1227
+ : []),
1228
+ ]
1229
+ const body = [...about, ...lines].join('\n')
1230
+ const footer =
1231
+ cut || snapshot.truncated
1232
+ ? [
1233
+ cut
1234
+ ? `The tree was cut at ${MAX_UI_SNAPSHOT_CHARS} characters after ${shown} of ${total} controls. Read a smaller window, or use a screenshot for the rest.`
1235
+ : 'The host stopped reading the tree before it ended; controls further down are missing. Use a screenshot for the rest.',
1236
+ ]
1237
+ : []
1238
+ const text = [
1239
+ ...header,
1240
+ wrapUntrusted(
1241
+ {
1242
+ kind: 'desktop-ui',
1243
+ ...(window ? { attributes: { window } } : {}),
1244
+ provenance:
1245
+ "The accessibility tree of a window on the user's desktop, as the host read it. Names and values are whatever the application shows, which can include text anyone wrote.",
1246
+ },
1247
+ body,
1248
+ ),
1249
+ ...footer,
1250
+ ].join('\n')
1251
+ return {
1252
+ success: true,
1253
+ output: header[0] ?? '',
1254
+ content: [{ type: 'text', text }],
1255
+ data: {
1256
+ uiSnapshot: {
1257
+ id,
1258
+ ...(window ? { windowId: window } : {}),
1259
+ controls: shown,
1260
+ refs: refs.size,
1261
+ truncated: cut || snapshot.truncated === true,
1262
+ },
1263
+ },
1264
+ }
1265
+ }
1266
+
1267
+ /**
1268
+ * Keys and text go to the window in front, and a terminal there — the
1269
+ * one running this agent, or the user's own — turns a model's typing into
1270
+ * a command line: ENTER runs it or sends it. A session had a WIN+R that
1271
+ * did not open the Run dialog type "notepad" and ENTER into the user's
1272
+ * terminal, and submitted their half-typed message. So with a window list
1273
+ * the tool reads what is in front before typing, and refuses a terminal.
1274
+ * Without one it cannot tell, and the description's advice stands alone.
1275
+ */
1276
+ const refuseTerminalInFront = async (): Promise<void> => {
1277
+ if (!available.has('list_windows')) return
1278
+ let windows: readonly WindowInfo[]
1279
+ try {
1280
+ windows = await (host.listWindows as NonNullable<ComputerUseHost['listWindows']>)()
1281
+ } catch (error) {
1282
+ throw new StepFailure(
1283
+ `the window in front could not be read, so nothing was typed: ${errorText(error)}`,
1284
+ )
1285
+ }
1286
+ const front = windows.find((window) => window.focused)
1287
+ if (front && TERMINAL_APPS.test(front.app))
1288
+ throw new StepFailure(
1289
+ `a terminal is in front (${front.app}, window ${front.id}), so nothing was typed: computer_use never sends keys or text to a terminal. Bring the window you mean to the front (focus_window, or click it) and try again; for a command, use a shell tool instead`,
1290
+ )
1291
+ }
1292
+
1293
+ const listWindows = async (): Promise<string> => {
1294
+ let windows: readonly WindowInfo[]
1295
+ try {
1296
+ windows = await (host.listWindows as NonNullable<ComputerUseHost['listWindows']>)()
1297
+ } catch (error) {
1298
+ throw new StepFailure(errorText(error))
1299
+ }
1300
+ if (windows.length === 0) return 'no windows are open'
1301
+ const frame = frames.latest()
1302
+ const lines = windows.slice(0, MAX_LISTED_WINDOWS).map((window) => {
1303
+ const where = frame
1304
+ ? (() => {
1305
+ const rect = window.minimized ? null : desktopRectOnImage(frame, window.bounds)
1306
+ return rect
1307
+ ? `at ${pointLabel(rect)} ${rect.width}x${rect.height} in ${frame.id}`
1308
+ : window.minimized
1309
+ ? 'minimized'
1310
+ : `not on the display of ${frame.id}`
1311
+ })()
1312
+ : window.minimized
1313
+ ? 'minimized'
1314
+ : ''
1315
+ return [
1316
+ `- ${window.id}`,
1317
+ JSON.stringify(window.title),
1318
+ `${window.app} (pid ${window.pid})`,
1319
+ ...(window.focused ? ['focused'] : []),
1320
+ ...(where ? [where] : []),
1321
+ ].join(' · ')
1322
+ })
1323
+ const more =
1324
+ windows.length > MAX_LISTED_WINDOWS
1325
+ ? [`… and ${windows.length - MAX_LISTED_WINDOWS} more`]
1326
+ : []
1327
+ // Titles are whatever an application puts there — a web page's title in
1328
+ // a browser window — so the list is framed as material, not direction.
1329
+ return `${windows.length} window${windows.length === 1 ? '' : 's'}, front to back:\n${wrapUntrusted(
1330
+ {
1331
+ kind: 'desktop-windows',
1332
+ provenance:
1333
+ "The open windows on the user's desktop, as the host listed them. Titles are whatever each application shows, which can include text anyone wrote.",
1334
+ },
1335
+ [...lines, ...more].join('\n'),
1336
+ )}`
1337
+ }
1338
+
1339
+ const describeFrame = (frame: ScreenshotFrame): string => {
1340
+ const { display } = frame
1341
+ const scaled = frame.imageWidth !== display.width || frame.imageHeight !== display.height
1342
+ return scaled
1343
+ ? `Screenshot ${frame.id}: ${frame.imageWidth}x${frame.imageHeight} pixels, showing the ${display.width}x${display.height} display. Send coordinates in this image's pixels (x 0–${frame.imageWidth - 1}, y 0–${frame.imageHeight - 1}); the tool maps them onto the display.`
1344
+ : `Screenshot ${frame.id}: ${frame.imageWidth}x${frame.imageHeight} pixels, the display at full size. Send coordinates in this image's pixels (x 0–${frame.imageWidth - 1}, y 0–${frame.imageHeight - 1}).`
1345
+ }
1346
+
1347
+ const frameData = (frame: ScreenshotFrame) => ({
1348
+ id: frame.id,
1349
+ width: frame.imageWidth,
1350
+ height: frame.imageHeight,
1351
+ display: frame.display,
1352
+ mimeType: 'image/png' as const,
1353
+ encoding: 'base64' as const,
1354
+ })
1355
+
1356
+ const pin = (frame: ScreenshotFrame): NonNullable<ToolResult['workingState']> => [
1357
+ {
1358
+ key: 'computer_use.screenshot',
1359
+ text: `computer_use coordinates are pixels of screenshot ${frame.id} (${frame.imageWidth}x${frame.imageHeight}), origin top-left.`,
1360
+ },
1361
+ ]
1362
+
1363
+ const imageBlock = (image: FittedImage): ToolResultBlock => ({
1364
+ type: 'image',
1365
+ data: image.data.toString('base64'),
1366
+ mediaType: 'image/png',
1367
+ })
1368
+
1369
+ // Said once, beside the first screenshot, where it is read: a model that
1370
+ // has just seen the screen reaches for pixels unless told the host can
1371
+ // name the controls.
1372
+ const firstLookHint = (frame: ScreenshotFrame): string[] =>
1373
+ frame.id === 's1' && available.has('ui_snapshot')
1374
+ ? [
1375
+ 'This host can also read a window’s controls: list_windows, then ui_snapshot {window_id}, then ui_act by ref (a batch of them for several buttons) — surer than clicking pixels in an ordinary application.',
1376
+ ]
1377
+ : []
1378
+
1379
+ const screenshotResult = (shot: Capture): ToolResult => ({
1380
+ success: true,
1381
+ output: `Screenshot ${shot.frame.id} captured (${shot.frame.imageWidth}x${shot.frame.imageHeight} of the ${shot.frame.display.width}x${shot.frame.display.height} display).`,
1382
+ content: [
1383
+ { type: 'text', text: [describeFrame(shot.frame), ...firstLookHint(shot.frame)].join('\n') },
1384
+ imageBlock(shot.image),
1385
+ ],
1386
+ data: { screenshot: frameData(shot.frame) },
1387
+ workingState: pin(shot.frame),
1388
+ })
1389
+
1390
+ const zoom = async (input: Extract<ActionInput, { type: 'zoom' }>): Promise<ToolResult> => {
1391
+ const frame = frameFor(input.screenshot_id)
1392
+ const { region } = input
1393
+ const onImage = {
1394
+ x: Math.max(0, region.x),
1395
+ y: Math.max(0, region.y),
1396
+ width: Math.min(region.x + region.width, frame.imageWidth) - Math.max(0, region.x),
1397
+ height: Math.min(region.y + region.height, frame.imageHeight) - Math.max(0, region.y),
1398
+ }
1399
+ if (onImage.width <= 0 || onImage.height <= 0)
1400
+ throw new StepFailure(
1401
+ `region ${region.width}x${region.height} at ${pointLabel(region)} is outside screenshot ${frame.id} (${frame.imageWidth}x${frame.imageHeight})`,
1402
+ )
1403
+ const rect = toDisplayRect(frame, onImage) as Rect
1404
+ let fitted: FittedImage
1405
+ if (caps.regionCapture === true && typeof host.captureRegion === 'function') {
1406
+ const piece = await host.captureRegion(rect)
1407
+ fitted = await fitPng(piece.data, settings.limits)
1408
+ } else {
1409
+ const result = await host.execute({ type: 'screenshot' })
1410
+ if (result.type !== 'screenshot')
1411
+ throw new Error(`computer_use: the host answered a screenshot with "${result.type}"`)
1412
+ const full: ScreenshotResult = result.result
1413
+ const size = pngSize(full.data)
1414
+ const display = full.display ?? assumedDisplay(size.width, size.height)
1415
+ if (display.width !== frame.display.width || display.height !== frame.display.height)
1416
+ throw new StepFailure(
1417
+ `the display is now ${display.width}x${display.height}, not what ${frame.id} showed; take a new screenshot`,
1418
+ )
1419
+ // The capture's own pixels, in case a host captures at another scale than it reports.
1420
+ const sx = size.width / display.width
1421
+ const sy = size.height / display.height
1422
+ const left = Math.floor(rect.x * sx)
1423
+ const top = Math.floor(rect.y * sy)
1424
+ fitted = await cropAndFitPng(
1425
+ full.data,
1426
+ {
1427
+ x: left,
1428
+ y: top,
1429
+ width: Math.max(1, Math.min(size.width, Math.ceil((rect.x + rect.width) * sx)) - left),
1430
+ height: Math.max(1, Math.min(size.height, Math.ceil((rect.y + rect.height) * sy)) - top),
1431
+ },
1432
+ settings.limits,
1433
+ )
1434
+ }
1435
+ const detail = fitted.width / onImage.width
1436
+ const text = `Zoom of ${onImage.width}x${onImage.height} at ${pointLabel(onImage)} in ${frame.id}, shown at ${fitted.width}x${fitted.height} (${detail.toFixed(1)}x the detail of ${frame.id}). Coordinates for actions still refer to ${frame.id} (${frame.imageWidth}x${frame.imageHeight}), not to this image.`
1437
+ return {
1438
+ success: true,
1439
+ output: `Zoomed into ${onImage.width}x${onImage.height} at ${pointLabel(onImage)} of ${frame.id} (shown at ${fitted.width}x${fitted.height}).`,
1440
+ content: [{ type: 'text', text }, imageBlock(fitted)],
1441
+ data: {
1442
+ zoom: {
1443
+ screenshot: frame.id,
1444
+ region: onImage,
1445
+ width: fitted.width,
1446
+ height: fitted.height,
1447
+ mimeType: 'image/png',
1448
+ encoding: 'base64',
1449
+ },
1450
+ },
1451
+ }
1452
+ }
1453
+
1454
+ /** Run actions in order, stop at the first failure, then show the screen once. */
1455
+ const runSteps = async (
1456
+ items: readonly BatchItem[],
1457
+ frameId: string | undefined,
1458
+ context: ToolContext | undefined,
1459
+ single: boolean,
1460
+ ): Promise<ToolResult> => {
1461
+ if (items.length > settings.maxBatchActions)
1462
+ return {
1463
+ success: false,
1464
+ output: '',
1465
+ error: `computer_use: a batch carries at most ${settings.maxBatchActions} actions; got ${items.length}. Nothing was run.`,
1466
+ }
1467
+ // Waits share one budget, so a batch stays well inside the tool's
1468
+ // deadline however its waits are split.
1469
+ const waited = items.reduce((total, item) => total + (item.type === 'wait' ? item.ms : 0), 0)
1470
+ if (waited > settings.maxWaitMs)
1471
+ return {
1472
+ success: false,
1473
+ output: '',
1474
+ error: `computer_use: ${single ? 'wait is' : "a batch's waits add up to"} at most ${settings.maxWaitMs} ms; got ${waited}. Nothing was run.`,
1475
+ }
1476
+ // Plan everything before doing anything: a refusal or a coordinate off
1477
+ // the screenshot in step 5 should not arrive after steps 1–4 changed
1478
+ // the desktop.
1479
+ const planned: PlannedStep[] = []
1480
+ for (const [index, item] of items.entries()) {
1481
+ try {
1482
+ planned.push(plan(item, frameId))
1483
+ } catch (error) {
1484
+ const message = errorText(error)
1485
+ return {
1486
+ success: false,
1487
+ output: '',
1488
+ error: single
1489
+ ? message
1490
+ : `computer_use: action ${index + 1} of ${items.length} (${label(item)}) cannot run: ${message}. Nothing was run.`,
1491
+ }
1492
+ }
1493
+ }
1494
+
1495
+ const signal = context?.abortSignal
1496
+ const records: StepRecord[] = []
1497
+ let changed = false
1498
+ let failure: { index: number; error: string; unknown?: ComputerUseOutcomeUnknown } | undefined
1499
+ // Whether the window in front was checked since the last step that could
1500
+ // have changed it. Typing does not move focus; everything else may.
1501
+ let frontChecked = false
1502
+ for (const [index, step] of planned.entries()) {
1503
+ if (signal?.aborted) {
1504
+ failure = { index, error: 'cancelled before it started' }
1505
+ records.push({ label: step.label, status: 'failed', error: failure.error })
1506
+ break
1507
+ }
1508
+ try {
1509
+ const keyboard = step.item.type === 'type_text' || step.item.type === 'key'
1510
+ if (keyboard && !frontChecked) {
1511
+ await refuseTerminalInFront()
1512
+ frontChecked = true
1513
+ }
1514
+ if (step.item.type !== 'type_text') frontChecked = false
1515
+ const note = await step.run(signal)
1516
+ records.push({ label: step.label, status: 'done', note })
1517
+ if (step.mutating) changed = true
1518
+ } catch (error) {
1519
+ const unknown = error instanceof StepFailure ? error.unknown : undefined
1520
+ const message = signal?.aborted ? 'cancelled' : errorText(error)
1521
+ failure = { index, error: message, ...(unknown ? { unknown } : {}) }
1522
+ records.push({
1523
+ label: step.label,
1524
+ status: 'failed',
1525
+ error: message,
1526
+ ...(unknown ? { unknown } : {}),
1527
+ })
1528
+ if (unknown) changed = true
1529
+ break
1530
+ }
1531
+ }
1532
+ for (const step of planned.slice(records.length))
1533
+ records.push({ label: step.label, status: 'not run' })
1534
+
1535
+ // The screen after the batch: after anything that may have changed it,
1536
+ // and after a wait, whose whole point is to look again.
1537
+ const looks =
1538
+ settings.screenshotAfterActions &&
1539
+ available.has('screenshot') &&
1540
+ (changed || planned.some((step) => step.item.type === 'wait')) &&
1541
+ !signal?.aborted
1542
+ let shot: Capture | undefined
1543
+ let shotError: string | undefined
1544
+ if (looks) {
1545
+ try {
1546
+ await sleep(settings.settleMs, signal)
1547
+ shot = await capture()
1548
+ } catch (error) {
1549
+ shotError = signal?.aborted ? 'cancelled' : errorText(error)
1550
+ }
1551
+ }
1552
+
1553
+ const summary = (record: StepRecord, index: number): string => {
1554
+ const n = single ? '' : `${index + 1}. `
1555
+ switch (record.status) {
1556
+ case 'done':
1557
+ return `${n}${record.label}: ${record.note}`
1558
+ case 'failed':
1559
+ return `${n}${record.label}: failed — ${record.error}`
1560
+ case 'not run':
1561
+ return `${n}${record.label}: not run`
1562
+ }
1563
+ }
1564
+ const header = single
1565
+ ? []
1566
+ : failure
1567
+ ? [
1568
+ `Batch stopped at action ${failure.index + 1} of ${planned.length}; ${planned.length - failure.index - 1} not run.`,
1569
+ ]
1570
+ : [`Batch: all ${planned.length} actions done.`]
1571
+ const stepLines = records.map(summary)
1572
+ const shotLines = shot
1573
+ ? [describeFrame(shot.frame)]
1574
+ : shotError
1575
+ ? [`The screenshot after acting failed: ${shotError}. Take one before acting again.`]
1576
+ : []
1577
+ const unknownLines = failure?.unknown ? [failure.unknown.message] : []
1578
+ const text = [...header, ...stepLines, ...unknownLines, ...shotLines].join('\n')
1579
+ const output = [
1580
+ ...header,
1581
+ ...stepLines,
1582
+ ...(shot
1583
+ ? [`Screenshot ${shot.frame.id} (${shot.frame.imageWidth}x${shot.frame.imageHeight}).`]
1584
+ : []),
1585
+ ].join('\n')
1586
+ const data = {
1587
+ steps: records.map((record) =>
1588
+ record.status === 'failed'
1589
+ ? { label: record.label, status: record.status, error: record.error }
1590
+ : { label: record.label, status: record.status },
1591
+ ),
1592
+ ...(shot ? { screenshot: frameData(shot.frame) } : {}),
1593
+ ...(failure?.unknown
1594
+ ? {
1595
+ code: failure.unknown.code,
1596
+ action: failure.unknown.action,
1597
+ outcome: failure.unknown.outcome,
1598
+ retrySafety: failure.unknown.retrySafety,
1599
+ timedOut: failure.unknown.timedOut,
1600
+ exitCode: failure.unknown.exitCode,
1601
+ }
1602
+ : {}),
1603
+ }
1604
+ const content: ToolResultBlock[] = [{ type: 'text', text }]
1605
+ if (shot) content.push(imageBlock(shot.image))
1606
+ const result: ToolResult = {
1607
+ success: failure === undefined,
1608
+ output,
1609
+ content,
1610
+ data,
1611
+ ...(shot ? { workingState: pin(shot.frame) } : {}),
1612
+ }
1613
+ if (failure)
1614
+ result.error = failure.unknown
1615
+ ? failure.unknown.message
1616
+ : single
1617
+ ? `computer_use failed: ${failure.error}`
1618
+ : `Batch stopped at action ${failure.index + 1} of ${planned.length} (${planned[failure.index]?.label}): ${failure.error}`
1619
+ return result
1620
+ }
1621
+
1622
+ // Whether a call sends the screen to the provider: an observation, or an
1623
+ // action that returns a screenshot afterwards. A diagnostic tool that can
1624
+ // reach nothing captures nothing.
1625
+ const observes =
1626
+ available.has('screenshot') || available.has('list_windows') || available.has('ui_snapshot')
1627
+ const capturesScreen = (input: ActionInput): boolean =>
1628
+ observes && capturesScreenInput(input, settings.screenshotAfterActions)
1629
+
1630
+ const tool = defineTool({
317
1631
  name: COMPUTER_USE_TOOL_NAME,
318
- description: buildDescription(host),
1632
+ description: buildDescription(host, caps, settings),
319
1633
  inputSchema: actionSchema,
320
- modelInputSchema: hostModelSchema(host.capabilities),
321
- validationErrorHint:
322
- 'Action requirements: mouse_move needs "to"; mouse_click needs "at" and "button"; mouse_drag needs "from", "to", and "button"; scroll needs "at", "direction", and positive "amount"; type_text needs "text"; key needs "keys".',
1634
+ modelInputSchema: hostModelSchema(host, caps, settings),
1635
+ validationErrorHint: `Action requirements: ${ALL_ACTIONS.map((action) => ACTION_REQUIREMENTS[action]).join('; ')}. A batch is {"type":"batch","actions":[...]} with at most ${settings.maxBatchActions} actions and no screenshot, zoom, ui_snapshot or batch inside.`,
323
1636
  category: 'custom',
324
1637
  permissions: [],
325
- readOnly: false,
326
- destructive: (input: ActionInput) => DESTRUCTIVE_ACTION_TYPES.has(input.type),
1638
+ readOnly: (input: ActionInput) => isReadOnlyInput(input),
1639
+ destructive: (input: ActionInput) => isDestructiveInput(input),
1640
+ capturesScreen,
327
1641
  concurrencySafe: false,
328
1642
  presentCall: (input) => ({
329
1643
  kind: 'generic',
330
- label: actionLabel(input),
1644
+ label: label(input),
331
1645
  presentation: 'activity',
332
1646
  }),
1647
+ // Decided from the result alone: a host may present a finished call
1648
+ // without its input (the CLI passes `{}`). An action that only reports
1649
+ // "done" — and the screenshot that followed it, which is for the model —
1650
+ // adds nothing to the call row; observations, batches and failures do.
333
1651
  presentResult: (_input, result) =>
334
- result.success && result.output.trim().toLowerCase() === 'ok'
1652
+ result.success && ACKNOWLEDGEMENT.test(result.output)
335
1653
  ? { kind: 'generic', label: result.output, visibility: 'hidden' }
336
1654
  : undefined,
337
1655
 
338
- async execute(input, _context): Promise<ToolResult> {
339
- const required = requiredCapability(input.type)
340
- if (required !== null && host.capabilities[required] !== true) {
341
- return {
342
- success: false,
343
- output: '',
344
- error: `computer_use: action "${input.type}" requires capability "${required}" which is not available on this host (displayServer=${host.capabilities.displayServer}).${host.capabilities.unavailableReason ? ` ${host.capabilities.unavailableReason} Do not retry; tell the user.` : ''}`,
345
- }
346
- }
347
- if (!availableActions(host.capabilities).includes(input.type)) {
348
- return {
349
- success: false,
350
- output: '',
351
- error: `computer_use: action "${input.type}" is not supported on this host.`,
352
- }
353
- }
354
- const buttons =
355
- input.type === 'mouse_click'
356
- ? host.capabilities.mouseClickButtons
357
- : input.type === 'mouse_drag'
358
- ? host.capabilities.mouseDragButtons
359
- : undefined
360
- if (buttons && 'button' in input && !buttons.includes(input.button)) {
361
- return {
362
- success: false,
363
- output: '',
364
- error: `computer_use: action "${input.type}" does not support button "${input.button}" on this host.`,
365
- }
366
- }
1656
+ async execute(input, context): Promise<ToolResult> {
1657
+ const denied = refusal(input.type)
1658
+ if (denied) return { success: false, output: '', error: denied }
367
1659
  try {
368
- const result = await host.execute(input as ComputerUseAction)
369
- return resultToToolResult(result)
370
- } catch (error) {
371
- if (isOutcomeUnknown(error, input.type)) {
372
- return unknownOutcomeToToolResult(error)
1660
+ switch (input.type) {
1661
+ case 'screenshot':
1662
+ return screenshotResult(await capture())
1663
+ case 'zoom':
1664
+ return await zoom(input)
1665
+ case 'ui_snapshot':
1666
+ return await uiSnapshot(input)
1667
+ case 'batch':
1668
+ return await runSteps(input.actions, input.screenshot_id, context, false)
1669
+ default: {
1670
+ const { screenshot_id: frameId, ...item } = input
1671
+ return await runSteps([item as BatchItem], frameId, context, true)
1672
+ }
373
1673
  }
1674
+ } catch (error) {
1675
+ if (error instanceof StepFailure)
1676
+ return { success: false, output: '', error: `computer_use failed: ${error.message}` }
374
1677
  throw error
375
1678
  }
376
1679
  },
377
1680
  })
1681
+ return Object.assign(tool, { describeUiRef })
378
1682
  }