@xopcai/xopc 0.0.412 → 0.0.414

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 (280) hide show
  1. package/dist/browser-ext/manifest.json +1 -1
  2. package/dist/extensions/telegram/xopc.extension.json +1 -1
  3. package/dist/gateway/static/root/assets/{agent-avatar-dicebear-BrGZdh5N.js → agent-avatar-dicebear-ClobpdW3.js} +1 -1
  4. package/dist/gateway/static/root/assets/agent-defaults-settings-panel-mf0jgEPE.js +1 -0
  5. package/dist/gateway/static/root/assets/agents-CKhZ4UYQ.js +77 -0
  6. package/dist/gateway/static/root/assets/{agents-admin-api-uGDeJR1f.js → agents-admin-api-CeBkHnl8.js} +1 -1
  7. package/dist/gateway/static/root/assets/app-management-settings-panel-CLunLNGp.js +1 -0
  8. package/dist/gateway/static/root/assets/app-window-DXFNqt_0.js +1 -0
  9. package/dist/gateway/static/root/assets/appearance-settings-DBEgoE9g.js +1 -0
  10. package/dist/gateway/static/root/assets/apps-page-BZ_MaQJf.js +1 -0
  11. package/dist/gateway/static/root/assets/{archive-plugin-DAmsio91.js → archive-plugin-JElK4Goq.js} +1 -1
  12. package/dist/gateway/static/root/assets/{automations-page-BVutrwON.js → automations-page-CZ0UqwyU.js} +1 -1
  13. package/dist/gateway/static/root/assets/automations-workspace-CHUDHNVL.js +8 -0
  14. package/dist/gateway/static/root/assets/{binary-plugins-41QCRAxz.js → binary-plugins-CXFawow6.js} +1 -1
  15. package/dist/gateway/static/root/assets/{block-editor-BQ8AA-Ot.js → block-editor-BoduwSYI.js} +30 -30
  16. package/dist/gateway/static/root/assets/{browser-automation-inputs-D4-vQc4g.js → browser-automation-inputs-D1pDgMJu.js} +1 -1
  17. package/dist/gateway/static/root/assets/browser-automations-page-DStDm9y1.js +3 -0
  18. package/dist/gateway/static/root/assets/browser-settings-page-6I4oetKt.js +3 -0
  19. package/dist/gateway/static/root/assets/capabilities-Bpeh6E22.js +1 -0
  20. package/dist/gateway/static/root/assets/capabilities-page-C-EecWbS.js +2 -0
  21. package/dist/gateway/static/root/assets/capabilities-settings-panel-BQoCPk3x.js +8 -0
  22. package/dist/gateway/static/root/assets/{capability-header-actions-CDgI-YZF.js → capability-header-actions-BasrAWA0.js} +1 -1
  23. package/dist/gateway/static/root/assets/channels-BKf_-Mdt.js +1 -0
  24. package/dist/gateway/static/root/assets/{chat-terminal-dock-CUkCx_9x.js → chat-terminal-dock-CwQbzcZ_.js} +2 -2
  25. package/dist/gateway/static/root/assets/chevron-down-ccbiQQz_.js +1 -0
  26. package/dist/gateway/static/root/assets/computer-settings-page-DNWeJKfF.js +1 -0
  27. package/dist/gateway/static/root/assets/{connector-service-page-49mgdRET.js → connector-service-page-CxHgC5RM.js} +1 -1
  28. package/dist/gateway/static/root/assets/connectors-page-CUBg8spT.js +1 -0
  29. package/dist/gateway/static/root/assets/{date-picker-BpBjADAk.js → date-picker-CXxLbLJb.js} +1 -1
  30. package/dist/gateway/static/root/assets/dependency-picker-DVuWMrSj.js +1 -0
  31. package/dist/gateway/static/root/assets/desktop-pet-KbVLtAlO.js +1 -0
  32. package/dist/gateway/static/root/assets/desktop-pet-settings-BDlg9g4s.js +1 -0
  33. package/dist/gateway/static/root/assets/device-pairing-wizard-PmP_vGem.js +1 -0
  34. package/dist/gateway/static/root/assets/{directory-picker-path-field-DwXqp_iu.js → directory-picker-path-field-a7xEodzo.js} +1 -1
  35. package/dist/gateway/static/root/assets/ellipsis-0k4rZLoy.js +1 -0
  36. package/dist/gateway/static/root/assets/extension-debug-page-CHelH5u_.js +1 -0
  37. package/dist/gateway/static/root/assets/{extension-page-Bri62YsN.js → extension-page-CrVHmS9-.js} +1 -1
  38. package/dist/gateway/static/root/assets/extension-settings-page-CiMybi0r.js +1 -0
  39. package/dist/gateway/static/root/assets/eye-CABVeTq0.js +1 -0
  40. package/dist/gateway/static/root/assets/folder-open-CyzJ19I9.js +1 -0
  41. package/dist/gateway/static/root/assets/{gateway-config-swr-BVqYVaGy.js → gateway-config-swr-DF_HV6NP.js} +1 -1
  42. package/dist/gateway/static/root/assets/{gateway-realtime-CY1LBGBS.js → gateway-realtime-DMAzNlm-.js} +1 -1
  43. package/dist/gateway/static/root/assets/gateway-realtime-store-CoIgC1Al.js +1 -0
  44. package/dist/gateway/static/root/assets/gateway-settings-DnrQ28E2.js +1 -0
  45. package/dist/gateway/static/root/assets/{gateway-startup-retry-CPKGkr6D.js → gateway-startup-retry-B91CBTzO.js} +1 -1
  46. package/dist/gateway/static/root/assets/highlight-code-AMjU04kR.js +1 -0
  47. package/dist/gateway/static/root/assets/{highlighted-body-KQOG7T2V-Ci1QpkBn.js → highlighted-body-KQOG7T2V-8ACAVktj.js} +1 -1
  48. package/dist/gateway/static/root/assets/home-page-pTwVME-g.js +2 -0
  49. package/dist/gateway/static/root/assets/{image-generation-api-N5MYXlxr.js → image-generation-api-CVBaWmrV.js} +1 -1
  50. package/dist/gateway/static/root/assets/imports-page-C5IP3e6d.js +1 -0
  51. package/dist/gateway/static/root/assets/index-BLL0wKRZ.css +1 -0
  52. package/dist/gateway/static/root/assets/index-ygkHNYyo.js +710 -0
  53. package/dist/gateway/static/root/assets/keyboard-shortcuts-settings-BhU6VYvd.js +1 -0
  54. package/dist/gateway/static/root/assets/local-app-workbench-page-Cwl6EvuX.js +1 -0
  55. package/dist/gateway/static/root/assets/{local-apps-page-BjzaB1ls.js → local-apps-page-DcP1Bdlk.js} +1 -1
  56. package/dist/gateway/static/root/assets/{local-session-drafts-BCaC-1rH.js → local-session-drafts-DvmwiLUx.js} +1 -1
  57. package/dist/gateway/static/root/assets/{locale-store-DAgpZxAS.js → locale-store-JogcXC4J.js} +1 -1
  58. package/dist/gateway/static/root/assets/logs-page-CS3hcTOq.js +2 -0
  59. package/dist/gateway/static/root/assets/management-settings-DC-Y_-GP.js +1 -0
  60. package/dist/gateway/static/root/assets/{media-plugins-8mlDAjD6.js → media-plugins-BnPpcJ2J.js} +1 -1
  61. package/dist/gateway/static/root/assets/mermaid-MCJ5UELQ-vckgHpON.js +1 -0
  62. package/dist/gateway/static/root/assets/{messages-DMXSIJUO.js → messages-RzwPiQNL.js} +2 -2
  63. package/dist/gateway/static/root/assets/note-detail-panel-C7u4dxEk.js +5 -0
  64. package/dist/gateway/static/root/assets/note-image-lightbox-jvqYtySx.js +1 -0
  65. package/dist/gateway/static/root/assets/notes-page-Bs7F5Hpf.js +8 -0
  66. package/dist/gateway/static/root/assets/page-context-capture-button-DO_yhXS1.js +1 -0
  67. package/dist/gateway/static/root/assets/personal-page-BexL2-PQ.js +1 -0
  68. package/dist/gateway/static/root/assets/personal-settings-panel-BKA4fbYH.js +1 -0
  69. package/dist/gateway/static/root/assets/phone-dependencies-card-ChFtdFhA.js +1 -0
  70. package/dist/gateway/static/root/assets/phone-model-picker-CZoIAQ1j.js +1 -0
  71. package/dist/gateway/static/root/assets/phone-settings-page-DRyK0nHH.js +1 -0
  72. package/dist/gateway/static/root/assets/plugin-details-dialog-B-N61jJH.js +1 -0
  73. package/dist/gateway/static/root/assets/{preview-open-alternatives-n6Shdp3u.js → preview-open-alternatives-FGqN-mQx.js} +1 -1
  74. package/dist/gateway/static/root/assets/{product-open-page-BPF5rRTI.js → product-open-page-CwnHLfdL.js} +1 -1
  75. package/dist/gateway/static/root/assets/{product-preview-content-BOsGPNbw.js → product-preview-content-BD6k5hF9.js} +1 -1
  76. package/dist/gateway/static/root/assets/project-detail-page-DtKmFBml.js +7 -0
  77. package/dist/gateway/static/root/assets/projects-page-CFZIz9mc.js +1 -0
  78. package/dist/gateway/static/root/assets/{registry-api-B6kqbuw8.js → registry-api-BGqSwITi.js} +1 -1
  79. package/dist/gateway/static/root/assets/remote-access-hub-CWEc7W0m.js +1 -0
  80. package/dist/gateway/static/root/assets/runtime-tools-api-CSm5SpnR.js +5 -0
  81. package/dist/gateway/static/root/assets/runtime-tools-settings-panel-q_oswAdV.js +1 -0
  82. package/dist/gateway/static/root/assets/{schema-form-cXD1BC02.js → schema-form-DoVn5jJK.js} +1 -1
  83. package/dist/gateway/static/root/assets/{session-api-D6-Vz8xK.js → session-api-DTxZyMta.js} +1 -1
  84. package/dist/gateway/static/root/assets/{session-manager-DWbcMHcj.js → session-manager-B2HDomj4.js} +1 -1
  85. package/dist/gateway/static/root/assets/sessions-page-BuPqNxpd.js +1 -0
  86. package/dist/gateway/static/root/assets/settings-advanced-gate-QTVDCGJG.js +1 -0
  87. package/dist/gateway/static/root/assets/{settings-form-section-BXeszXR7.js → settings-form-section-Ce8vVv-9.js} +1 -1
  88. package/dist/gateway/static/root/assets/{settings-page-CNmh4YmR.js → settings-page-DP2w3ejC.js} +1 -1
  89. package/dist/gateway/static/root/assets/{settings-page-layout-Dx5S-Msc.js → settings-page-header-CYpuTXUW.js} +1 -1
  90. package/dist/gateway/static/root/assets/settings-page-layout-B20AfmFG.js +1 -0
  91. package/dist/gateway/static/root/assets/setup-status-panel-ZmlE8jIE.js +1 -0
  92. package/dist/gateway/static/root/assets/{share-preview-page-CS9cOCgX.js → share-preview-page-DV4dKrmG.js} +1 -1
  93. package/dist/gateway/static/root/assets/shares-settings-CtNxWqW-.js +1 -0
  94. package/dist/gateway/static/root/assets/{skill-api-B5lDCuOH.js → skill-api-DTdejInH.js} +1 -1
  95. package/dist/gateway/static/root/assets/skill-reload-api-Egaf8PqZ.js +1 -0
  96. package/dist/gateway/static/root/assets/skills-page-C6FKxf4J.js +2 -0
  97. package/dist/gateway/static/root/assets/system-settings-panel-Baxkhov7.js +1 -0
  98. package/dist/gateway/static/root/assets/task-detail-page-C77-rpkt.js +2 -0
  99. package/dist/gateway/static/root/assets/{task-result-evidence-HJO1BlJm.js → task-result-evidence-DIGSC4qF.js} +1 -1
  100. package/dist/gateway/static/root/assets/tracing-settings-page-BaU8frtc.js +1 -0
  101. package/dist/gateway/static/root/assets/{tunnel-api-DXU94F2Y.js → tunnel-api-BCcOUsqu.js} +1 -1
  102. package/dist/gateway/static/root/assets/usage-page-BFeIbpbi.js +1 -0
  103. package/dist/gateway/static/root/assets/{use-autosave-B1z_TIBu.js → use-autosave-C1X47WVb.js} +1 -1
  104. package/dist/gateway/static/root/assets/user-model-page-u8zY5jea.js +2 -0
  105. package/dist/gateway/static/root/assets/{voice-api-key-field-HOLjEhh3.js → voice-api-key-field-Bk4DQMTw.js} +1 -1
  106. package/dist/gateway/static/root/assets/{voice-config-api-CPKzUb4R.js → voice-config-api-D7xsWrdh.js} +1 -1
  107. package/dist/gateway/static/root/assets/{work-discovery-overlay-C9RklDaR.js → work-discovery-overlay-B7_NmTB5.js} +1 -1
  108. package/dist/gateway/static/root/assets/{xopc-cloud-account-card-CWwGTBib.js → xopc-cloud-account-card-CJ_akAvc.js} +1 -1
  109. package/dist/gateway/static/root/index.html +19 -14
  110. package/dist/package.js +1 -1
  111. package/dist/packages/gateway-contract/src/capabilities.js +1 -0
  112. package/dist/src/agent/background-review/prompts.js +4 -3
  113. package/dist/src/agent/embedded/run-turn.js +1 -1
  114. package/dist/src/agent/embedded/xopc-stream-bridge.js +7 -3
  115. package/dist/src/agent/prompt/system-prompt.js +1 -1
  116. package/dist/src/agent/tool-manuals/index.js +7 -0
  117. package/dist/src/agent/tool-manuals/phone.d.ts +1 -0
  118. package/dist/src/agent/tool-manuals/phone.js +16 -0
  119. package/dist/src/agent/tools/factory.js +15 -0
  120. package/dist/src/agent/tools/knowledge-memory-tool.js +1 -1
  121. package/dist/src/agent/tools/personal-task-tool.d.ts +2 -1
  122. package/dist/src/agent/tools/personal-task-tool.js +43 -20
  123. package/dist/src/agent/tools/phone-use-tool.d.ts +8 -0
  124. package/dist/src/agent/tools/phone-use-tool.js +49 -0
  125. package/dist/src/agent/tools/session-recall-tool.js +2 -1
  126. package/dist/src/agent/tools/session-search-tool.js +11 -5
  127. package/dist/src/agent/tools/user-context-tool.js +1 -1
  128. package/dist/src/agent-config/index.js +2 -2
  129. package/dist/src/agent-config/resolver.js +5 -0
  130. package/dist/src/agent-config/schema.d.ts +24 -0
  131. package/dist/src/agent-config/schema.js +5 -2
  132. package/dist/src/cli/command-manifest.js +1 -1
  133. package/dist/src/cli/commands/extension-marketplace.js +2 -2
  134. package/dist/src/cli/commands/extension.js +2 -2
  135. package/dist/src/cli/commands/runtime.js +32 -4
  136. package/dist/src/computer/diagnostics.d.ts +1 -1
  137. package/dist/src/config/models-json.d.ts +36 -0
  138. package/dist/src/config/models-json.js +9 -0
  139. package/dist/src/config/schema.d.ts +12 -0
  140. package/dist/src/config/schema.js +6 -2
  141. package/dist/src/extensions/update.js +1 -1
  142. package/dist/src/gateway/agent-builtin-tools.d.ts +7 -1
  143. package/dist/src/gateway/agent-builtin-tools.js +7 -0
  144. package/dist/src/gateway/global-defaults-admin.js +2 -0
  145. package/dist/src/gateway/hono/lib/config-payload.d.ts +8 -0
  146. package/dist/src/gateway/hono/routes/auth-registry-extensions.js +1 -1
  147. package/dist/src/gateway/hono/routes/lazy-bundles.js +8 -0
  148. package/dist/src/gateway/hono/routes/models.js +3 -0
  149. package/dist/src/gateway/hono/routes/phone-dependencies.d.ts +3 -0
  150. package/dist/src/gateway/hono/routes/phone-dependencies.js +137 -0
  151. package/dist/src/gateway/hono/routes/phone.d.ts +4 -0
  152. package/dist/src/gateway/hono/routes/phone.js +466 -0
  153. package/dist/src/gateway/hono/routes/runtime-tools.js +2 -0
  154. package/dist/src/gateway/server.js +1 -0
  155. package/dist/src/gateway/service/config-coordinator.d.ts +8 -0
  156. package/dist/src/gateway/service/config-coordinator.js +1 -1
  157. package/dist/src/gateway/service.d.ts +2 -0
  158. package/dist/src/gateway/service.js +12 -7
  159. package/dist/src/infra/update-runner.js +1 -1
  160. package/dist/src/knowledge-memory/repository.js +5 -0
  161. package/dist/src/memory-learning/forget-capture.js +12 -3
  162. package/dist/src/memory-learning/forgotten-history.d.ts +4 -0
  163. package/dist/src/memory-learning/forgotten-history.js +71 -4
  164. package/dist/src/memory-learning/work-capture.d.ts +1 -0
  165. package/dist/src/memory-learning/work-capture.js +28 -17
  166. package/dist/src/personal-agent/prompts.js +3 -2
  167. package/dist/src/phone/adb-driver.d.ts +38 -0
  168. package/dist/src/phone/adb-driver.js +392 -0
  169. package/dist/src/phone/app-access.d.ts +10 -0
  170. package/dist/src/phone/app-access.js +21 -0
  171. package/dist/src/phone/applications.d.ts +4 -0
  172. package/dist/src/phone/applications.js +49 -0
  173. package/dist/src/phone/cloud-adb.d.ts +35 -0
  174. package/dist/src/phone/cloud-adb.js +273 -0
  175. package/dist/src/phone/cloud-phones.d.ts +29 -0
  176. package/dist/src/phone/cloud-phones.js +142 -0
  177. package/dist/src/phone/cloud-scrcpy.d.ts +20 -0
  178. package/dist/src/phone/cloud-scrcpy.js +123 -0
  179. package/dist/src/phone/contracts.d.ts +212 -0
  180. package/dist/src/phone/contracts.js +116 -0
  181. package/dist/src/phone/interactive-session.d.ts +34 -0
  182. package/dist/src/phone/interactive-session.js +164 -0
  183. package/dist/src/phone/live-provider.d.ts +57 -0
  184. package/dist/src/phone/live-provider.js +18 -0
  185. package/dist/src/phone/live-state.d.ts +28 -0
  186. package/dist/src/phone/live-state.js +71 -0
  187. package/dist/src/phone/model-adapter.d.ts +31 -0
  188. package/dist/src/phone/model-adapter.js +350 -0
  189. package/dist/src/phone/repository.d.ts +19 -0
  190. package/dist/src/phone/repository.js +101 -0
  191. package/dist/src/phone/runtime.d.ts +88 -0
  192. package/dist/src/phone/runtime.js +924 -0
  193. package/dist/src/phone/scrcpy-assets.d.ts +4 -0
  194. package/dist/src/phone/scrcpy-assets.js +16 -0
  195. package/dist/src/phone/scrcpy.d.ts +59 -0
  196. package/dist/src/phone/scrcpy.js +337 -0
  197. package/dist/src/phone/stream-gateway.d.ts +26 -0
  198. package/dist/src/phone/stream-gateway.js +210 -0
  199. package/dist/src/process/phone-process.d.ts +4 -0
  200. package/dist/src/process/phone-process.js +38 -0
  201. package/dist/src/providers/extension-stream-bridge.js +8 -1
  202. package/dist/src/providers/model-call.js +6 -2
  203. package/dist/src/providers/model-catalog-persistence.js +3 -0
  204. package/dist/src/providers/model-catalog-types.d.ts +3 -0
  205. package/dist/src/providers/model-reference-auditor.js +5 -1
  206. package/dist/src/providers/model-registry.js +3 -0
  207. package/dist/src/runtime-tools/archive.d.ts +2 -2
  208. package/dist/src/runtime-tools/downloader.d.ts +2 -2
  209. package/dist/src/runtime-tools/errors.d.ts +3 -3
  210. package/dist/src/runtime-tools/phone-dependencies.d.ts +40 -0
  211. package/dist/src/runtime-tools/phone-dependencies.js +188 -0
  212. package/dist/src/runtime-tools/phone-dependency-catalog.d.ts +16 -0
  213. package/dist/src/runtime-tools/phone-dependency-catalog.js +51 -0
  214. package/dist/src/runtime-tools/types.d.ts +1 -0
  215. package/dist/src/storage/sqlite/context-extraction-repository.d.ts +1 -0
  216. package/dist/src/storage/sqlite/context-extraction-repository.js +12 -2
  217. package/dist/src/tasks/task-research-result.js +19 -2
  218. package/dist/src/usage/cloud-receipts.d.ts +5 -1
  219. package/dist/src/usage/cloud-receipts.js +10 -4
  220. package/dist/src/usage/cloud-wire.d.ts +37 -0
  221. package/dist/src/usage/cloud-wire.js +181 -0
  222. package/dist/src/usage/recorder.js +5 -4
  223. package/dist/src/user-model/capture/semantic.js +2 -2
  224. package/dist/src/voice/platform-catalog.d.ts +3 -0
  225. package/package.json +10 -5
  226. package/dist/gateway/static/root/assets/agent-defaults-settings-panel-BjZpom5V.js +0 -1
  227. package/dist/gateway/static/root/assets/agents-s3ws5Qi-.js +0 -77
  228. package/dist/gateway/static/root/assets/app-management-settings-panel-C6DHuX0L.js +0 -1
  229. package/dist/gateway/static/root/assets/appearance-settings-DYGY92Yr.js +0 -1
  230. package/dist/gateway/static/root/assets/apps-page-PpT_YMmD.js +0 -1
  231. package/dist/gateway/static/root/assets/automations-workspace-G6-9IYbR.js +0 -8
  232. package/dist/gateway/static/root/assets/browser-automations-page-o3jNFlCb.js +0 -3
  233. package/dist/gateway/static/root/assets/browser-settings-page-G0dVwiX4.js +0 -3
  234. package/dist/gateway/static/root/assets/capabilities-B9b5KCBb.js +0 -1
  235. package/dist/gateway/static/root/assets/capabilities-page-BZQanmZY.js +0 -2
  236. package/dist/gateway/static/root/assets/capabilities-settings-panel-D-YadSx_.js +0 -8
  237. package/dist/gateway/static/root/assets/channels-3WX3f0bR.js +0 -1
  238. package/dist/gateway/static/root/assets/computer-settings-page-CuZrL9gL.js +0 -1
  239. package/dist/gateway/static/root/assets/connectors-page-Bjj2wAeG.js +0 -1
  240. package/dist/gateway/static/root/assets/dependency-picker-R8kdHLr3.js +0 -1
  241. package/dist/gateway/static/root/assets/desktop-pet-BpiosRqb.js +0 -1
  242. package/dist/gateway/static/root/assets/desktop-pet-settings-9h6evrKB.js +0 -1
  243. package/dist/gateway/static/root/assets/device-pairing-wizard-CZ5lwiLE.js +0 -1
  244. package/dist/gateway/static/root/assets/extension-debug-page-Bj93A2Gn.js +0 -1
  245. package/dist/gateway/static/root/assets/extension-settings-page-BSo6vRhS.js +0 -1
  246. package/dist/gateway/static/root/assets/gateway-realtime-store-Dafw95ol.js +0 -1
  247. package/dist/gateway/static/root/assets/gateway-settings-DgEwWJwz.js +0 -1
  248. package/dist/gateway/static/root/assets/highlight-code-CSFBakwF.js +0 -1
  249. package/dist/gateway/static/root/assets/home-page-BKj-aPsL.js +0 -2
  250. package/dist/gateway/static/root/assets/imports-page-YfaRnpk1.js +0 -1
  251. package/dist/gateway/static/root/assets/index-CiDVRMmX.js +0 -710
  252. package/dist/gateway/static/root/assets/index-caRW34rg.css +0 -1
  253. package/dist/gateway/static/root/assets/keyboard-shortcuts-settings-BTQcPOix.js +0 -1
  254. package/dist/gateway/static/root/assets/local-app-workbench-page-kCZ_fbSZ.js +0 -1
  255. package/dist/gateway/static/root/assets/logs-page-5Jt3uqYt.js +0 -2
  256. package/dist/gateway/static/root/assets/management-settings-DOQMLPrI.js +0 -1
  257. package/dist/gateway/static/root/assets/mermaid-MCJ5UELQ-B4nztFE4.js +0 -1
  258. package/dist/gateway/static/root/assets/note-detail-panel-1wsKlkk3.js +0 -5
  259. package/dist/gateway/static/root/assets/note-image-lightbox-B82cToeV.js +0 -1
  260. package/dist/gateway/static/root/assets/notes-page-DKREWOku.js +0 -8
  261. package/dist/gateway/static/root/assets/page-context-capture-button-oSJIFF3E.js +0 -1
  262. package/dist/gateway/static/root/assets/personal-page-CoqQTkkS.js +0 -1
  263. package/dist/gateway/static/root/assets/personal-settings-panel-BgcJLmZN.js +0 -1
  264. package/dist/gateway/static/root/assets/plugin-details-dialog-CdOJ3Tir.js +0 -1
  265. package/dist/gateway/static/root/assets/project-detail-page-ZADyvA7-.js +0 -7
  266. package/dist/gateway/static/root/assets/projects-page-BkRe6Z8S.js +0 -1
  267. package/dist/gateway/static/root/assets/remote-access-hub-CCGf5kF4.js +0 -1
  268. package/dist/gateway/static/root/assets/runtime-tools-api-CY3R7unQ.js +0 -5
  269. package/dist/gateway/static/root/assets/runtime-tools-settings-panel-BxaxVlwM.js +0 -1
  270. package/dist/gateway/static/root/assets/sessions-page-B81rRu2-.js +0 -1
  271. package/dist/gateway/static/root/assets/settings-advanced-gate-DLHGV-I-.js +0 -1
  272. package/dist/gateway/static/root/assets/setup-status-panel-gEqkJWyw.js +0 -1
  273. package/dist/gateway/static/root/assets/shares-settings-D9Ul96XW.js +0 -1
  274. package/dist/gateway/static/root/assets/skill-reload-api-NLAaNaDn.js +0 -1
  275. package/dist/gateway/static/root/assets/skills-page-dWeFt-6J.js +0 -2
  276. package/dist/gateway/static/root/assets/system-settings-panel-DdDsFdCh.js +0 -1
  277. package/dist/gateway/static/root/assets/task-detail-page-DO3FM--W.js +0 -2
  278. package/dist/gateway/static/root/assets/tracing-settings-page-Dm3yToJ1.js +0 -1
  279. package/dist/gateway/static/root/assets/usage-page-DJJhACRJ.js +0 -1
  280. package/dist/gateway/static/root/assets/user-model-page-DnZ4qFaO.js +0 -2
@@ -0,0 +1,924 @@
1
+ import { createLogger } from "../utils/logger/index.js";
2
+ import { init_logger } from "../utils/logger.js";
3
+ import { MOCK_PHONE_MODEL, PhoneError, PhoneInputSchema, init_contracts } from "./contracts.js";
4
+ import { resolveEffectiveAgentConfigForAgent } from "../config/agent-profile.js";
5
+ import { phoneAllowsApp, phoneAppAccess } from "./app-access.js";
6
+ import { PhoneFrameCache, PhoneOperationBudget } from "./live-state.js";
7
+ import { PhoneInteractiveSession } from "./interactive-session.js";
8
+ import { AdbPhoneDriver, resolveAdbPath } from "./adb-driver.js";
9
+ import { LocalScrcpyProvider } from "./scrcpy.js";
10
+ import { CloudPhones } from "./cloud-phones.js";
11
+ import { createPhoneModel, isAboutScreen } from "./model-adapter.js";
12
+ import { PhoneRepository } from "./repository.js";
13
+ import { createHash, randomUUID } from "node:crypto";
14
+ //#region src/phone/runtime.ts
15
+ init_logger();
16
+ init_contracts();
17
+ const log = createLogger("PhoneRuntime");
18
+ const terminal = new Set([
19
+ "cancelled",
20
+ "closed",
21
+ "failed",
22
+ "outcome_unknown"
23
+ ]);
24
+ var PhoneRuntime = class {
25
+ cloudPhones;
26
+ get cloud() {
27
+ return this.cloudPhones ??= new CloudPhones(this.repository);
28
+ }
29
+ sessions = /* @__PURE__ */ new Map();
30
+ openings = /* @__PURE__ */ new Map();
31
+ maintenance;
32
+ constructor(repository = new PhoneRepository(), driverFactory = (path) => new AdbPhoneDriver(path), modelFactory = createPhoneModel, interactiveFactory = (adb, serial, failed) => new LocalScrcpyProvider(adb, serial, failed)) {
33
+ this.repository = repository;
34
+ this.driverFactory = driverFactory;
35
+ this.modelFactory = modelFactory;
36
+ this.interactiveFactory = interactiveFactory;
37
+ }
38
+ async driverFor(device, config) {
39
+ return device.connection ? this.cloud.driver(device) : this.driverFactory(resolveAdbPath(config.phone.adbPath));
40
+ }
41
+ async discover(config, signal) {
42
+ return this.driverFactory(resolveAdbPath(config.phone.adbPath)).discover(signal);
43
+ }
44
+ async applications(input, config, signal) {
45
+ let driver;
46
+ let serial;
47
+ if (input.deviceId) {
48
+ const device = this.repository.get(input.deviceId);
49
+ if (!device.enabled) throw new PhoneError("PHONE_DEVICE_DENIED", "Phone access is disabled", 403);
50
+ driver = await this.driverFor(device, config);
51
+ serial = device.serial;
52
+ } else {
53
+ const found = (await this.discover(config, signal)).find((device) => device.serial === input.serial && device.state === "device");
54
+ if (!found) throw new PhoneError("PHONE_DEVICE_OFFLINE", "Choose a connected and authorized phone");
55
+ driver = this.driverFactory(resolveAdbPath(config.phone.adbPath));
56
+ serial = found.serial;
57
+ }
58
+ if (!driver.applications) throw new PhoneError("PHONE_APPS_UNAVAILABLE", "The phone provider cannot list applications");
59
+ return driver.applications(serial, signal);
60
+ }
61
+ async preview(id, config, signal) {
62
+ const device = this.repository.get(id);
63
+ if (!device.enabled) throw new PhoneError("PHONE_DEVICE_DENIED", "Phone access is disabled", 403);
64
+ const lease = this.repository.acquire(device.serial);
65
+ try {
66
+ const observation = await (await this.driverFor(device, config)).observe(device.serial, signal);
67
+ if (!phoneAllowsApp(device, observation.package)) {
68
+ observation.frame.fill(0);
69
+ throw new PhoneError("PHONE_APP_DENIED", "Open an allowed app before previewing the screen");
70
+ }
71
+ return observation;
72
+ } finally {
73
+ this.repository.release(device.serial, lease);
74
+ }
75
+ }
76
+ async previewSession(owner, context, signal) {
77
+ const session = this.sessions.get(owner);
78
+ if (!session || terminal.has(session.report.status)) throw new PhoneError("PHONE_OPEN_REQUIRED", "Open a phone session first");
79
+ if (session.active) throw new PhoneError("PHONE_SESSION_BUSY", "Pause the task before previewing", 409);
80
+ this.guard(session, context);
81
+ const observation = await this.observe(session, signal);
82
+ try {
83
+ this.allowedScreen(session, observation);
84
+ return observation;
85
+ } catch (error) {
86
+ observation.frame.fill(0);
87
+ throw error;
88
+ }
89
+ }
90
+ reports() {
91
+ return [...this.sessions.values()].map((session) => structuredClone(session.report));
92
+ }
93
+ status(owner) {
94
+ const report = this.sessions.get(owner)?.report;
95
+ return report && structuredClone(report);
96
+ }
97
+ publish(session) {
98
+ session.report.updatedAt = Date.now();
99
+ this.repository.report(session.report);
100
+ return structuredClone(session.report);
101
+ }
102
+ guard(session, context) {
103
+ if (!context.config.phone.enabled || context.toolDenied) throw new PhoneError("PHONE_DISABLED", "Phone Use is disabled for this agent");
104
+ if (context.modelRef !== session.modelRef || resolveAdbPath(context.config.phone.adbPath) !== session.adbPath) throw new PhoneError("PHONE_BINDING_CHANGED", "Phone model or execution configuration changed; reopen the phone");
105
+ if (context.validateBinding && !context.validateBinding()) throw new PhoneError("PHONE_BINDING_CHANGED", "Agent model or tool policy changed; reopen the phone");
106
+ if (Date.now() - session.createdAt >= context.config.phone.maxSessionDurationMs) throw new PhoneError("PHONE_SESSION_LIMIT", "Phone session duration limit reached");
107
+ const current = this.repository.get(session.device.id);
108
+ if (!current.enabled || current.revision !== session.device.revision) throw new PhoneError("PHONE_ACCESS_CHANGED", "Device access changed; reopen the phone");
109
+ this.repository.renew(session.device.serial, session.lease);
110
+ }
111
+ allowedScreen(session, observation) {
112
+ if (!phoneAllowsApp(session.device, observation.package)) throw new PhoneError("PHONE_APP_DENIED", "The foreground application is outside this phone session allowlist. Open an allowed app or reopen with appPackage.");
113
+ }
114
+ async execute(owner, raw, context, signal = new AbortController().signal) {
115
+ const input = PhoneInputSchema.parse(raw);
116
+ if ([
117
+ "cancel",
118
+ "close",
119
+ "pause"
120
+ ].includes(input.op)) return this.stop(owner, input.op);
121
+ if (input.op === "status") return this.status(owner) ?? { status: "closed" };
122
+ if (!context.config.phone.enabled || context.toolDenied) throw new PhoneError("PHONE_DISABLED", "Enable Phone Use in settings and allow the tool for this agent");
123
+ if (input.op === "discover") return {
124
+ devices: this.repository.list().filter((device) => device.enabled),
125
+ defaultDeviceId: this.repository.defaultDevice()
126
+ };
127
+ signal.throwIfAborted();
128
+ if (input.op === "open") {
129
+ if (this.openings.has(owner)) throw new PhoneError("PHONE_SESSION_BUSY", "A phone is already being opened", 409);
130
+ const opening = new AbortController();
131
+ this.openings.set(owner, opening);
132
+ signal = AbortSignal.any([signal, opening.signal]);
133
+ try {
134
+ for (const [key, value] of this.sessions) if (terminal.has(value.report.status)) this.sessions.delete(key);
135
+ if (this.sessions.size >= 50) throw new PhoneError("PHONE_CAPACITY", "Close an existing phone session before opening another", 409);
136
+ const previous = this.sessions.get(owner);
137
+ if (previous && !terminal.has(previous.report.status)) throw new PhoneError("PHONE_ALREADY_OPEN", "Close the current phone session before opening another");
138
+ const deviceId = input.deviceId ?? this.repository.defaultDevice();
139
+ if (!deviceId) throw new PhoneError("PHONE_DEVICE_REQUIRED", "Choose a registered phone in Phone Use settings");
140
+ const device = this.repository.get(deviceId);
141
+ if (!device.enabled) throw new PhoneError("PHONE_DEVICE_DENIED", "This phone is disabled", 403);
142
+ const modelRef = context.modelRef;
143
+ if (!modelRef) throw new PhoneError("PHONE_MODEL_REQUIRED", "Choose a Phone Use model in settings");
144
+ const adbPath = resolveAdbPath(context.config.phone.adbPath);
145
+ const driver = await this.driverFor(device, context.config);
146
+ const found = (await driver.discover(signal)).find((item) => item.serial === device.serial);
147
+ if (found?.state !== "device") throw new PhoneError("PHONE_DEVICE_OFFLINE", "Phone is offline or has not authorized ADB");
148
+ if (modelRef === "phone-mock/settings-about" && !found.emulator) throw new PhoneError("PHONE_MOCK_EMULATOR_ONLY", "The deterministic mock model is only enabled for local Android emulators");
149
+ const lease = this.repository.acquire(device.serial);
150
+ try {
151
+ const adapter = await this.modelFactory(modelRef, context.config, context.agentId);
152
+ signal.throwIfAborted();
153
+ let applications = [];
154
+ if (input.mode === "control" && modelRef !== "phone-mock/settings-about" && driver.applications) try {
155
+ applications = await driver.applications(device.serial, signal);
156
+ } catch {
157
+ signal.throwIfAborted();
158
+ }
159
+ const now = Date.now();
160
+ const session = {
161
+ device,
162
+ driver,
163
+ adapter,
164
+ lease,
165
+ controller: new AbortController(),
166
+ createdAt: now,
167
+ modelRef,
168
+ adbPath,
169
+ context,
170
+ applications,
171
+ frames: new PhoneFrameCache(),
172
+ io: Promise.resolve(),
173
+ ioBusy: false,
174
+ wake: /* @__PURE__ */ new Set(),
175
+ viewedAt: 0,
176
+ commands: Promise.resolve(),
177
+ receipts: /* @__PURE__ */ new Map(),
178
+ report: {
179
+ sessionId: randomUUID(),
180
+ deviceId: device.id,
181
+ deviceName: device.name,
182
+ owner,
183
+ agentId: context.agentId,
184
+ modelRef,
185
+ mock: modelRef === MOCK_PHONE_MODEL,
186
+ mode: input.mode,
187
+ status: "ready",
188
+ actions: 0,
189
+ modelRequests: 0,
190
+ controlOwner: "agent",
191
+ controlEpoch: 1,
192
+ handoff: "idle",
193
+ createdAt: now,
194
+ updatedAt: now,
195
+ verified: false,
196
+ verificationLevel: "none",
197
+ steps: []
198
+ }
199
+ };
200
+ this.sessions.set(owner, session);
201
+ if (input.appPackage) {
202
+ if (input.mode !== "control" || !phoneAllowsApp(device, input.appPackage)) throw new PhoneError("PHONE_APP_DENIED", "Launching requires control mode and an allowed application");
203
+ const epoch = session.report.controlEpoch;
204
+ const observation = await this.observe(session, signal);
205
+ try {
206
+ await this.dispatch(session, {
207
+ type: "launch",
208
+ package: input.appPackage
209
+ }, observation, context, signal, epoch);
210
+ } finally {
211
+ observation.frame.fill(0);
212
+ }
213
+ }
214
+ return this.publish(session);
215
+ } catch (error) {
216
+ this.repository.release(device.serial, lease);
217
+ this.sessions.delete(owner);
218
+ throw error;
219
+ }
220
+ } finally {
221
+ this.openings.delete(owner);
222
+ }
223
+ }
224
+ const session = this.sessions.get(owner);
225
+ if (!session || terminal.has(session.report.status)) throw new PhoneError("PHONE_OPEN_REQUIRED", "Open a phone session first");
226
+ if ((session.human || session.report.controlOwner === "none" && !(input.op === "resume" && session.controller.signal.aborted)) && input.op !== "run" && input.op !== "step") throw new PhoneError("PHONE_PAUSED", "The user controls this phone. Wait for explicit handback in the phone panel", 409);
227
+ if (session.active) throw new PhoneError("PHONE_SESSION_BUSY", "A phone operation is already running", 409);
228
+ this.guard(session, context);
229
+ if (input.op === "resume") {
230
+ session.controller = new AbortController();
231
+ session.report.controlOwner = "agent";
232
+ session.report.controlEpoch++;
233
+ session.report.status = "ready";
234
+ session.report.stopReason = void 0;
235
+ session.report.message = void 0;
236
+ return this.publish(session);
237
+ }
238
+ if (session.report.status === "paused" || session.report.status === "needs_user") throw new PhoneError("PHONE_PAUSED", "Complete manual interaction and resume the phone session");
239
+ session.budget = new PhoneOperationBudget(context.config.phone.maxOperationDurationMs);
240
+ if (session.report.controlOwner === "agent") session.budget.resume();
241
+ const combined = AbortSignal.any([
242
+ signal,
243
+ session.controller.signal,
244
+ session.budget.controller.signal
245
+ ]);
246
+ const operation = this.perform(session, input, context, combined);
247
+ session.active = operation;
248
+ try {
249
+ return await operation;
250
+ } finally {
251
+ session.active = void 0;
252
+ session.budget?.dispose();
253
+ session.budget = void 0;
254
+ }
255
+ }
256
+ async perform(session, input, context, signal) {
257
+ let observation;
258
+ let heartbeat;
259
+ const startedAt = Date.now();
260
+ session.report.errorCode = void 0;
261
+ session.report.verified = false;
262
+ try {
263
+ await this.waitForAgent(session, signal);
264
+ session.report.status = "running";
265
+ this.publish(session);
266
+ heartbeat = setInterval(() => {
267
+ try {
268
+ this.guard(session, context);
269
+ } catch (error) {
270
+ session.controller.abort(error);
271
+ }
272
+ }, 1e4);
273
+ heartbeat.unref();
274
+ observation = await this.observe(session, signal);
275
+ this.allowedScreen(session, observation);
276
+ if (input.op === "observe") {
277
+ this.recordObservation(session, observation);
278
+ session.report.status = "ready";
279
+ return this.publish(session);
280
+ }
281
+ if (input.op !== "run" && input.op !== "step") throw new PhoneError("PHONE_INVALID_OPERATION", "Unsupported operation");
282
+ if (session.report.mode !== "control") throw new PhoneError("PHONE_OBSERVE_ONLY", "Reopen the phone in control mode to execute actions");
283
+ if (session.report.mock && input.scenario !== "settings-about") throw new PhoneError("PHONE_MOCK_SCENARIO_REQUIRED", "Mock mode only supports the explicit settings-about scenario");
284
+ const maxActions = input.op === "step" ? 1 : input.maxActions;
285
+ let actions = 0;
286
+ while (true) {
287
+ if (await this.waitForAgent(session, signal)) {
288
+ observation.frame.fill(0);
289
+ observation = await this.observe(session, signal);
290
+ }
291
+ signal.throwIfAborted();
292
+ this.guard(session, context);
293
+ this.allowedScreen(session, observation);
294
+ const epoch = session.report.controlEpoch;
295
+ this.recordObservation(session, observation);
296
+ if (session.report.mock ? isAboutScreen(observation) : Boolean(input.expectText && session.report.facts?.some((text) => text.includes(input.expectText)))) {
297
+ session.report.verified = true;
298
+ session.report.verificationLevel = "ui";
299
+ session.report.status = "completed";
300
+ session.report.stopReason = actions ? "condition_satisfied" : "condition_present";
301
+ return this.publish(session);
302
+ }
303
+ if (actions >= maxActions) {
304
+ session.report.status = "ready";
305
+ session.report.stopReason = "action_limit";
306
+ return this.publish(session);
307
+ }
308
+ if (session.report.modelRequests >= context.config.phone.maxModelRequests) throw new PhoneError("PHONE_MODEL_LIMIT", "Phone model request limit reached");
309
+ session.report.modelRequests++;
310
+ session.report.currentAction = void 0;
311
+ this.publish(session);
312
+ session.prediction = new AbortController();
313
+ let decision;
314
+ try {
315
+ decision = await session.adapter.predict({
316
+ goal: input.goal,
317
+ observation,
318
+ allowedApps: session.device.allowedApps,
319
+ appAccess: phoneAppAccess(session.device),
320
+ applications: session.applications.filter((app) => phoneAllowsApp(session.device, app.packageName)),
321
+ history: session.report.steps.map((step) => step.action),
322
+ scenario: input.scenario
323
+ }, AbortSignal.any([signal, session.prediction.signal]));
324
+ } catch (error) {
325
+ if (!signal.aborted && epoch !== session.report.controlEpoch) {
326
+ await this.waitForAgent(session, signal);
327
+ observation.frame.fill(0);
328
+ observation = await this.observe(session, signal);
329
+ continue;
330
+ }
331
+ throw error;
332
+ } finally {
333
+ session.prediction = void 0;
334
+ }
335
+ signal.throwIfAborted();
336
+ this.guard(session, context);
337
+ if (epoch !== session.report.controlEpoch || session.report.controlOwner !== "agent") {
338
+ await this.waitForAgent(session, signal);
339
+ observation.frame.fill(0);
340
+ observation = await this.observe(session, signal);
341
+ continue;
342
+ }
343
+ if (decision.kind !== "action") {
344
+ session.report.status = decision.kind === "needsUser" ? "needs_user" : "completed";
345
+ session.report.message = decision.kind === "needsUser" ? decision.reason : void 0;
346
+ session.report.stopReason = decision.kind === "needsUser" ? "user_takeover" : "model_finished_unverified";
347
+ if (decision.kind === "needsUser" && !session.report.mock) {
348
+ session.report.controlOwner = "none";
349
+ session.report.controlEpoch++;
350
+ session.budget?.pause();
351
+ this.publish(session);
352
+ await this.waitForAgent(session, signal);
353
+ observation.frame.fill(0);
354
+ observation = await this.observe(session, signal);
355
+ continue;
356
+ }
357
+ return this.publish(session);
358
+ }
359
+ const fresh = await this.observe(session, signal);
360
+ try {
361
+ this.allowedScreen(session, fresh);
362
+ if (fresh.package !== observation.package || fresh.width !== observation.width || fresh.height !== observation.height || fresh.rotation !== observation.rotation || JSON.stringify(fresh.elements) !== JSON.stringify(observation.elements) || !fresh.elements.length && !fresh.frame.equals(observation.frame)) {
363
+ observation.frame.fill(0);
364
+ observation = fresh;
365
+ continue;
366
+ }
367
+ if (epoch !== session.report.controlEpoch || session.report.controlOwner !== "agent") continue;
368
+ if (await this.dispatch(session, decision.action, fresh, context, signal, epoch)) actions++;
369
+ } finally {
370
+ if (observation !== fresh) fresh.frame.fill(0);
371
+ }
372
+ observation.frame.fill(0);
373
+ observation = await this.observe(session, signal);
374
+ }
375
+ } catch (error) {
376
+ const failure = signal.aborted && signal.reason instanceof PhoneError ? signal.reason : error;
377
+ if (failure instanceof PhoneError && failure.code === "PHONE_OPERATION_TIMEOUT" && !terminal.has(session.report.status) && session.report.status !== "paused") {
378
+ session.report.status = "paused";
379
+ session.report.controlOwner = "none";
380
+ session.report.controlEpoch++;
381
+ session.report.handoff = "idle";
382
+ session.report.errorCode = failure.code;
383
+ session.report.stopReason = "operation_timeout";
384
+ session.report.message = failure.message;
385
+ session.budget?.pause();
386
+ await session.stream?.revoke();
387
+ log.warn({
388
+ sessionId: session.report.sessionId,
389
+ conversationId: session.report.owner,
390
+ durationMs: Date.now() - startedAt,
391
+ actions: session.report.actions,
392
+ modelRequests: session.report.modelRequests,
393
+ errorCode: failure.code,
394
+ phase: "goal_timeout"
395
+ }, "Phone goal timed out; session remains paused for inspection");
396
+ return this.publish(session);
397
+ }
398
+ log.warn({
399
+ sessionId: session.report.sessionId,
400
+ conversationId: session.report.owner,
401
+ durationMs: Date.now() - startedAt,
402
+ errorCode: failure instanceof PhoneError ? failure.code : signal.aborted ? "PHONE_CANCELLED" : "PHONE_OPERATION_FAILED",
403
+ phase: "goal_failed"
404
+ }, "Phone goal stopped before completion");
405
+ if (![
406
+ "paused",
407
+ "cancelled",
408
+ "closed"
409
+ ].includes(session.report.status)) {
410
+ if (String(session.report.status) !== "outcome_unknown") session.report.status = signal.aborted ? "cancelled" : "failed";
411
+ session.report.errorCode = failure instanceof PhoneError ? failure.code : signal.aborted ? "PHONE_CANCELLED" : "PHONE_OPERATION_FAILED";
412
+ session.report.message = failure instanceof PhoneError ? failure.message : signal.aborted ? "Phone operation stopped" : "Phone operation failed; inspect device diagnostics";
413
+ session.report.stopReason = String(session.report.status) === "outcome_unknown" ? "inspect_before_retry" : "operation_failed";
414
+ }
415
+ if (terminal.has(session.report.status)) this.repository.release(session.device.serial, session.lease);
416
+ this.publish(session);
417
+ if (failure instanceof PhoneError) throw failure;
418
+ throw new PhoneError(signal.aborted ? "PHONE_CANCELLED" : "PHONE_OPERATION_FAILED", signal.aborted ? "Phone operation stopped" : "Phone operation failed; inspect device diagnostics");
419
+ } finally {
420
+ if (heartbeat) clearInterval(heartbeat);
421
+ observation?.frame.fill(0);
422
+ log.info({
423
+ sessionId: session.report.sessionId,
424
+ conversationId: session.report.owner,
425
+ phase: "goal_end",
426
+ durationMs: Date.now() - startedAt,
427
+ status: session.report.status,
428
+ stopReason: session.report.stopReason,
429
+ actions: session.report.actions,
430
+ modelRequests: session.report.modelRequests
431
+ }, "Phone goal finished");
432
+ }
433
+ }
434
+ recordObservation(session, observation) {
435
+ session.report.foregroundApp = observation.package;
436
+ session.report.facts = [...new Set(observation.elements.flatMap((node) => node.password ? [] : [node.text, node.description]).filter(Boolean))].slice(0, 60);
437
+ }
438
+ async dispatch(session, action, observation, context, signal, epoch = session.report.controlEpoch, resetTask = false) {
439
+ return this.io(session, async () => {
440
+ signal.throwIfAborted();
441
+ this.guard(session, context);
442
+ if (epoch !== session.report.controlEpoch || session.report.controlOwner === "none" || session.report.handoff !== "idle") return false;
443
+ if (session.human && Date.now() > (session.humanExpiresAt ?? 0)) throw new PhoneError("PHONE_CONTROL_REQUIRED", "Manual control expired", 409);
444
+ if (!session.human && session.report.actions - (session.report.humanActions ?? 0) >= context.config.phone.maxActionsPerSession) throw new PhoneError("PHONE_ACTION_LIMIT", "Phone action limit reached");
445
+ if (session.human && (session.report.humanActions ?? 0) >= 1200) throw new PhoneError("PHONE_ACTION_LIMIT", "Manual input limit reached; reopen the session");
446
+ if (action.type === "launch" && !phoneAllowsApp(session.device, action.package)) throw new PhoneError("PHONE_APP_DENIED", "Application is not authorized");
447
+ if (action.type === "home" && phoneAppAccess(session.device) !== "all") throw new PhoneError("PHONE_APP_DENIED", "Home leaves the app allowlist");
448
+ if (action.type === "typeText" && !observation.elements.some((node) => node.focused && node.editable && !node.password)) throw new PhoneError("PHONE_INPUT_NOT_FOCUSED", "Focus a non-password editable field before typing", 409);
449
+ const step = {
450
+ action: action.type,
451
+ at: Date.now(),
452
+ dispatch: "unknown",
453
+ actor: session.human ? "human" : "agent"
454
+ };
455
+ session.report.currentAction = action.type;
456
+ session.report.steps.push(step);
457
+ session.report.actions++;
458
+ if (session.human) {
459
+ session.report.humanActions = (session.report.humanActions ?? 0) + 1;
460
+ session.report.steps = session.report.steps.slice(-200);
461
+ }
462
+ this.publish(session);
463
+ try {
464
+ await session.driver.execute(session.device.serial, action, observation, signal, session.human ? {
465
+ settleMs: 0,
466
+ ...resetTask ? { resetTask: true } : {}
467
+ } : void 0);
468
+ step.dispatch = "completed";
469
+ } catch (error) {
470
+ session.report.status = "outcome_unknown";
471
+ session.frames.clear();
472
+ throw error;
473
+ }
474
+ signal.throwIfAborted();
475
+ this.publish(session);
476
+ return true;
477
+ });
478
+ }
479
+ async stop(owner, op) {
480
+ this.openings.get(owner)?.abort();
481
+ const session = this.sessions.get(owner);
482
+ if (!session) return { status: "closed" };
483
+ session.controller.abort();
484
+ session.prediction?.abort();
485
+ session.human = void 0;
486
+ session.report.controlOwner = "none";
487
+ session.report.controlEpoch++;
488
+ if (op === "pause") await session.stream?.revoke();
489
+ else {
490
+ await session.stream?.close();
491
+ session.stream = void 0;
492
+ }
493
+ for (const wake of session.wake) wake();
494
+ session.report.status = op === "pause" ? "paused" : op === "cancel" ? "cancelled" : "closed";
495
+ session.report.stopReason = op;
496
+ if (op !== "close") session.report.verified = false;
497
+ if (op !== "pause") {
498
+ session.report.facts = void 0;
499
+ session.report.message = void 0;
500
+ session.frames.clear();
501
+ session.adapter = { predict: async () => {
502
+ throw new PhoneError("PHONE_OPEN_REQUIRED", "Reopen the phone session");
503
+ } };
504
+ }
505
+ this.publish(session);
506
+ await session.active?.catch(() => {});
507
+ await session.io.catch(() => {});
508
+ if (op !== "pause") this.repository.release(session.device.serial, session.lease);
509
+ return this.publish(session);
510
+ }
511
+ async io(session, work) {
512
+ const operation = session.io.catch(() => {}).then(async () => {
513
+ session.ioBusy = true;
514
+ try {
515
+ return await work();
516
+ } finally {
517
+ session.ioBusy = false;
518
+ }
519
+ });
520
+ session.io = operation.catch(() => {});
521
+ return operation;
522
+ }
523
+ async observe(session, signal, forText = false) {
524
+ return this.io(session, async () => {
525
+ signal.throwIfAborted();
526
+ const observation = await (forText && session.driver.observeForText ? session.driver.observeForText : session.driver.observe).call(session.driver, session.device.serial, signal);
527
+ if (phoneAllowsApp(session.device, observation.package)) {
528
+ if (Date.now() - session.viewedAt < 3e4 && !terminal.has(session.report.status)) session.frames.set(observation);
529
+ } else session.frames.clear();
530
+ return observation;
531
+ });
532
+ }
533
+ async capture(session, signal) {
534
+ return this.io(session, async () => {
535
+ signal.throwIfAborted();
536
+ this.guard(session, session.context);
537
+ const observation = await (session.driver.capture ?? session.driver.observe).call(session.driver, session.device.serial, signal);
538
+ try {
539
+ this.guard(session, session.context);
540
+ this.allowedScreen(session, observation);
541
+ if (!terminal.has(session.report.status)) session.frames.set(observation);
542
+ return observation;
543
+ } catch (error) {
544
+ observation.frame.fill(0);
545
+ session.frames.clear();
546
+ throw error;
547
+ }
548
+ });
549
+ }
550
+ async waitForAgent(session, signal) {
551
+ let waited = false;
552
+ while (session.report.controlOwner !== "agent") {
553
+ waited = true;
554
+ signal.throwIfAborted();
555
+ await new Promise((resolve, reject) => {
556
+ const cleanup = () => {
557
+ session.wake.delete(done);
558
+ signal.removeEventListener("abort", aborted);
559
+ };
560
+ const done = () => {
561
+ cleanup();
562
+ resolve();
563
+ };
564
+ const aborted = () => {
565
+ cleanup();
566
+ reject(signal.reason);
567
+ };
568
+ session.wake.add(done);
569
+ signal.addEventListener("abort", aborted, { once: true });
570
+ if (signal.aborted) aborted();
571
+ });
572
+ }
573
+ signal.throwIfAborted();
574
+ return waited;
575
+ }
576
+ startMaintenance() {
577
+ if (this.maintenance) return;
578
+ this.maintenance = setInterval(() => {
579
+ for (const session of this.sessions.values()) {
580
+ if (terminal.has(session.report.status)) {
581
+ session.frames.clear();
582
+ const stream = session.stream;
583
+ session.stream = void 0;
584
+ stream?.close();
585
+ continue;
586
+ }
587
+ try {
588
+ this.guard(session, session.context);
589
+ if (session.human && Date.now() > (session.humanExpiresAt ?? 0)) {
590
+ session.human = void 0;
591
+ session.report.controlOwner = "none";
592
+ session.report.controlEpoch++;
593
+ session.report.status = "paused";
594
+ session.report.stopReason = "control_disconnected";
595
+ this.publish(session);
596
+ session.stream?.revoke().catch(() => this.stop(session.report.owner, "cancel"));
597
+ }
598
+ if (Date.now() - session.viewedAt > 3e4) session.frames.clear();
599
+ } catch {
600
+ session.frames.clear();
601
+ this.stop(session.report.owner, "cancel");
602
+ }
603
+ }
604
+ }, 3e3);
605
+ this.maintenance.unref();
606
+ }
607
+ liveSession(owner, id, context) {
608
+ const session = this.sessions.get(owner);
609
+ if (!session || session.report.sessionId !== id) throw new PhoneError("PHONE_SESSION_NOT_FOUND", "Phone session not found", 404);
610
+ if (context.agentId !== session.report.agentId) throw new PhoneError("PHONE_ACCESS_CHANGED", "Agent binding changed", 403);
611
+ if (!terminal.has(session.report.status)) try {
612
+ this.guard(session, context);
613
+ } catch (error) {
614
+ session.frames.clear();
615
+ throw error;
616
+ }
617
+ session.context = context;
618
+ this.startMaintenance();
619
+ return session;
620
+ }
621
+ liveStatus(owner, id, context, client) {
622
+ const session = this.liveSession(owner, id, context);
623
+ return {
624
+ report: structuredClone(session.report),
625
+ ownControl: Boolean(session.human === client),
626
+ allowedApps: [...session.device.allowedApps],
627
+ appAccess: phoneAppAccess(session.device),
628
+ frame: session.frames.get() ? {
629
+ seq: session.frames.get().seq,
630
+ capturedAt: session.frames.get().capturedAt
631
+ } : null
632
+ };
633
+ }
634
+ async liveVideo(owner, id, context) {
635
+ const session = this.liveSession(owner, id, context);
636
+ if (terminal.has(session.report.status)) throw new PhoneError("PHONE_OPEN_REQUIRED", "Phone session ended", 409);
637
+ if (session.streamOpening) return session.streamOpening;
638
+ const opening = this.openLiveVideo(owner, session);
639
+ session.streamOpening = opening;
640
+ try {
641
+ return await opening;
642
+ } finally {
643
+ if (session.streamOpening === opening) session.streamOpening = void 0;
644
+ }
645
+ }
646
+ async openLiveVideo(owner, session) {
647
+ if (!session.stream) {
648
+ await session.io;
649
+ if (terminal.has(session.report.status)) throw new PhoneError("PHONE_VIDEO_ENDED", "Phone session ended", 409);
650
+ const failed = () => {
651
+ this.stop(owner, "cancel");
652
+ };
653
+ const provider = session.device.connection ? await this.cloud.interactive(session.device, failed) : this.interactiveFactory(session.adbPath, session.device.serial, failed);
654
+ if (terminal.has(session.report.status)) {
655
+ await provider.close();
656
+ throw new PhoneError("PHONE_VIDEO_ENDED", "Phone session ended", 409);
657
+ }
658
+ session.stream = new PhoneInteractiveSession(provider, async () => {
659
+ if (terminal.has(session.report.status)) throw new PhoneError("PHONE_VIDEO_ENDED", "Phone session ended", 409);
660
+ this.guard(session, session.context);
661
+ const app = await provider.foreground(AbortSignal.timeout(2e3));
662
+ if (!phoneAllowsApp(session.device, app)) throw new PhoneError("PHONE_APP_DENIED", "Current page is outside the authorized applications", 403);
663
+ session.viewedAt = Date.now();
664
+ }, (client, epoch) => {
665
+ if (terminal.has(session.report.status) || session.report.handoff !== "idle" || session.report.controlOwner !== "human" || session.human !== client || session.report.controlEpoch !== epoch || Date.now() > (session.humanExpiresAt ?? 0)) throw new PhoneError("PHONE_CONTROL_CHANGED", "Manual control is no longer available", 409);
666
+ session.humanExpiresAt = Date.now() + 15e3;
667
+ if ((session.report.humanActions ?? 0) >= 1200) throw new PhoneError("PHONE_ACTION_LIMIT", "Manual input limit reached; reopen the session");
668
+ }, (action) => {
669
+ session.report.actions++;
670
+ session.report.humanActions = (session.report.humanActions ?? 0) + 1;
671
+ session.report.steps.push({
672
+ action,
673
+ at: Date.now(),
674
+ actor: "human",
675
+ dispatch: "completed"
676
+ });
677
+ session.report.steps = session.report.steps.slice(-200);
678
+ this.publish(session);
679
+ });
680
+ }
681
+ const stream = session.stream;
682
+ try {
683
+ await stream.start();
684
+ return stream;
685
+ } catch (error) {
686
+ await stream.close();
687
+ if (session.stream === stream) session.stream = void 0;
688
+ throw error;
689
+ }
690
+ }
691
+ async disconnectVideo(owner, id, client) {
692
+ const session = this.sessions.get(owner);
693
+ if (!session || session.report.sessionId !== id) return;
694
+ if (session.human === client) {
695
+ session.human = void 0;
696
+ session.report.controlOwner = "none";
697
+ session.report.controlEpoch++;
698
+ session.report.status = "paused";
699
+ session.report.stopReason = "control_disconnected";
700
+ this.publish(session);
701
+ try {
702
+ await session.stream?.revoke();
703
+ } catch {
704
+ await this.stop(owner, "cancel");
705
+ }
706
+ }
707
+ if (session.stream?.viewerCount === 0) {
708
+ const stream = session.stream;
709
+ session.stream = void 0;
710
+ await stream.close();
711
+ }
712
+ }
713
+ async liveFrame(owner, id, context, signal) {
714
+ const session = this.liveSession(owner, id, context);
715
+ if (terminal.has(session.report.status)) {
716
+ session.frames.clear();
717
+ throw new PhoneError("PHONE_OPEN_REQUIRED", "Phone session ended", 409);
718
+ }
719
+ session.viewedAt = Date.now();
720
+ if (!session.frames.get() || Date.now() - session.frames.get().capturedAt > (session.human ? 150 : 600)) {
721
+ session.preview ??= (async () => {
722
+ const observation = await this.capture(session, signal);
723
+ try {
724
+ this.guard(session, context);
725
+ this.allowedScreen(session, observation);
726
+ } catch (error) {
727
+ session.frames.clear();
728
+ throw error;
729
+ } finally {
730
+ observation.frame.fill(0);
731
+ }
732
+ })().finally(() => {
733
+ session.preview = void 0;
734
+ });
735
+ await session.preview;
736
+ }
737
+ this.guard(session, context);
738
+ const frame = session.frames.get();
739
+ if (!frame) throw new PhoneError("PHONE_OPEN_REQUIRED", "Phone session ended", 409);
740
+ return {
741
+ ...frame,
742
+ frame: Buffer.from(frame.frame)
743
+ };
744
+ }
745
+ async liveControl(owner, id, context, client, input) {
746
+ const session = this.liveSession(owner, id, context);
747
+ if (input.op === "heartbeat") {
748
+ if (terminal.has(session.report.status) || input.epoch !== session.report.controlEpoch || session.human !== client || Date.now() > (session.humanExpiresAt ?? 0)) throw new PhoneError("PHONE_CONTROL_REQUIRED", "Manual control is no longer available", 409);
749
+ session.humanExpiresAt = Date.now() + 15e3;
750
+ return this.liveStatus(owner, id, context, client);
751
+ }
752
+ const key = `${client}:${input.operationId}`;
753
+ const hash = createHash("sha256").update(JSON.stringify(input)).digest("hex");
754
+ const receipt = session.receipts.get(key);
755
+ if (receipt) {
756
+ if (receipt.hash !== hash) throw new PhoneError("PHONE_DUPLICATE_OPERATION", "Operation ID was reused with different input", 409);
757
+ return receipt.result;
758
+ }
759
+ if (session.receipts.size >= 240 && ![
760
+ "cancel",
761
+ "release",
762
+ "heartbeat"
763
+ ].includes(input.op)) throw new PhoneError("PHONE_SESSION_LIMIT", "Phone control request limit reached; reopen the session");
764
+ const result = session.commands.catch(() => {}).then(async () => {
765
+ if (terminal.has(session.report.status)) throw new PhoneError("PHONE_OPEN_REQUIRED", "Phone session ended", 409);
766
+ this.guard(session, context);
767
+ if (input.epoch !== session.report.controlEpoch) throw new PhoneError("PHONE_CONTROL_CHANGED", "Control changed. Refresh before trying again.", 409);
768
+ if (session.human && session.human !== client) throw new PhoneError("PHONE_CONTROL_BUSY", "Another browser window controls this phone", 409);
769
+ if (input.op === "cancel") {
770
+ await this.stop(owner, "cancel");
771
+ return this.liveStatus(owner, id, context, client);
772
+ }
773
+ if (input.op === "takeover" || input.op === "pause") {
774
+ if (input.op === "takeover" && session.report.mode !== "control") throw new PhoneError("PHONE_OBSERVE_ONLY", "This phone session only allows observation", 403);
775
+ session.report.controlOwner = "none";
776
+ session.report.controlEpoch++;
777
+ session.report.handoff = "acquiring";
778
+ session.report.status = "paused";
779
+ session.budget?.pause();
780
+ session.prediction?.abort();
781
+ this.publish(session);
782
+ await session.stream?.revoke();
783
+ await session.io;
784
+ if (terminal.has(session.report.status) || session.report.steps.at(-1)?.dispatch === "unknown") {
785
+ session.report.handoff = "idle";
786
+ this.publish(session);
787
+ throw new PhoneError("PHONE_DISPATCH_UNKNOWN", "Inspect the phone before taking control; previous input is unresolved", 409);
788
+ }
789
+ if (session.controller.signal.aborted && !session.active) session.controller = new AbortController();
790
+ session.human = void 0;
791
+ if (input.op === "takeover") {
792
+ session.human = client;
793
+ session.humanExpiresAt = Date.now() + 15e3;
794
+ session.report.controlOwner = "human";
795
+ }
796
+ session.report.handoff = "idle";
797
+ this.publish(session);
798
+ } else if (input.op === "release") {
799
+ if (session.human !== client) throw new PhoneError("PHONE_CONTROL_REQUIRED", "This browser does not control the phone", 409);
800
+ session.human = void 0;
801
+ session.report.controlOwner = "none";
802
+ session.report.controlEpoch++;
803
+ await session.stream?.revoke();
804
+ session.report.status = "paused";
805
+ session.report.stopReason = "user_paused";
806
+ this.publish(session);
807
+ } else if (input.op === "handback") {
808
+ if (session.report.controlOwner === "agent") throw new PhoneError("PHONE_NOT_PAUSED", "The Agent already controls this phone", 409);
809
+ if (session.controller.signal.aborted && !session.active) session.controller = new AbortController();
810
+ session.report.handoff = "returning";
811
+ session.report.controlEpoch++;
812
+ session.human = void 0;
813
+ session.report.controlOwner = "none";
814
+ this.publish(session);
815
+ await session.stream?.revoke();
816
+ let observation;
817
+ try {
818
+ observation = await this.observe(session, session.controller.signal);
819
+ if (terminal.has(session.report.status)) throw new PhoneError("PHONE_OPEN_REQUIRED", "Phone session ended during handback", 409);
820
+ this.guard(session, context);
821
+ this.allowedScreen(session, observation);
822
+ this.recordObservation(session, observation);
823
+ } catch (error) {
824
+ session.frames.clear();
825
+ session.report.handoff = "idle";
826
+ if (!terminal.has(session.report.status)) session.report.status = "paused";
827
+ this.publish(session);
828
+ throw error;
829
+ } finally {
830
+ observation?.frame.fill(0);
831
+ }
832
+ session.report.controlOwner = "agent";
833
+ session.report.status = session.active ? "running" : "ready";
834
+ session.report.handoff = "idle";
835
+ session.report.message = void 0;
836
+ session.report.stopReason = void 0;
837
+ session.budget?.resume();
838
+ this.publish(session);
839
+ for (const wake of session.wake) wake();
840
+ } else if (input.op === "recover") {
841
+ if (session.human !== client || Date.now() > (session.humanExpiresAt ?? 0)) throw new PhoneError("PHONE_CONTROL_REQUIRED", "Take over the phone first", 409);
842
+ if (input.action?.type !== "launch" || !phoneAllowsApp(session.device, input.action.package)) throw new PhoneError("PHONE_APP_DENIED", "Choose an authorized recovery application", 403);
843
+ const observation = await this.io(session, () => session.driver.capture ? session.driver.capture(session.device.serial, session.controller.signal) : session.driver.observe(session.device.serial, session.controller.signal));
844
+ try {
845
+ if (!await this.dispatch(session, input.action, observation, context, session.controller.signal, input.epoch, true)) throw new PhoneError("PHONE_CONTROL_CHANGED", "Control changed before recovery", 409);
846
+ session.humanExpiresAt = Date.now() + 15e3;
847
+ } finally {
848
+ observation.frame.fill(0);
849
+ session.frames.clear();
850
+ }
851
+ this.publish(session);
852
+ } else if (input.op === "input") {
853
+ if (session.human !== client || Date.now() > (session.humanExpiresAt ?? 0)) throw new PhoneError("PHONE_CONTROL_REQUIRED", "Take over the phone first", 409);
854
+ const frame = session.frames.metadata(input.frameSeq);
855
+ const video = input.geometry !== void 0 && session.stream?.verifyGeometry(input.geometry);
856
+ if (!video && (!frame || Date.now() - frame.capturedAt > 5e3)) throw new PhoneError("PHONE_FRAME_STALE", "Wait for a fresh phone image before interacting", 409);
857
+ if (video && input.action?.type !== "typeText" && input.action?.type !== "back") throw new PhoneError("PHONE_INVALID_INPUT", "Use the continuous input channel for video gestures", 409);
858
+ if (!input.action || input.action.type === "launch" || input.action.type === "home" || input.action.type === "wait") throw new PhoneError("PHONE_INVALID_INPUT", "Unsupported manual input");
859
+ const observation = input.action.type === "typeText" ? await this.observe(session, session.controller.signal, true) : await this.capture(session, session.controller.signal);
860
+ try {
861
+ if (input.epoch !== session.report.controlEpoch || session.human !== client || Date.now() > (session.humanExpiresAt ?? 0)) throw new PhoneError("PHONE_CONTROL_CHANGED", "Manual control expired while observing the phone", 409);
862
+ this.allowedScreen(session, observation);
863
+ if (frame && !video && (observation.width !== frame.width || observation.height !== frame.height || observation.rotation !== frame.rotation || observation.package !== frame.package)) throw new PhoneError("PHONE_FRAME_STALE", "Phone orientation or application changed; retry on the new image", 409);
864
+ if (!await this.dispatch(session, input.action, observation, context, session.controller.signal, input.epoch)) throw new PhoneError("PHONE_CONTROL_CHANGED", "Control changed before input dispatch", 409);
865
+ session.humanExpiresAt = Date.now() + 15e3;
866
+ } finally {
867
+ observation.frame.fill(0);
868
+ }
869
+ session.frames.invalidate();
870
+ this.publish(session);
871
+ }
872
+ return {
873
+ ...this.liveStatus(owner, id, context, client),
874
+ resumeTurnRequired: input.op === "handback" && !session.active
875
+ };
876
+ });
877
+ session.commands = result.catch(() => {
878
+ if (terminal.has(session.report.status)) {
879
+ session.frames.clear();
880
+ this.repository.release(session.device.serial, session.lease);
881
+ session.controller.abort();
882
+ for (const wake of session.wake) wake();
883
+ this.publish(session);
884
+ }
885
+ });
886
+ session.receipts.set(key, {
887
+ hash,
888
+ result
889
+ });
890
+ return result;
891
+ }
892
+ async stopDevice(id) {
893
+ await Promise.all(this.reports().filter((report) => report.deviceId === id).map((report) => this.stop(report.owner, "cancel")));
894
+ this.cloudPhones?.disconnect(id);
895
+ }
896
+ async shutdown() {
897
+ for (const controller of this.openings.values()) controller.abort();
898
+ await Promise.all([...this.sessions.keys()].map((owner) => this.stop(owner, "close")));
899
+ this.cloudPhones?.close();
900
+ this.sessions.clear();
901
+ if (this.maintenance) clearInterval(this.maintenance);
902
+ this.maintenance = void 0;
903
+ }
904
+ };
905
+ let runtime;
906
+ function getPhoneRuntime() {
907
+ return runtime ??= new PhoneRuntime();
908
+ }
909
+ function phoneExecutionContext(config, agentId) {
910
+ const agent = resolveEffectiveAgentConfigForAgent(agentId).config;
911
+ const denied = (value) => value.tools.phone_use?.mode === "deny" || Boolean(value.toolAllowlist && !value.toolAllowlist.includes("phone_use"));
912
+ return {
913
+ config,
914
+ agentId,
915
+ modelRef: agent.models.phoneUse?.primary,
916
+ toolDenied: denied(agent),
917
+ validateBinding: () => {
918
+ const current = resolveEffectiveAgentConfigForAgent(agentId).config;
919
+ return current.models.phoneUse?.primary === agent.models.phoneUse?.primary && !denied(current);
920
+ }
921
+ };
922
+ }
923
+ //#endregion
924
+ export { PhoneRuntime, getPhoneRuntime, phoneExecutionContext };