@xopcai/xopc 0.0.367 → 0.0.369

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 (263) hide show
  1. package/dist/browser-ext/_locales/en/messages.json +30 -1
  2. package/dist/browser-ext/_locales/zh_CN/messages.json +30 -1
  3. package/dist/browser-ext/dist/assets/{auth-beGtbLPS.js → page-context-BeworyNB.js} +1331 -1355
  4. package/dist/browser-ext/dist/assets/{sidepanel-BT0ojEzG.css → sidepanel-Ccl0eSa1.css} +43 -0
  5. package/dist/browser-ext/dist/background.js +680 -182
  6. package/dist/browser-ext/dist/sidepanel.html +2 -2
  7. package/dist/browser-ext/dist/sidepanel.js +372 -193
  8. package/dist/browser-ext/manifest.json +2 -8
  9. package/dist/extensions/telegram/xopc.extension.json +1 -1
  10. package/dist/gateway/static/root/assets/{activity-store-Cuf3JFFJ.js → activity-store-BQ3PMp2q.js} +1 -1
  11. package/dist/gateway/static/root/assets/{agent-avatar-dicebear-DyATPNTe.js → agent-avatar-dicebear-Stj17S9u.js} +1 -1
  12. package/dist/gateway/static/root/assets/agent-defaults-settings-panel-CCRfvuov.js +1 -0
  13. package/dist/gateway/static/root/assets/agent-plugin-dialog-CRlzd8El.js +1 -0
  14. package/dist/gateway/static/root/assets/agents-_ug2LCQb.js +66 -0
  15. package/dist/gateway/static/root/assets/{agents-admin-api-D842OcNo.js → agents-admin-api-HBZvVyzF.js} +1 -1
  16. package/dist/gateway/static/root/assets/app-management-settings-panel-pJFe6M6c.js +1 -0
  17. package/dist/gateway/static/root/assets/{appearance-settings-DQr0o6Lm.js → appearance-settings-BrxSpBWa.js} +1 -1
  18. package/dist/gateway/static/root/assets/{apps-page-CltMV4Xp.js → apps-page-DegSl5Lh.js} +1 -1
  19. package/dist/gateway/static/root/assets/{archive-plugin-DT8Nio-r.js → archive-plugin-BO3I9zRP.js} +1 -1
  20. package/dist/gateway/static/root/assets/{automations-page-BIfWS_3Y.js → automations-page-BZQ_VHjp.js} +1 -1
  21. package/dist/gateway/static/root/assets/automations-workspace-BQywN6nG.js +6 -0
  22. package/dist/gateway/static/root/assets/{binary-plugins-7qs3qcW9.js → binary-plugins-gFD7vH9R.js} +1 -1
  23. package/dist/gateway/static/root/assets/{block-editor-BFPUlxJ6.js → block-editor-3XjFv3kK.js} +28 -28
  24. package/dist/gateway/static/root/assets/browser-automation-inputs-DxRoKFun.js +1 -0
  25. package/dist/gateway/static/root/assets/browser-automations-page-yiEuG1v7.js +3 -0
  26. package/dist/gateway/static/root/assets/browser-settings-page-ch2_bQF0.js +3 -0
  27. package/dist/gateway/static/root/assets/{capabilities-K0Zb_lH0.js → capabilities-B48r0DDa.js} +1 -1
  28. package/dist/gateway/static/root/assets/{capabilities-BzWFO_x_.js → capabilities-DO-vfNKI.js} +1 -1
  29. package/dist/gateway/static/root/assets/capabilities-page-BEnFt_dj.js +2 -0
  30. package/dist/gateway/static/root/assets/capabilities-settings-panel-NckMsJ5N.js +8 -0
  31. package/dist/gateway/static/root/assets/{capability-header-actions-AMn_vTNv.js → capability-header-actions-CY8KCO9j.js} +1 -1
  32. package/dist/gateway/static/root/assets/channels-CHOS2CGa.js +1 -0
  33. package/dist/gateway/static/root/assets/{chat-terminal-dock-C7nAROhF.js → chat-terminal-dock-Bwknnb4c.js} +2 -2
  34. package/dist/gateway/static/root/assets/computer-settings-page-B2xeFCSt.js +1 -0
  35. package/dist/gateway/static/root/assets/{connector-service-page-CTx1B8wx.js → connector-service-page-C83DnAqK.js} +1 -1
  36. package/dist/gateway/static/root/assets/connectors-page-D6wTmWA6.js +2 -0
  37. package/dist/gateway/static/root/assets/{date-picker-Cx_xIKvN.js → date-picker-Bq9GR-DU.js} +1 -1
  38. package/dist/gateway/static/root/assets/{dependency-picker-DYm96PDu.js → dependency-picker-BTERkA9X.js} +1 -1
  39. package/dist/gateway/static/root/assets/{desktop-pet-D90OzgeR.js → desktop-pet-BwsbQw8V.js} +1 -1
  40. package/dist/gateway/static/root/assets/{desktop-pet-settings-CmdCecjm.js → desktop-pet-settings-sL9i44pP.js} +1 -1
  41. package/dist/gateway/static/root/assets/device-pairing-wizard-DI2bqaKO.js +1 -0
  42. package/dist/gateway/static/root/assets/{directory-picker-path-field-DZpaoeNa.js → directory-picker-path-field-BFlDmsS5.js} +1 -1
  43. package/dist/gateway/static/root/assets/{extension-debug-page-Bv6QE8kC.js → extension-debug-page-7Z1Tp7-w.js} +1 -1
  44. package/dist/gateway/static/root/assets/{extension-page-CEsq71Cm.js → extension-page-uGqvb8QX.js} +1 -1
  45. package/dist/gateway/static/root/assets/{extension-provider-CX-sK0Tw.js → extension-provider-xlSyz4Z6.js} +1 -1
  46. package/dist/gateway/static/root/assets/{extension-settings-page-UyOHF39P.js → extension-settings-page-CKHgrjOs.js} +1 -1
  47. package/dist/gateway/static/root/assets/{fallback-models-CKv_Wqg8.js → fallback-models-BwQg0G1D.js} +1 -1
  48. package/dist/gateway/static/root/assets/{gateway-config-swr-Cjnkle5J.js → gateway-config-swr-7RKqgU_U.js} +1 -1
  49. package/dist/gateway/static/root/assets/{gateway-realtime-DCCLdyQD.js → gateway-realtime-DDWymi-o.js} +1 -1
  50. package/dist/gateway/static/root/assets/gateway-realtime-store-BheKaO6d.js +1 -0
  51. package/dist/gateway/static/root/assets/gateway-settings-BAQnSEMJ.js +1 -0
  52. package/dist/gateway/static/root/assets/{gateway-startup-retry-CXKmdVzN.js → gateway-startup-retry-jiV5sbEX.js} +1 -1
  53. package/dist/gateway/static/root/assets/home-page-BZDjvwet.js +2 -0
  54. package/dist/gateway/static/root/assets/{image-generation-api-NLHzm4Nc.js → image-generation-api-DO4mXb6I.js} +1 -1
  55. package/dist/gateway/static/root/assets/{imports-page-C7qvlBTQ.js → imports-page-re1_Nh64.js} +1 -1
  56. package/dist/gateway/static/root/assets/index-BB1z8QxA.js +106 -0
  57. package/dist/gateway/static/root/assets/index-D9R6tU7r.css +1 -0
  58. package/dist/gateway/static/root/assets/{keyboard-shortcuts-settings-pkgFWJb-.js → keyboard-shortcuts-settings-B_xja9LN.js} +1 -1
  59. package/dist/gateway/static/root/assets/{local-app-workbench-page-dMDhZXAi.js → local-app-workbench-page-CErdCrPX.js} +1 -1
  60. package/dist/gateway/static/root/assets/{local-apps-page-TpUAfOn2.js → local-apps-page-BPL2RXdK.js} +1 -1
  61. package/dist/gateway/static/root/assets/{local-session-drafts-Cvv7I0fw.js → local-session-drafts-DRPsDCj1.js} +1 -1
  62. package/dist/gateway/static/root/assets/{locale-store-C8TMD2gn.js → locale-store-COVscAqC.js} +1 -1
  63. package/dist/gateway/static/root/assets/logs-page-DL-BM0xq.js +2 -0
  64. package/dist/gateway/static/root/assets/management-settings-BOVRHhXk.js +1 -0
  65. package/dist/gateway/static/root/assets/{markdown-view-Dse7rkio.js → markdown-view-B_qnAcW2.js} +1 -1
  66. package/dist/gateway/static/root/assets/{media-plugins-BZrnzaBM.js → media-plugins-C0Pc-wSY.js} +1 -1
  67. package/dist/gateway/static/root/assets/messages-Bb2GK-Nj.js +3 -0
  68. package/dist/gateway/static/root/assets/models-hub-cache-DyPPksMP.js +1 -0
  69. package/dist/gateway/static/root/assets/note-detail-panel-DJJK2yL4.js +5 -0
  70. package/dist/gateway/static/root/assets/{notes-api-DKiVeQKG.js → notes-api-sBgBcyLW.js} +1 -1
  71. package/dist/gateway/static/root/assets/notes-page-oCxO7ZTv.js +8 -0
  72. package/dist/gateway/static/root/assets/{page-context-capture-button-Cxzd4QPB.js → page-context-capture-button-tf77xIz3.js} +1 -1
  73. package/dist/gateway/static/root/assets/personal-page-DOk9gUK5.js +1 -0
  74. package/dist/gateway/static/root/assets/{product-open-page-D4hoEWiu.js → product-open-page-Dc3Y-Y7J.js} +1 -1
  75. package/dist/gateway/static/root/assets/{product-preview-content-aR38El2E.js → product-preview-content-MG_hmVA3.js} +1 -1
  76. package/dist/gateway/static/root/assets/{project-detail-page-D20QH4YB.js → project-detail-page-3uAzbW4T.js} +1 -1
  77. package/dist/gateway/static/root/assets/{projects-page-Bf3BIcT6.js → projects-page-MllfNLGA.js} +1 -1
  78. package/dist/gateway/static/root/assets/{registry-api-AdnxowbH.js → registry-api-Bh9UBb-f.js} +1 -1
  79. package/dist/gateway/static/root/assets/remote-access-hub-Bs-C70el.js +1 -0
  80. package/dist/gateway/static/root/assets/{runtime-tools-settings-panel-CBN3raz1.js → runtime-tools-settings-panel-DpJ5jNzR.js} +1 -1
  81. package/dist/gateway/static/root/assets/scenes-page-Dpxamkw6.js +2 -0
  82. package/dist/gateway/static/root/assets/{schema-form-CxrMGsT9.js → schema-form-DUeyQ1zh.js} +1 -1
  83. package/dist/gateway/static/root/assets/{send-session-input-CgOzhDZ0.js → send-session-input-Dgn4BRk_.js} +1 -1
  84. package/dist/gateway/static/root/assets/{session-api-_HIN8or3.js → session-api-yjpEz4pP.js} +1 -1
  85. package/dist/gateway/static/root/assets/{session-manager-CzCto5zT.js → session-manager-LInoZ_Yi.js} +1 -1
  86. package/dist/gateway/static/root/assets/sessions-page-BCVlNHtq.js +1 -0
  87. package/dist/gateway/static/root/assets/settings-advanced-gate-Bz-ue9fZ.js +1 -0
  88. package/dist/gateway/static/root/assets/{settings-page-D8Q3H3-o.js → settings-page-DouHuBXt.js} +1 -1
  89. package/dist/gateway/static/root/assets/{setup-status-panel-DvLMMiuS.js → setup-status-panel-B5BoZ-1y.js} +1 -1
  90. package/dist/gateway/static/root/assets/{share-link-dialog-Dg5os066.js → share-link-dialog-DJTabLAM.js} +1 -1
  91. package/dist/gateway/static/root/assets/{share-preview-page-DMvohAyr.js → share-preview-page-jw7h4t_B.js} +1 -1
  92. package/dist/gateway/static/root/assets/{shares-api-C96IO4qJ.js → shares-api-c0dfWz6h.js} +1 -1
  93. package/dist/gateway/static/root/assets/shares-settings-BlnlzGjH.js +1 -0
  94. package/dist/gateway/static/root/assets/{skill-api-C10ML0CH.js → skill-api-CfyX_0zN.js} +1 -1
  95. package/dist/gateway/static/root/assets/{skill-reload-api-DNbJzSfB.js → skill-reload-api-Ge5XymHa.js} +1 -1
  96. package/dist/gateway/static/root/assets/{skills-page-DObMKvA6.js → skills-page-DjdTeYzm.js} +1 -1
  97. package/dist/gateway/static/root/assets/system-settings-panel-DWf256TE.js +1 -0
  98. package/dist/gateway/static/root/assets/{task-detail-page-BR2L1L8l.js → task-detail-page-DE6N1ZQe.js} +2 -2
  99. package/dist/gateway/static/root/assets/{task-expandable-content-DL2Q2gre.js → task-expandable-content-hYcgCEE3.js} +1 -1
  100. package/dist/gateway/static/root/assets/{theme-store-DQ6KJa4l.js → theme-store-BOfCKgl6.js} +1 -1
  101. package/dist/gateway/static/root/assets/{tracing-settings-page-Ed8_GYUT.js → tracing-settings-page-DDWFXcST.js} +1 -1
  102. package/dist/gateway/static/root/assets/{tunnel-api-D3X8jDwo.js → tunnel-api-BBm9esrY.js} +1 -1
  103. package/dist/gateway/static/root/assets/{usage-page-BqzRyyb9.js → usage-page-C93x89uW.js} +1 -1
  104. package/dist/gateway/static/root/assets/{use-autosave-CRFefpzO.js → use-autosave-CSckcjFx.js} +1 -1
  105. package/dist/gateway/static/root/assets/{use-composer-attachments-OLrvSjn5.js → use-composer-attachments-C7Gaaai1.js} +1 -1
  106. package/dist/gateway/static/root/assets/{use-file-preview-expanded-CJvzfQvw.js → use-file-preview-expanded-Db2VYg1b.js} +2 -2
  107. package/dist/gateway/static/root/assets/user-model-page-B74LkVbp.js +2 -0
  108. package/dist/gateway/static/root/assets/{voice-api-key-field-R9zNWYR_.js → voice-api-key-field-GEw5x2fU.js} +1 -1
  109. package/dist/gateway/static/root/assets/{voice-config-api-DLudE9Rc.js → voice-config-api-3yyVwXln.js} +1 -1
  110. package/dist/gateway/static/root/assets/{work-discovery-overlay-DVmQrolV.js → work-discovery-overlay-Bs2MKiTv.js} +1 -1
  111. package/dist/gateway/static/root/assets/{workflow-run-setup-panel-DEkmuD3T.js → workflow-run-setup-panel-BR_U_snV.js} +2 -2
  112. package/dist/gateway/static/root/assets/{workflows-page-pqCB3sh4.js → workflows-page-Crk4SxtC.js} +3 -3
  113. package/dist/gateway/static/root/assets/{workspace-api-CdVUhyPo.js → workspace-api-Si2o3inw.js} +2 -2
  114. package/dist/gateway/static/root/index.html +27 -28
  115. package/dist/package.js +1 -1
  116. package/dist/packages/browser-control-contract/src/automation.js +213 -0
  117. package/dist/packages/browser-control-contract/src/index.js +43 -12
  118. package/dist/packages/gateway-contract/src/gateway-scopes.js +3 -1
  119. package/dist/packages/gateway-contract/src/notifications.js +1 -0
  120. package/dist/packages/realtime-protocol/src/voice-catalog.js +7 -1
  121. package/dist/packages/realtime-protocol/src/voice.js +9 -3
  122. package/dist/src/agent/coding/repository-instructions.js +8 -1
  123. package/dist/src/agent/tool-manuals/browser.d.ts +1 -1
  124. package/dist/src/agent/tool-manuals/browser.js +3 -3
  125. package/dist/src/agent/tools/browser/tool/browser-use-tool.js +1 -1
  126. package/dist/src/agent/tools/browser/tool/schemas.d.ts +0 -13
  127. package/dist/src/agent/tools/browser/tool/schemas.js +0 -4
  128. package/dist/src/agent/tools/browser-automation-tool.d.ts +74 -1
  129. package/dist/src/agent/tools/browser-automation-tool.js +52 -6
  130. package/dist/src/agent-config/schema.d.ts +1 -1
  131. package/dist/src/agent-config/schema.js +1 -1
  132. package/dist/src/automations/capabilities/write.d.ts +2 -0
  133. package/dist/src/automations/domain/types.d.ts +11 -0
  134. package/dist/src/automations/domain/validation.d.ts +4 -0
  135. package/dist/src/automations/domain/validation.js +1 -0
  136. package/dist/src/automations/draft/automation-draft-validator.d.ts +1 -0
  137. package/dist/src/automations/service/action-executor.js +11 -3
  138. package/dist/src/automations/service/automation-service.d.ts +1 -0
  139. package/dist/src/automations/service/automation-service.js +13 -1
  140. package/dist/src/bootstrap/application-state.js +3 -1
  141. package/dist/src/browser/automations/repository.d.ts +4 -0
  142. package/dist/src/browser/automations/repository.js +27 -6
  143. package/dist/src/browser/automations/runner.js +101 -9
  144. package/dist/src/browser/automations/runtime-service.js +4 -3
  145. package/dist/src/browser/automations/schema.d.ts +84 -1
  146. package/dist/src/browser/automations/schema.js +68 -2
  147. package/dist/src/browser/automations/service.d.ts +78 -1
  148. package/dist/src/browser/automations/service.js +36 -4
  149. package/dist/src/browser/automations/types.d.ts +19 -4
  150. package/dist/src/browser/drivers/extension-driver.d.ts +5 -5
  151. package/dist/src/browser/drivers/extension-driver.js +10 -10
  152. package/dist/src/browser/drivers/playwright-driver.js +46 -0
  153. package/dist/src/browser/index.d.ts +1 -2
  154. package/dist/src/browser/index.js +2 -3
  155. package/dist/src/browser/policy/browser-policy.d.ts +0 -1
  156. package/dist/src/browser/policy/browser-policy.js +1 -4
  157. package/dist/src/browser/providers/index.d.ts +1 -1
  158. package/dist/src/browser/providers/index.js +2 -2
  159. package/dist/src/browser/providers/realtime-extension.d.ts +1 -1
  160. package/dist/src/browser/providers/realtime-extension.js +5 -1
  161. package/dist/src/browser/recordings/service.d.ts +22 -0
  162. package/dist/src/browser/recordings/service.js +160 -0
  163. package/dist/src/browser/runtime/browser-runtime.d.ts +1 -1
  164. package/dist/src/browser/runtime/browser-runtime.js +19 -56
  165. package/dist/src/cli/commands/doctor/checks/database-schema.js +1 -1
  166. package/dist/src/computer/runtime.js +2 -2
  167. package/dist/src/config/schema.d.ts +12 -12
  168. package/dist/src/config/schema.js +12 -12
  169. package/dist/src/endpoint-tools/policy.js +4 -3
  170. package/dist/src/gateway/hono/routes/browser-automations.js +23 -7
  171. package/dist/src/gateway/hono/routes/browser-recordings.d.ts +3 -0
  172. package/dist/src/gateway/hono/routes/browser-recordings.js +78 -0
  173. package/dist/src/gateway/hono/routes/browser.js +1 -34
  174. package/dist/src/gateway/hono/routes/device-push.js +1 -0
  175. package/dist/src/gateway/hono/routes/home.js +1 -1
  176. package/dist/src/gateway/hono/routes/lazy-bundles.js +8 -0
  177. package/dist/src/gateway/hono/routes/personal-agent.js +35 -0
  178. package/dist/src/gateway/hono/routes/voice.js +5 -2
  179. package/dist/src/gateway/security/gateway-scopes.js +3 -1
  180. package/dist/src/gateway/service/agent-runner.js +1 -0
  181. package/dist/src/home-intelligence/generator.d.ts +2 -0
  182. package/dist/src/home-intelligence/generator.js +53 -4
  183. package/dist/src/home-intelligence/host.d.ts +2 -1
  184. package/dist/src/home-intelligence/host.js +23 -3
  185. package/dist/src/migrations/cloud-public-models.d.ts +7 -0
  186. package/dist/src/migrations/cloud-public-models.js +157 -0
  187. package/dist/src/notifications/clarification.d.ts +5 -0
  188. package/dist/src/notifications/clarification.js +66 -0
  189. package/dist/src/notifications/device-store.js +1 -0
  190. package/dist/src/notifications/harmony-push.d.ts +1 -0
  191. package/dist/src/notifications/harmony-push.js +3 -1
  192. package/dist/src/notifications/planner.d.ts +1 -0
  193. package/dist/src/notifications/service.d.ts +2 -2
  194. package/dist/src/notifications/service.js +20 -4
  195. package/dist/src/notifications/store.d.ts +2 -0
  196. package/dist/src/notifications/store.js +7 -1
  197. package/dist/src/notifications/types.d.ts +1 -0
  198. package/dist/src/notifications/types.js +1 -0
  199. package/dist/src/providers/model-catalog-persistence.js +2 -2
  200. package/dist/src/providers/presentation.js +1 -0
  201. package/dist/src/providers/xopc-cloud-model-source.js +1 -1
  202. package/dist/src/storage/sqlite/clarification-wait-repository.js +1 -0
  203. package/dist/src/storage/sqlite/migrations/238_browser_record_replay.sql +45 -0
  204. package/dist/src/storage/sqlite/migrations/239_clarification_notifications.sql +26 -0
  205. package/dist/src/storage/sqlite/migrations/runner.d.ts +1 -1
  206. package/dist/src/storage/sqlite/migrations/runner.js +3 -3
  207. package/dist/src/storage/sqlite/schema.js +1 -1
  208. package/dist/src/tasks/home-query-service.d.ts +1 -1
  209. package/dist/src/tasks/home-query-service.js +6 -2
  210. package/dist/src/voice/dashscope/streaming-tts-stream.d.ts +2 -1
  211. package/dist/src/voice/dashscope/streaming-tts-stream.js +135 -79
  212. package/dist/src/voice/metadata/types.d.ts +1 -0
  213. package/dist/src/voice/platform-catalog.d.ts +4 -0
  214. package/dist/src/voice/platform-settings.js +1 -0
  215. package/dist/src/voice/realtime/agentEngine.js +78 -1
  216. package/dist/src/voice/realtime/speakable-segmenter.d.ts +2 -0
  217. package/dist/src/voice/realtime/speakable-segmenter.js +30 -0
  218. package/dist/src/voice/tts/providers/alibaba-speech.js +17 -1
  219. package/dist/src/voice/tts/providers/xopc-cloud-speech.js +3 -2
  220. package/dist/src/voice/tts/speak-core.d.ts +3 -0
  221. package/dist/src/voice/tts/speak-core.js +21 -1
  222. package/dist/src/voice/tts/speech-provider-types.d.ts +8 -0
  223. package/package.json +3 -3
  224. package/dist/gateway/static/root/assets/agent-defaults-settings-panel-gT9_j4tr.js +0 -1
  225. package/dist/gateway/static/root/assets/agent-plugin-dialog-CLXCJ6JN.js +0 -1
  226. package/dist/gateway/static/root/assets/agents-BAUfOMsq.js +0 -66
  227. package/dist/gateway/static/root/assets/app-management-settings-panel-CoIFxQKH.js +0 -1
  228. package/dist/gateway/static/root/assets/automations-workspace-BCGPjsdU.js +0 -6
  229. package/dist/gateway/static/root/assets/browser-automation-inputs-BUAIKIX0.js +0 -1
  230. package/dist/gateway/static/root/assets/browser-automations-page-CSwu1U1F.js +0 -3
  231. package/dist/gateway/static/root/assets/browser-settings-page-C1ReMNqX.js +0 -3
  232. package/dist/gateway/static/root/assets/capabilities-page-BdTSqloI.js +0 -2
  233. package/dist/gateway/static/root/assets/capabilities-settings-panel-BUblBMZC.js +0 -8
  234. package/dist/gateway/static/root/assets/channels-CXCf5qO6.js +0 -1
  235. package/dist/gateway/static/root/assets/computer-settings-page-B5SuSZ3G.js +0 -1
  236. package/dist/gateway/static/root/assets/connectors-page-DfnK1crf.js +0 -2
  237. package/dist/gateway/static/root/assets/device-pairing-wizard-s2ogSa4l.js +0 -1
  238. package/dist/gateway/static/root/assets/gateway-realtime-store-C_Th8kGg.js +0 -1
  239. package/dist/gateway/static/root/assets/gateway-settings-CZ73IpbP.js +0 -1
  240. package/dist/gateway/static/root/assets/home-page-BQWq4kYi.js +0 -2
  241. package/dist/gateway/static/root/assets/index-BQgqzhQj.js +0 -106
  242. package/dist/gateway/static/root/assets/index-COB4QIJp.css +0 -1
  243. package/dist/gateway/static/root/assets/logs-page-B-Y9oZLh.js +0 -2
  244. package/dist/gateway/static/root/assets/management-settings-B2lzsw6X.js +0 -1
  245. package/dist/gateway/static/root/assets/messages-Dy26npMy.js +0 -3
  246. package/dist/gateway/static/root/assets/models-hub-cache-B1C86nRp.js +0 -1
  247. package/dist/gateway/static/root/assets/note-detail-panel-DL__qP6U.js +0 -5
  248. package/dist/gateway/static/root/assets/notes-page-DTBbg9Hs.js +0 -8
  249. package/dist/gateway/static/root/assets/oauth-api-BwpzpRwC.js +0 -1
  250. package/dist/gateway/static/root/assets/personal-page-CzaTnHeD.js +0 -1
  251. package/dist/gateway/static/root/assets/remote-access-hub-CKxqncqW.js +0 -1
  252. package/dist/gateway/static/root/assets/scenes-page-Cv_5ozfX.js +0 -2
  253. package/dist/gateway/static/root/assets/sessions-page-DZcu6NLJ.js +0 -1
  254. package/dist/gateway/static/root/assets/settings-advanced-gate-DazqeDQ7.js +0 -1
  255. package/dist/gateway/static/root/assets/shares-settings-CsDATuCw.js +0 -1
  256. package/dist/gateway/static/root/assets/system-settings-panel-Bn3khdzb.js +0 -1
  257. package/dist/gateway/static/root/assets/user-model-page-CG6wqIKP.js +0 -2
  258. package/dist/src/browser/policy/approval-store.d.ts +0 -17
  259. package/dist/src/browser/policy/approval-store.js +0 -62
  260. package/dist/src/browser/providers/extension-ws-acquire.d.ts +0 -19
  261. package/dist/src/browser/providers/extension-ws-acquire.js +0 -78
  262. package/dist/src/browser/providers/extension.d.ts +0 -61
  263. package/dist/src/browser/providers/extension.js +0 -378
@@ -1,4 +1,343 @@
1
- import { n as endpointToolContentSchema, E as ENDPOINT_PROTOCOL_VERSION, g as canonicalJson, T as TAB_BINDING_PREFIX, h as captureContextMenuSelection, P as PENDING_CONTEXT_KEY, H as t, y as readProfile, z as registerBrowserEndpoint, q as getAccessProfile, a as RealtimeClient, j as createBrowserEndpointHello, p as gatewayFetch, R as REALTIME_PROTOCOL_VERSION } from './assets/auth-beGtbLPS.js';
1
+ import { y as readProfile, p as gatewayFetch, n as endpointToolContentSchema, E as ENDPOINT_PROTOCOL_VERSION, g as canonicalJson, T as TAB_BINDING_PREFIX, h as captureContextMenuSelection, P as PENDING_CONTEXT_KEY, H as t, z as registerBrowserEndpoint, q as getAccessProfile, a as RealtimeClient, j as createBrowserEndpointHello, R as REALTIME_PROTOCOL_VERSION } from './assets/page-context-BeworyNB.js';
2
+
3
+ const LOG_COLORS = {
4
+ debug: "color: #888",
5
+ info: "color: #3b82f6",
6
+ warn: "color: #f59e0b",
7
+ error: "color: #ef4444"
8
+ };
9
+ function createLogger(prefix) {
10
+ function log(level, message, data) {
11
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString().slice(11, 23);
12
+ const formatted = `[${timestamp}] [${prefix}] ${message}`;
13
+ if (data && Object.keys(data).length > 0) {
14
+ console[level](`%c${formatted}`, LOG_COLORS[level], data);
15
+ } else {
16
+ console[level](`%c${formatted}`, LOG_COLORS[level]);
17
+ }
18
+ }
19
+ return {
20
+ debug: (msg, data) => log("debug", msg, data),
21
+ info: (msg, data) => log("info", msg, data),
22
+ warn: (msg, data) => log("warn", msg, data),
23
+ error: (msg, data) => log("error", msg, data)
24
+ };
25
+ }
26
+
27
+ const log$2 = createLogger("SessionManager");
28
+ const XOPC_TAB_GROUP_TITLE = "xopc";
29
+ const busySessions = /* @__PURE__ */ new Set();
30
+ function holdAutomationSession(id) {
31
+ busySessions.add(id);
32
+ }
33
+ function releaseAutomationSession(id) {
34
+ busySessions.delete(id);
35
+ resetWindowIdleTimer(id);
36
+ }
37
+ const automationSessions = /* @__PURE__ */ new Map();
38
+ const WINDOW_IDLE_TIMEOUT = 12e4;
39
+ const DEFAULT_WORKSPACE = "default";
40
+ function resetWindowIdleTimer(workspace = DEFAULT_WORKSPACE) {
41
+ const session = automationSessions.get(workspace);
42
+ if (!session) return;
43
+ if (session.idleTimer) clearTimeout(session.idleTimer);
44
+ session.idleDeadlineAt = Date.now() + WINDOW_IDLE_TIMEOUT;
45
+ session.idleTimer = setTimeout(async () => {
46
+ const current = automationSessions.get(workspace);
47
+ if (!current) return;
48
+ if (busySessions.has(workspace)) {
49
+ resetWindowIdleTimer(workspace);
50
+ return;
51
+ }
52
+ try {
53
+ const tabs = await chrome.tabs.query({ groupId: current.groupId });
54
+ for (const t of tabs) {
55
+ if (t.id != null) await chrome.tabs.remove(t.id).catch(() => {
56
+ });
57
+ }
58
+ } catch {
59
+ }
60
+ automationSessions.delete(workspace);
61
+ log$2.info("Automation tab group cleared due to idle timeout", { workspace });
62
+ }, WINDOW_IDLE_TIMEOUT);
63
+ }
64
+ async function waitForTabLoad(tabId, timeoutMs = 3e4) {
65
+ return new Promise((resolve) => {
66
+ const timeout = setTimeout(resolve, timeoutMs);
67
+ const listener = (id, info) => {
68
+ if (id === tabId && info.status === "complete") {
69
+ clearTimeout(timeout);
70
+ chrome.tabs.onUpdated.removeListener(listener);
71
+ resolve();
72
+ }
73
+ };
74
+ chrome.tabs.onUpdated.addListener(listener);
75
+ chrome.tabs.get(tabId).then((tab) => {
76
+ if (tab.status === "complete") {
77
+ clearTimeout(timeout);
78
+ chrome.tabs.onUpdated.removeListener(listener);
79
+ resolve();
80
+ }
81
+ }).catch(() => {
82
+ clearTimeout(timeout);
83
+ chrome.tabs.onUpdated.removeListener(listener);
84
+ resolve();
85
+ });
86
+ });
87
+ }
88
+ async function resolveTargetWindowId() {
89
+ const w = await chrome.windows.getLastFocused({ windowTypes: ["normal"] });
90
+ if (w?.id != null) return w.id;
91
+ const all = await chrome.windows.getAll({ windowTypes: ["normal"] });
92
+ const first = all.find((x) => x.id != null);
93
+ if (first?.id != null) return first.id;
94
+ throw new Error("No normal browser window — open a Chrome window, then run automation again.");
95
+ }
96
+ async function getOrCreateAutomationWindow(workspace = DEFAULT_WORKSPACE) {
97
+ const existing = automationSessions.get(workspace);
98
+ if (existing) {
99
+ try {
100
+ await chrome.tabGroups.get(existing.groupId);
101
+ const tabsInGroup = await chrome.tabs.query({ groupId: existing.groupId });
102
+ if (tabsInGroup.length === 0) throw new Error("empty group");
103
+ existing.tabIds = tabsInGroup.map((t) => t.id).filter(Boolean);
104
+ const active = tabsInGroup.find((t) => t.active);
105
+ existing.activeTabId = active?.id ?? tabsInGroup[0]?.id ?? null;
106
+ resetWindowIdleTimer(workspace);
107
+ return existing;
108
+ } catch {
109
+ automationSessions.delete(workspace);
110
+ }
111
+ }
112
+ const windowId = await resolveTargetWindowId();
113
+ const tab = await chrome.tabs.create({
114
+ windowId,
115
+ url: "about:blank",
116
+ active: false
117
+ });
118
+ if (tab.id == null) throw new Error("Failed to create automation tab");
119
+ const groupId = await chrome.tabs.group({
120
+ tabIds: [tab.id],
121
+ createProperties: { windowId }
122
+ });
123
+ await chrome.tabGroups.update(groupId, {
124
+ title: XOPC_TAB_GROUP_TITLE,
125
+ collapsed: false,
126
+ color: "blue"
127
+ });
128
+ const session = {
129
+ windowId,
130
+ groupId,
131
+ tabIds: [tab.id],
132
+ activeTabId: tab.id,
133
+ idleTimer: null,
134
+ idleDeadlineAt: 0,
135
+ createdAt: Date.now()
136
+ };
137
+ automationSessions.set(workspace, session);
138
+ resetWindowIdleTimer(workspace);
139
+ log$2.info("Created automation tab group", { workspace, windowId, groupId, tabId: tab.id });
140
+ return session;
141
+ }
142
+ async function getActiveTabId(workspace = DEFAULT_WORKSPACE) {
143
+ const session = await getOrCreateAutomationWindow(workspace);
144
+ if (session.activeTabId != null) {
145
+ const ok = await chrome.tabs.get(session.activeTabId).catch(() => null);
146
+ if (ok) return session.activeTabId;
147
+ }
148
+ const tabs = await chrome.tabs.query({ groupId: session.groupId });
149
+ const tabId = tabs.find((t) => t.active)?.id ?? tabs[0]?.id;
150
+ if (tabId == null) throw new Error("No tab in xopc automation group");
151
+ session.activeTabId = tabId;
152
+ return tabId;
153
+ }
154
+ async function addTabToAutomationGroup(url, workspace = DEFAULT_WORKSPACE) {
155
+ const session = await getOrCreateAutomationWindow(workspace);
156
+ const tab = await chrome.tabs.create({
157
+ windowId: session.windowId,
158
+ url,
159
+ active: false
160
+ });
161
+ if (tab.id == null) throw new Error("Failed to create tab");
162
+ const inGroup = await chrome.tabs.query({ groupId: session.groupId });
163
+ const mergedIds = [...inGroup.map((t) => t.id).filter(Boolean), tab.id];
164
+ await chrome.tabs.group({ tabIds: mergedIds, groupId: session.groupId });
165
+ session.activeTabId = tab.id;
166
+ session.tabIds = mergedIds;
167
+ resetWindowIdleTimer(workspace);
168
+ return { tabId: tab.id, session };
169
+ }
170
+ async function closeSession(workspace = DEFAULT_WORKSPACE) {
171
+ const session = automationSessions.get(workspace);
172
+ if (!session) return;
173
+ if (session.idleTimer) clearTimeout(session.idleTimer);
174
+ const tabs = await chrome.tabs.query({ groupId: session.groupId }).catch(() => []);
175
+ for (const t of tabs) {
176
+ if (t.id != null) await chrome.tabs.remove(t.id).catch(() => {
177
+ });
178
+ }
179
+ automationSessions.delete(workspace);
180
+ log$2.info("Closed automation session", { workspace });
181
+ }
182
+
183
+ async function browserDom(input) {
184
+ const root = globalThis;
185
+ const doc = root.document;
186
+ const normalize = (value) => String(value || "").trim().replace(/\s+/g, " ").slice(0, 240);
187
+ const sensitive = (element) => element.type === "password" || element.type === "file" || /cc-|one-time-code|transaction-|password|token|secret|otp|cvv|cvc|card.?number|验证码|密码|银行卡|卡号/i.test([
188
+ element.autocomplete,
189
+ element.name,
190
+ element.id,
191
+ element.getAttribute("aria-label"),
192
+ name(element)
193
+ ].join(" "));
194
+ const role = (element) => element.getAttribute("role") || {
195
+ A: "link",
196
+ BUTTON: "button",
197
+ SUMMARY: "button",
198
+ SELECT: "combobox",
199
+ TEXTAREA: "textbox",
200
+ FORM: "form",
201
+ FIELDSET: "group",
202
+ SECTION: "region"
203
+ }[element.tagName] || (element.tagName === "INPUT" ? element.type === "checkbox" ? "checkbox" : element.type === "radio" ? "radio" : ["submit", "button", "reset"].includes(element.type) ? "button" : "textbox" : "text");
204
+ const name = (element) => normalize(
205
+ (element.getAttribute("aria-labelledby") || "").split(/\s+/).map((id) => doc.getElementById(id)?.textContent || "").join(" ") || element.getAttribute("aria-label") || element.labels?.[0]?.textContent || (element.tagName === "FIELDSET" ? element.querySelector("legend")?.textContent : "") || element.getAttribute("alt") || element.getAttribute("placeholder") || (["submit", "button", "reset"].includes(element.type) ? element.value : "") || element.innerText || element.getAttribute("title") || ""
206
+ );
207
+ const describe = (element) => {
208
+ const container = element.parentElement?.closest('form,fieldset,section,[role="region"],[role="group"],[role="dialog"]');
209
+ return {
210
+ role: role(element),
211
+ name: name(element),
212
+ ...element.getAttribute("data-testid") ? { testId: element.getAttribute("data-testid") } : {},
213
+ ...container && name(container) ? { scope: { role: role(container), name: name(container) } } : {}
214
+ };
215
+ };
216
+ const selector = 'a[href],button,input:not([type="hidden"]),textarea,select,summary,[role],[data-testid],label,p,span,h1,h2,h3,td,output';
217
+ if (input.operation === "resolve") {
218
+ const target = input.target;
219
+ const matches = Array.from(doc.querySelectorAll(selector)).filter((element2) => {
220
+ const rect = element2.getBoundingClientRect();
221
+ const style = root.getComputedStyle(element2);
222
+ if (!rect.width || !rect.height || style.display === "none" || style.visibility === "hidden") return false;
223
+ const descriptor = describe(element2);
224
+ return descriptor.role === target.role && (!target.name || descriptor.name === target.name) && (!target.nameIncludes || descriptor.name?.includes(target.nameIncludes)) && (!target.testId || descriptor.testId === target.testId) && (!target.scope || descriptor.scope?.role === target.scope.role && descriptor.scope?.name === target.scope.name);
225
+ });
226
+ if (matches.length !== 1) return { matches: matches.length };
227
+ const element = matches[0];
228
+ const ref = `xopc-e${++root.__xopcRefCounter || (root.__xopcRefCounter = 1)}`;
229
+ element.setAttribute("data-xopc-ref", ref);
230
+ return { matches: 1, node: {
231
+ ref,
232
+ role: role(element),
233
+ name: name(element),
234
+ value: !sensitive(element) && "value" in element ? String(element.value).slice(0, 4096) : void 0,
235
+ description: !sensitive(element) ? normalize(element.textContent) : void 0,
236
+ href: element.tagName === "A" ? element.href : void 0,
237
+ states: [sensitive(element) ? "sensitive" : "", element.disabled ? "disabled" : "", element.checked ? "checked" : "", ["BUTTON", "INPUT"].includes(element.tagName) && element.type === "submit" ? "submit" : ""].filter(Boolean)
238
+ } };
239
+ }
240
+ const prior = root.__xopcRecorder;
241
+ if (input.operation === "record" && prior?.recordingId === input.recordingId && prior?.generation === input.generation) return { recording: true };
242
+ if (prior) {
243
+ await prior.stop();
244
+ delete root.__xopcRecorder;
245
+ }
246
+ if (input.operation === "stop") return { stopped: true };
247
+ let sourceSeq = 0;
248
+ const pending = /* @__PURE__ */ new Set();
249
+ const dirty = /* @__PURE__ */ new Set();
250
+ const lastValues = /* @__PURE__ */ new WeakMap();
251
+ const listeners = [];
252
+ const cleanUrl = () => {
253
+ const url = new URL(root.location.href);
254
+ url.username = "";
255
+ url.password = "";
256
+ url.hash = "";
257
+ for (const key of [...url.searchParams.keys()]) if (/token|secret|password|key|code/i.test(key)) url.searchParams.delete(key);
258
+ return url.toString();
259
+ };
260
+ const documentId = `${root.performance.timeOrigin}:${input.generation}`;
261
+ let failed = false;
262
+ let ended = false;
263
+ let enterTarget;
264
+ let enterAt = 0;
265
+ const emit = (event) => {
266
+ const task = root.chrome.runtime.sendMessage({
267
+ type: "browser/recording-event",
268
+ recordingId: input.recordingId,
269
+ generation: input.generation,
270
+ event: { ...event, id: root.crypto.randomUUID(), sourceSeq: ++sourceSeq, documentId, url: cleanUrl() }
271
+ }).then((ack) => {
272
+ if (!ack?.ok) failed = true;
273
+ }).catch(() => {
274
+ failed = true;
275
+ });
276
+ pending.add(task);
277
+ void task.finally(() => pending.delete(task));
278
+ };
279
+ const flush = (element) => {
280
+ if (!dirty.delete(element) || sensitive(element)) return;
281
+ const value = String(element.value || "").slice(0, 4096);
282
+ if (lastValues.get(element) === value) return;
283
+ lastValues.set(element, value);
284
+ emit({ action: element.tagName === "SELECT" ? "select" : "fill", target: describe(element), value });
285
+ };
286
+ const listen = (type, handler) => {
287
+ listeners.push([type, handler]);
288
+ (type === "pagehide" ? root : doc).addEventListener(type, handler, true);
289
+ };
290
+ listen("input", (event) => {
291
+ if (event.isTrusted && !sensitive(event.target) && !["checkbox", "radio"].includes(event.target.type) && "value" in event.target) dirty.add(event.target);
292
+ });
293
+ listen("dragstart", (event) => {
294
+ if (event.isTrusted) emit({ action: "unsupported", value: "Drag actions are not supported by the recorder." });
295
+ });
296
+ listen("change", (event) => {
297
+ if (!event.isTrusted || sensitive(event.target)) return;
298
+ if (["checkbox", "radio"].includes(event.target.type)) emit({ action: "check", target: describe(event.target), checked: !!event.target.checked });
299
+ else {
300
+ dirty.add(event.target);
301
+ flush(event.target);
302
+ }
303
+ });
304
+ listen("focusout", (event) => flush(event.target));
305
+ listen("click", (event) => {
306
+ if (!event.isTrusted) return;
307
+ for (const element2 of dirty) flush(element2);
308
+ const element = event.target.closest('a[href],button,summary,[role="button"],input[type="submit"],input[type="button"]');
309
+ if (element && event.detail === 0 && Date.now() - enterAt < 500 && (element === enterTarget || element.form && element.form === enterTarget?.form)) return;
310
+ if (element && !sensitive(element)) emit({ action: "click", target: describe(element) });
311
+ else if (event.target.closest('canvas,[contenteditable="true"],input[type="file"]')) emit({ action: "unsupported", value: "Canvas, rich text and file actions require an assistant to complete the workflow." });
312
+ });
313
+ listen("pagehide", () => {
314
+ for (const element of dirty) flush(element);
315
+ emit({ action: "checkpoint" });
316
+ ended = true;
317
+ for (const [type, handler] of listeners) (type === "pagehide" ? root : doc).removeEventListener(type, handler, true);
318
+ });
319
+ listen("keydown", (event) => {
320
+ if (!event.isTrusted || event.isComposing || sensitive(event.target) || !["Enter", "Escape"].includes(event.key)) return;
321
+ flush(event.target);
322
+ if (event.key === "Enter") {
323
+ enterTarget = event.target;
324
+ enterAt = Date.now();
325
+ }
326
+ emit({ action: "press", target: describe(event.target), key: event.key });
327
+ });
328
+ root.__xopcRecorder = { recordingId: input.recordingId, generation: input.generation, stop: async () => {
329
+ for (const [type, handler] of listeners) (type === "pagehide" ? root : doc).removeEventListener(type, handler, true);
330
+ for (const element of dirty) flush(element);
331
+ if (!ended) {
332
+ emit({ action: "checkpoint" });
333
+ ended = true;
334
+ }
335
+ await Promise.all([...pending]);
336
+ if (failed) throw new Error("Recording events could not be saved.");
337
+ } };
338
+ emit({ action: "navigate" });
339
+ return { recording: true, documentId };
340
+ }
2
341
 
3
342
  const BROWSER_EXTENSION_PROTOCOL_VERSION = 5;
4
343
  const BROWSER_CONTROL_ENDPOINT_TOOL_NAME = "browser.control";
@@ -44,6 +383,285 @@ const BROWSER_CONTROL_ENDPOINT_DESCRIPTOR = {
44
383
  idempotent: false,
45
384
  resultKinds: ["json"]
46
385
  };
386
+ const BROWSER_RECORDING_ENDPOINT_INPUT_SCHEMA = {
387
+ type: "object",
388
+ additionalProperties: false,
389
+ required: ["operation"],
390
+ properties: { operation: { type: "string", enum: ["start", "pause", "resume", "finish", "status"] } }
391
+ };
392
+ const BROWSER_RECORDING_ENDPOINT_DESCRIPTOR = {
393
+ ...BROWSER_CONTROL_ENDPOINT_DESCRIPTOR,
394
+ name: "browser.recording",
395
+ title: "Browser workflow recording",
396
+ description: "Record a workflow in the active browser tab and save it as an automation.",
397
+ inputSchema: BROWSER_RECORDING_ENDPOINT_INPUT_SCHEMA,
398
+ maxConcurrency: 1
399
+ };
400
+
401
+ function open() {
402
+ return new Promise((resolve, reject) => {
403
+ const request2 = indexedDB.open("xopc-browser-recordings", 1);
404
+ request2.onupgradeneeded = () => {
405
+ request2.result.createObjectStore("sessions", { keyPath: "id" });
406
+ request2.result.createObjectStore("events", { keyPath: ["recordingId", "seq"] });
407
+ };
408
+ request2.onsuccess = () => resolve(request2.result);
409
+ request2.onerror = () => reject(request2.error);
410
+ });
411
+ }
412
+ function request(value) {
413
+ return new Promise((resolve, reject) => {
414
+ value.onsuccess = () => resolve(value.result);
415
+ value.onerror = () => reject(value.error);
416
+ });
417
+ }
418
+ async function transaction(mode, work) {
419
+ const db = await open();
420
+ const tx = db.transaction(["sessions", "events"], mode);
421
+ const done = new Promise((resolve, reject) => {
422
+ tx.oncomplete = () => resolve();
423
+ tx.onerror = () => reject(tx.error);
424
+ tx.onabort = () => reject(tx.error);
425
+ });
426
+ try {
427
+ const result = await work(tx);
428
+ await done;
429
+ return result;
430
+ } catch (error) {
431
+ try {
432
+ tx.abort();
433
+ } catch {
434
+ }
435
+ await done.catch(() => {
436
+ });
437
+ throw error;
438
+ } finally {
439
+ db.close();
440
+ }
441
+ }
442
+ function recordings() {
443
+ return transaction("readonly", async (tx) => {
444
+ const rows = await request(tx.objectStore("sessions").getAll());
445
+ return rows.sort((a, b) => a.createdAtMs - b.createdAtMs);
446
+ });
447
+ }
448
+ function saveRecording(session) {
449
+ return transaction("readwrite", async (tx) => {
450
+ await request(tx.objectStore("sessions").put(session));
451
+ });
452
+ }
453
+ function recordingEvents(id, after = 0) {
454
+ return transaction("readonly", async (tx) => {
455
+ const rows = await request(tx.objectStore("events").getAll(IDBKeyRange.bound([id, after + 1], [id, Number.MAX_SAFE_INTEGER]), 100));
456
+ return rows.map(({ recordingId: _recordingId, ...event }) => event);
457
+ });
458
+ }
459
+ function appendRecordingEvent(id, generation, tabId, event) {
460
+ return transaction("readwrite", async (tx) => {
461
+ const sessions = tx.objectStore("sessions");
462
+ const session = await request(sessions.get(id));
463
+ if (!session || session.state !== "recording" || session.generation !== generation || session.tabId !== tabId) throw new Error("Recording is no longer active.");
464
+ if (session.count >= 1e4) throw new Error("Recording event limit reached. Finish this recording.");
465
+ const bytes = session.bytes + new TextEncoder().encode(JSON.stringify(event)).length;
466
+ if (bytes > 20 * 1024 * 1024) throw new Error("Recording storage limit reached.");
467
+ const seq = session.count + 1;
468
+ await request(tx.objectStore("events").add({ ...event, recordingId: id, seq }));
469
+ await request(sessions.put({ ...session, count: seq, bytes }));
470
+ });
471
+ }
472
+ function deleteRecordingEvents(id, through) {
473
+ return transaction("readwrite", async (tx) => {
474
+ await request(tx.objectStore("events").delete(IDBKeyRange.bound([id, 1], [id, through])));
475
+ });
476
+ }
477
+ function patchRecording(id, patch) {
478
+ return transaction("readwrite", async (tx) => {
479
+ const store = tx.objectStore("sessions");
480
+ const current = await request(store.get(id));
481
+ if (!current) throw new Error("Recording not found.");
482
+ await request(store.put({ ...current, ...patch }));
483
+ });
484
+ }
485
+
486
+ let uploadTask;
487
+ let resyncRequested = false;
488
+ let controlTask;
489
+ let claim;
490
+ function setRecordingClaim(value) {
491
+ claim = value;
492
+ void syncRecordings();
493
+ }
494
+ async function inject(tabId, input) {
495
+ const result = await chrome.scripting.executeScript({ target: { tabId }, func: browserDom, args: [input] });
496
+ return result[0]?.result;
497
+ }
498
+ async function recordingCommand(operation, tabId) {
499
+ if (operation === "status") return recordings();
500
+ if (controlTask) throw new Error("A recording action is already in progress.");
501
+ controlTask = control(operation, tabId);
502
+ try {
503
+ return await controlTask;
504
+ } finally {
505
+ controlTask = void 0;
506
+ }
507
+ }
508
+ async function control(operation, tabId) {
509
+ const all = await recordings();
510
+ const active = all.find((item) => item.state === "recording" || item.state === "paused");
511
+ if (operation === "start") {
512
+ if (active) return active;
513
+ const profile = await readProfile();
514
+ if (!profile) throw new Error("Connect to your Gateway first.");
515
+ if (tabId !== void 0 && (!Number.isInteger(tabId) || tabId <= 0)) throw new Error("Invalid recording tab.");
516
+ const tab = tabId !== void 0 ? await chrome.tabs.get(tabId) : (await chrome.tabs.query({ active: true, lastFocusedWindow: true }))[0];
517
+ if (!tab?.id || !tab.url?.match(/^https?:\/\//)) throw new Error("RECORDING_PAGE_UNAVAILABLE");
518
+ if ([...automationSessions.values()].some((session3) => session3.tabIds.includes(tab.id))) throw new Error("This page belongs to a running browser task. Select your workflow page to record.");
519
+ const session2 = {
520
+ id: crypto.randomUUID(),
521
+ state: "recording",
522
+ tabId: tab.id,
523
+ gatewayId: profile.gatewayId,
524
+ deviceId: profile.deviceId,
525
+ generation: 1,
526
+ count: 0,
527
+ ack: 0,
528
+ bytes: 0,
529
+ createdAtMs: Date.now()
530
+ };
531
+ await saveRecording(session2);
532
+ try {
533
+ await inject(tab.id, { operation: "record", recordingId: session2.id, generation: session2.generation });
534
+ } catch (error) {
535
+ await patchRecording(session2.id, { state: "interrupted", error: String(error) });
536
+ throw error;
537
+ }
538
+ await chrome.action.setBadgeText({ text: "REC" });
539
+ return session2;
540
+ }
541
+ if (!active) throw new Error("There is no active recording.");
542
+ if (operation === "resume") {
543
+ if (active.state !== "paused") return active;
544
+ const session2 = { ...active, state: "recording", generation: active.generation + 1 };
545
+ await patchRecording(session2.id, { state: session2.state, generation: session2.generation });
546
+ try {
547
+ await inject(session2.tabId, { operation: "record", recordingId: session2.id, generation: session2.generation });
548
+ } catch (error) {
549
+ await patchRecording(session2.id, { state: "interrupted", error: String(error) });
550
+ throw error;
551
+ }
552
+ await chrome.action.setBadgeText({ text: "REC" });
553
+ return session2;
554
+ }
555
+ if (operation !== "pause" && operation !== "finish") throw new Error("Unknown recording operation.");
556
+ try {
557
+ await inject(active.tabId, { operation: "stop" });
558
+ } catch (error) {
559
+ const current2 = (await recordings()).find((item) => item.id === active.id);
560
+ await patchRecording(current2.id, { state: "interrupted", error: `Recording may be incomplete: ${String(error)}` });
561
+ throw error;
562
+ }
563
+ const current = (await recordings()).find((item) => item.id === active.id);
564
+ const session = { ...current, state: operation === "pause" ? "paused" : "stopped" };
565
+ await patchRecording(session.id, { state: session.state });
566
+ await chrome.action.setBadgeText({ text: operation === "pause" ? "Ⅱ" : "" });
567
+ void syncRecordings();
568
+ return session;
569
+ }
570
+ async function acceptRecordingEvent(message, sender) {
571
+ if (sender.id !== chrome.runtime.id || !sender.tab?.id || sender.frameId !== 0) throw new Error("Invalid recording source.");
572
+ const event = message.event;
573
+ if (!event || JSON.stringify(event).length > 16384 || !["navigate", "click", "fill", "select", "check", "press", "checkpoint", "unsupported"].includes(event.action)) throw new Error("Invalid recording event.");
574
+ try {
575
+ await appendRecordingEvent(message.recordingId, message.generation, sender.tab.id, event);
576
+ } catch (error) {
577
+ if (/limit reached/.test(String(error))) {
578
+ await patchRecording(message.recordingId, { state: "interrupted", error: String(error) });
579
+ void inject(sender.tab.id, { operation: "stop" }).catch(() => void 0);
580
+ await chrome.action.setBadgeText({ text: "" });
581
+ }
582
+ throw error;
583
+ }
584
+ void syncRecordings();
585
+ }
586
+ function syncRecordings() {
587
+ if (uploadTask) {
588
+ resyncRequested = true;
589
+ return uploadTask;
590
+ }
591
+ uploadTask = upload().catch(() => void 0).finally(() => {
592
+ uploadTask = void 0;
593
+ if (resyncRequested) {
594
+ resyncRequested = false;
595
+ void syncRecordings();
596
+ }
597
+ });
598
+ return uploadTask;
599
+ }
600
+ async function upload() {
601
+ if (!claim) return;
602
+ const profile = await readProfile();
603
+ if (!profile) return;
604
+ for (const session of await recordings()) {
605
+ if (session.gatewayId !== profile.gatewayId || session.deviceId !== profile.deviceId || session.automationId) continue;
606
+ try {
607
+ const post = async (operation, body) => {
608
+ const response = await gatewayFetch(`/api/browser/recordings/${session.id}/${operation}`, {
609
+ method: "POST",
610
+ headers: { "Content-Type": "application/json", "x-endpoint-id": claim.endpointId, "x-endpoint-claim": claim.token },
611
+ body: JSON.stringify(body)
612
+ }, profile);
613
+ const data = await response.json();
614
+ if (!response.ok) throw new Error(data.error || "Recording could not be synchronized.");
615
+ return data;
616
+ };
617
+ let ack = session.ack;
618
+ let events = await recordingEvents(session.id, ack);
619
+ while (events.length) {
620
+ while (JSON.stringify({ events }).length > 24e4) events.pop();
621
+ const data = await post("events", { events });
622
+ if (data.ackThrough <= ack) throw new Error("Recording synchronization did not advance.");
623
+ ack = data.ackThrough;
624
+ const current2 = (await recordings()).find((item) => item.id === session.id);
625
+ await patchRecording(current2.id, { ack, error: void 0 });
626
+ events = await recordingEvents(session.id, ack);
627
+ }
628
+ const current = (await recordings()).find((item) => item.id === session.id);
629
+ if (current.state === "stopped" && current.count === ack) {
630
+ const data = await post("finish", { finalSeq: current.count });
631
+ await patchRecording(current.id, { ack, automationId: data.automation.id, error: void 0 });
632
+ await deleteRecordingEvents(session.id, current.count);
633
+ }
634
+ } catch (error) {
635
+ const current = (await recordings()).find((item) => item.id === session.id);
636
+ await patchRecording(current.id, { error: error instanceof Error ? error.message : String(error) });
637
+ }
638
+ }
639
+ }
640
+ chrome.tabs.onUpdated.addListener((tabId, info) => {
641
+ if (info.status !== "complete") return;
642
+ void recordings().then(async (all) => {
643
+ const session = all.find((item) => item.tabId === tabId && item.state === "recording");
644
+ if (!session) return;
645
+ try {
646
+ await patchRecording(session.id, { generation: session.generation + 1 });
647
+ await inject(tabId, { operation: "record", recordingId: session.id, generation: session.generation + 1 });
648
+ } catch (error) {
649
+ await patchRecording(session.id, { state: "interrupted", error: String(error) });
650
+ }
651
+ });
652
+ });
653
+ chrome.tabs.onRemoved.addListener((tabId) => {
654
+ void recordings().then(async (all) => {
655
+ for (const session of all.filter((item) => item.tabId === tabId && ["recording", "paused"].includes(item.state))) {
656
+ await patchRecording(session.id, { state: "interrupted", error: "The recorded tab was closed. The partial recording is saved locally." });
657
+ }
658
+ });
659
+ });
660
+ chrome.runtime.onStartup.addListener(() => {
661
+ void recordings().then(async (all) => {
662
+ for (const session of all.filter((item) => item.state === "recording")) await patchRecording(session.id, { state: "interrupted", error: "Browser restarted. The recording was stopped." });
663
+ });
664
+ });
47
665
 
48
666
  /**
49
667
  * Utilities for hex, bytes, CSPRNG.
@@ -594,31 +1212,7 @@ class EndpointToolHostController {
594
1212
  }
595
1213
  }
596
1214
 
597
- const LOG_COLORS = {
598
- debug: "color: #888",
599
- info: "color: #3b82f6",
600
- warn: "color: #f59e0b",
601
- error: "color: #ef4444"
602
- };
603
- function createLogger(prefix) {
604
- function log(level, message, data) {
605
- const timestamp = (/* @__PURE__ */ new Date()).toISOString().slice(11, 23);
606
- const formatted = `[${timestamp}] [${prefix}] ${message}`;
607
- if (data && Object.keys(data).length > 0) {
608
- console[level](`%c${formatted}`, LOG_COLORS[level], data);
609
- } else {
610
- console[level](`%c${formatted}`, LOG_COLORS[level]);
611
- }
612
- }
613
- return {
614
- debug: (msg, data) => log("debug", msg, data),
615
- info: (msg, data) => log("info", msg, data),
616
- warn: (msg, data) => log("warn", msg, data),
617
- error: (msg, data) => log("error", msg, data)
618
- };
619
- }
620
-
621
- const log$2 = createLogger("CDP");
1215
+ const log$1 = createLogger("CDP");
622
1216
  const attached = /* @__PURE__ */ new Set();
623
1217
  function isDebuggableUrl(url) {
624
1218
  if (!url) return true;
@@ -660,7 +1254,7 @@ async function ensureAttached(tabId) {
660
1254
  attached.add(tabId);
661
1255
  await chrome.debugger.sendCommand({ tabId }, "Runtime.enable", {});
662
1256
  await chrome.debugger.sendCommand({ tabId }, "Page.enable", {});
663
- log$2.debug("Attached to tab", { tabId, attempt });
1257
+ log$1.debug("Attached to tab", { tabId, attempt });
664
1258
  return;
665
1259
  } catch (e) {
666
1260
  lastError = e instanceof Error ? e.message : String(e);
@@ -701,151 +1295,8 @@ chrome.debugger.onDetach.addListener((source) => {
701
1295
  if (source.tabId) attached.delete(source.tabId);
702
1296
  });
703
1297
 
704
- const log$1 = createLogger("SessionManager");
705
- const XOPC_TAB_GROUP_TITLE = "xopc";
706
- const automationSessions = /* @__PURE__ */ new Map();
707
- const WINDOW_IDLE_TIMEOUT = 12e4;
708
- const DEFAULT_WORKSPACE = "default";
709
- function resetWindowIdleTimer(workspace = DEFAULT_WORKSPACE) {
710
- const session = automationSessions.get(workspace);
711
- if (!session) return;
712
- if (session.idleTimer) clearTimeout(session.idleTimer);
713
- session.idleDeadlineAt = Date.now() + WINDOW_IDLE_TIMEOUT;
714
- session.idleTimer = setTimeout(async () => {
715
- const current = automationSessions.get(workspace);
716
- if (!current) return;
717
- try {
718
- const tabs = await chrome.tabs.query({ groupId: current.groupId });
719
- for (const t of tabs) {
720
- if (t.id != null) await chrome.tabs.remove(t.id).catch(() => {
721
- });
722
- }
723
- } catch {
724
- }
725
- automationSessions.delete(workspace);
726
- log$1.info("Automation tab group cleared due to idle timeout", { workspace });
727
- }, WINDOW_IDLE_TIMEOUT);
728
- }
729
- async function waitForTabLoad(tabId, timeoutMs = 3e4) {
730
- return new Promise((resolve) => {
731
- const timeout = setTimeout(resolve, timeoutMs);
732
- const listener = (id, info) => {
733
- if (id === tabId && info.status === "complete") {
734
- clearTimeout(timeout);
735
- chrome.tabs.onUpdated.removeListener(listener);
736
- resolve();
737
- }
738
- };
739
- chrome.tabs.onUpdated.addListener(listener);
740
- chrome.tabs.get(tabId).then((tab) => {
741
- if (tab.status === "complete") {
742
- clearTimeout(timeout);
743
- chrome.tabs.onUpdated.removeListener(listener);
744
- resolve();
745
- }
746
- }).catch(() => {
747
- clearTimeout(timeout);
748
- chrome.tabs.onUpdated.removeListener(listener);
749
- resolve();
750
- });
751
- });
752
- }
753
- async function resolveTargetWindowId() {
754
- const w = await chrome.windows.getLastFocused({ windowTypes: ["normal"] });
755
- if (w?.id != null) return w.id;
756
- const all = await chrome.windows.getAll({ windowTypes: ["normal"] });
757
- const first = all.find((x) => x.id != null);
758
- if (first?.id != null) return first.id;
759
- throw new Error("No normal browser window — open a Chrome window, then run automation again.");
760
- }
761
- async function getOrCreateAutomationWindow(workspace = DEFAULT_WORKSPACE) {
762
- const existing = automationSessions.get(workspace);
763
- if (existing) {
764
- try {
765
- await chrome.tabGroups.get(existing.groupId);
766
- const tabsInGroup = await chrome.tabs.query({ groupId: existing.groupId });
767
- if (tabsInGroup.length === 0) throw new Error("empty group");
768
- existing.tabIds = tabsInGroup.map((t) => t.id).filter(Boolean);
769
- const active = tabsInGroup.find((t) => t.active);
770
- existing.activeTabId = active?.id ?? tabsInGroup[0]?.id ?? null;
771
- resetWindowIdleTimer(workspace);
772
- return existing;
773
- } catch {
774
- automationSessions.delete(workspace);
775
- }
776
- }
777
- const windowId = await resolveTargetWindowId();
778
- const tab = await chrome.tabs.create({
779
- windowId,
780
- url: "about:blank",
781
- active: false
782
- });
783
- if (tab.id == null) throw new Error("Failed to create automation tab");
784
- const groupId = await chrome.tabs.group({
785
- tabIds: [tab.id],
786
- createProperties: { windowId }
787
- });
788
- await chrome.tabGroups.update(groupId, {
789
- title: XOPC_TAB_GROUP_TITLE,
790
- collapsed: false,
791
- color: "blue"
792
- });
793
- const session = {
794
- windowId,
795
- groupId,
796
- tabIds: [tab.id],
797
- activeTabId: tab.id,
798
- idleTimer: null,
799
- idleDeadlineAt: 0,
800
- createdAt: Date.now()
801
- };
802
- automationSessions.set(workspace, session);
803
- resetWindowIdleTimer(workspace);
804
- log$1.info("Created automation tab group", { workspace, windowId, groupId, tabId: tab.id });
805
- return session;
806
- }
807
- async function getActiveTabId(workspace = DEFAULT_WORKSPACE) {
808
- const session = await getOrCreateAutomationWindow(workspace);
809
- if (session.activeTabId != null) {
810
- const ok = await chrome.tabs.get(session.activeTabId).catch(() => null);
811
- if (ok) return session.activeTabId;
812
- }
813
- const tabs = await chrome.tabs.query({ groupId: session.groupId });
814
- const tabId = tabs.find((t) => t.active)?.id ?? tabs[0]?.id;
815
- if (tabId == null) throw new Error("No tab in xopc automation group");
816
- session.activeTabId = tabId;
817
- return tabId;
818
- }
819
- async function addTabToAutomationGroup(url, workspace = DEFAULT_WORKSPACE) {
820
- const session = await getOrCreateAutomationWindow(workspace);
821
- const tab = await chrome.tabs.create({
822
- windowId: session.windowId,
823
- url,
824
- active: false
825
- });
826
- if (tab.id == null) throw new Error("Failed to create tab");
827
- const inGroup = await chrome.tabs.query({ groupId: session.groupId });
828
- const mergedIds = [...inGroup.map((t) => t.id).filter(Boolean), tab.id];
829
- await chrome.tabs.group({ tabIds: mergedIds, groupId: session.groupId });
830
- session.activeTabId = tab.id;
831
- session.tabIds = mergedIds;
832
- resetWindowIdleTimer(workspace);
833
- return { tabId: tab.id, session };
834
- }
835
- async function closeSession(workspace = DEFAULT_WORKSPACE) {
836
- const session = automationSessions.get(workspace);
837
- if (!session) return;
838
- if (session.idleTimer) clearTimeout(session.idleTimer);
839
- const tabs = await chrome.tabs.query({ groupId: session.groupId }).catch(() => []);
840
- for (const t of tabs) {
841
- if (t.id != null) await chrome.tabs.remove(t.id).catch(() => {
842
- });
843
- }
844
- automationSessions.delete(workspace);
845
- log$1.info("Closed automation session", { workspace });
846
- }
847
-
848
1298
  const observations = /* @__PURE__ */ new Map();
1299
+ const activeCommands = /* @__PURE__ */ new Set();
849
1300
  async function markerDigest(tabId) {
850
1301
  const marker = await evaluate(tabId, `({ url: location.href, timeOrigin: performance.timeOrigin })`);
851
1302
  const url = new URL(marker.url);
@@ -867,9 +1318,9 @@ async function resolveAttachedTarget(input) {
867
1318
  if (input.action === "navigate" || input.action === "tabs" || input.action === "sequence") {
868
1319
  return fail("INVALID_INPUT", `${input.action} is unavailable for an attached tab.`);
869
1320
  }
870
- const needsAct = !["observe", "wait", "close"].includes(input.action);
1321
+ const needsAct = !["observe", "resolve", "wait", "close"].includes(input.action);
871
1322
  if (needsAct && binding.mode !== "act") {
872
- return fail("APPROVAL_REQUIRED", "This tab is attached in read-only mode. Enable Control tab in the side panel.");
1323
+ return fail("INVALID_INPUT", "This tab is attached in read-only mode. Enable Control tab in the side panel.");
873
1324
  }
874
1325
  const tabId = Number(binding.tabId);
875
1326
  const tab = Number.isInteger(tabId) ? await chrome.tabs.get(tabId).catch(() => null) : null;
@@ -890,10 +1341,15 @@ async function resolveAttachedTarget(input) {
890
1341
  }
891
1342
  return { binding, tabId, stateKey: `attached:${binding.id}` };
892
1343
  }
893
- async function executeBrowserCommand(command) {
1344
+ async function executeBrowserCommand(command, signal) {
894
1345
  const startedAt = Date.now();
1346
+ const lockKey = command.input.target?.kind === "attached_tab" ? command.input.target.bindingId : command.input.sessionId;
1347
+ if (activeCommands.has(lockKey)) return { id: command.id, connectionId: command.connectionId, result: fail("INVALID_INPUT", "This browser page is already executing an action.") };
1348
+ activeCommands.add(lockKey);
1349
+ holdAutomationSession(command.input.sessionId);
895
1350
  try {
896
- const result = await execute(command.input, command.timeoutMs, command.visualFallback, startedAt);
1351
+ if (signal?.aborted) return { id: command.id, connectionId: command.connectionId, result: fail("ABORTED", "Operation was cancelled.") };
1352
+ const result = await execute(command.input, command.timeoutMs, command.visualFallback, startedAt, signal);
897
1353
  return { id: command.id, connectionId: command.connectionId, result };
898
1354
  } catch (error) {
899
1355
  return {
@@ -902,18 +1358,26 @@ async function executeBrowserCommand(command) {
902
1358
  result: {
903
1359
  ok: false,
904
1360
  error: {
905
- code: /timeout/i.test(String(error)) ? "TIMEOUT" : "DRIVER_UNAVAILABLE",
1361
+ code: signal?.aborted ? "ABORTED" : /timeout/i.test(String(error)) ? "TIMEOUT" : "DRIVER_UNAVAILABLE",
906
1362
  message: error instanceof Error ? error.message : String(error)
907
1363
  }
908
1364
  }
909
1365
  };
1366
+ } finally {
1367
+ activeCommands.delete(lockKey);
1368
+ releaseAutomationSession(command.input.sessionId);
910
1369
  }
911
1370
  }
912
- async function execute(input, timeoutMs, visualFallback, startedAt) {
1371
+ async function execute(input, timeoutMs, visualFallback, startedAt, signal) {
913
1372
  const sessionId = input.sessionId;
914
1373
  if (!sessionId) return fail("INVALID_INPUT", "Extension actions require a session id.");
915
1374
  const attached = await resolveAttachedTarget(input);
916
1375
  if (attached && !("binding" in attached)) return attached;
1376
+ if (attached && !["observe", "resolve", "wait", "close"].includes(input.action)) {
1377
+ const activeRecording = (await recordings()).some((item) => item.tabId === attached.tabId && ["recording", "paused"].includes(item.state));
1378
+ if (activeRecording) return fail("INVALID_INPUT", "This page is being recorded. Finish recording before running an automation.");
1379
+ }
1380
+ if (signal?.aborted) return fail("ABORTED", "Operation was cancelled.");
917
1381
  const stateKey = attached?.stateKey ?? sessionId;
918
1382
  if (input.action === "close") {
919
1383
  if (!attached) await closeSession(sessionId);
@@ -935,6 +1399,15 @@ async function execute(input, timeoutMs, visualFallback, startedAt) {
935
1399
  }
936
1400
  if (input.action === "tabs") return tabs(sessionId, input, startedAt, timeoutMs);
937
1401
  if (input.action === "sequence") return fail("INVALID_INPUT", "Sequences are expanded by the browser runtime.");
1402
+ if (input.action === "resolve") {
1403
+ const tabId2 = attached?.tabId ?? await getActiveTabId(sessionId);
1404
+ const resolved = await evaluate(tabId2, `(${browserDom.toString()})(${JSON.stringify({ operation: "resolve", target: input.semanticTarget })})`);
1405
+ if (resolved.matches !== 1 || !resolved.node) return fail("TARGET_NOT_FOUND", `Semantic target must match exactly one element; matched ${resolved.matches}.`);
1406
+ const observation2 = await observe(sessionId, "never", attached);
1407
+ observation2.nodes = [resolved.node];
1408
+ observations.get(stateKey).nodes = observation2.nodes;
1409
+ return success("resolve", "read", startedAt, observation2);
1410
+ }
938
1411
  const current = observations.get(stateKey);
939
1412
  if (!current || current.revision !== input.revision) {
940
1413
  return fail("STALE_OBSERVATION", "The page changed. Observe it again.", await observe(sessionId, "never", attached));
@@ -965,6 +1438,13 @@ async function execute(input, timeoutMs, visualFallback, startedAt) {
965
1438
  }`, input.value);
966
1439
  if (!changed) return fail("TARGET_NOT_FOUND", `Target ${input.ref} was not found.`);
967
1440
  if (input.submit) await pressKey(tabId, "Enter");
1441
+ } else if (input.action === "check") {
1442
+ const changed = await evaluateRef(tabId, input.ref, `(element, checked) => {
1443
+ if (!['checkbox', 'radio'].includes(element.type)) return false;
1444
+ if (element.checked !== checked) element.click();
1445
+ return element.checked === checked;
1446
+ }`, input.checked);
1447
+ if (!changed) return fail("INVALID_INPUT", "The checkbox could not be set to the requested state.");
968
1448
  } else if (input.action === "select") {
969
1449
  const changed = await evaluateRef(tabId, input.ref, `(element, value) => {
970
1450
  element.value = value;
@@ -983,7 +1463,7 @@ async function execute(input, timeoutMs, visualFallback, startedAt) {
983
1463
  await dispatchInput(tabId, "dispatchMouseEvent", { type: "mouseWheel", x: 640, y: 360, deltaX: 0, deltaY: input.deltaY });
984
1464
  }
985
1465
  } else if (input.action === "wait") {
986
- await wait(tabId, input, timeoutMs);
1466
+ await wait(tabId, input, timeoutMs, signal);
987
1467
  } else if (input.action === "upload") {
988
1468
  const document = await sendCommand(tabId, "DOM.getDocument", { depth: 1 });
989
1469
  const query = await sendCommand(tabId, "DOM.querySelectorAll", {
@@ -1089,10 +1569,11 @@ async function tabs(sessionId, input, startedAt, timeoutMs) {
1089
1569
  }
1090
1570
  };
1091
1571
  }
1092
- async function wait(tabId, input, defaultTimeout) {
1572
+ async function wait(tabId, input, defaultTimeout, signal) {
1093
1573
  const timeout = input.timeoutMs ?? defaultTimeout;
1094
1574
  const deadline = Date.now() + timeout;
1095
1575
  while (Date.now() < deadline) {
1576
+ if (signal?.aborted) throw new Error("Operation aborted.");
1096
1577
  if (input.condition === "page_idle") {
1097
1578
  const tab = await chrome.tabs.get(tabId);
1098
1579
  if (tab.status === "complete") return;
@@ -1228,7 +1709,7 @@ function scheduleReconnectAlarm() {
1228
1709
  }
1229
1710
  const browserToolRegistry = new EndpointToolRegistry([{
1230
1711
  descriptor: BROWSER_CONTROL_ENDPOINT_DESCRIPTOR,
1231
- execute: async (args) => {
1712
+ execute: async (args, context) => {
1232
1713
  const input = args.input;
1233
1714
  if (!input || typeof input !== "object") throw new TypeError("Browser control input is required");
1234
1715
  const result = await executeBrowserCommand({
@@ -1238,9 +1719,12 @@ const browserToolRegistry = new EndpointToolRegistry([{
1238
1719
  input,
1239
1720
  timeoutMs: typeof args.timeoutMs === "number" ? args.timeoutMs : 3e4,
1240
1721
  visualFallback: args.visualFallback !== false
1241
- });
1722
+ }, context.signal);
1242
1723
  return { content: [{ type: "json", value: fitBrowserControlResultToFrame(result.result) }] };
1243
1724
  }
1725
+ }, {
1726
+ descriptor: BROWSER_RECORDING_ENDPOINT_DESCRIPTOR,
1727
+ execute: async (args) => ({ content: [{ type: "json", value: await recordingCommand(String(args.operation)) }] })
1244
1728
  }]);
1245
1729
  const endpointHost = new EndpointToolHostController({
1246
1730
  registry: browserToolRegistry,
@@ -1300,6 +1784,7 @@ async function connect(generation) {
1300
1784
  createHello: () => createBrowserEndpointHello(browserToolRegistry.descriptors()),
1301
1785
  onReady: ({ endpointId, turnToken }) => {
1302
1786
  endpointClaim = { endpointId, token: turnToken };
1787
+ setRecordingClaim(endpointClaim);
1303
1788
  lastConnectionError = void 0;
1304
1789
  clearReconnectAlarm();
1305
1790
  endpointHost.connect((message) => client.sendEndpointMessage(message));
@@ -1311,6 +1796,7 @@ async function connect(generation) {
1311
1796
  },
1312
1797
  onDisconnected: () => {
1313
1798
  endpointClaim = void 0;
1799
+ setRecordingClaim(void 0);
1314
1800
  endpointHost.disconnect();
1315
1801
  scheduleReconnectAlarm();
1316
1802
  }
@@ -1339,7 +1825,15 @@ function connectWithLogging() {
1339
1825
  });
1340
1826
  }
1341
1827
  chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
1342
- if (message.type === "browser/get-status") {
1828
+ if (message.type === "browser/recording-event") {
1829
+ void acceptRecordingEvent(message, sender).then(() => sendResponse({ ok: true })).catch((error) => sendResponse({ ok: false, error: String(error) }));
1830
+ } else if (message.type === "browser/recording-command") {
1831
+ if (!sender.url?.startsWith(`chrome-extension://${chrome.runtime.id}/`) || sender.tab) {
1832
+ sendResponse({ ok: false, error: "Invalid recording control source." });
1833
+ return true;
1834
+ }
1835
+ void recordingCommand(message.operation ?? "status", message.tabId).then((value) => sendResponse({ ok: true, value })).catch((error) => sendResponse({ ok: false, error: error instanceof Error ? error.message : String(error) }));
1836
+ } else if (message.type === "browser/get-status") {
1343
1837
  sendResponse({ connected: Boolean(endpointClaim), transport: "gateway-realtime", error: lastConnectionError });
1344
1838
  } else if (message.type === "browser/get-endpoint-claim") {
1345
1839
  sendResponse({ claim: endpointClaim, error: lastConnectionError });
@@ -1402,3 +1896,7 @@ chrome.storage.onChanged.addListener((changes, area) => {
1402
1896
  });
1403
1897
  void chrome.sidePanel.setPanelBehavior({ openPanelOnActionClick: true });
1404
1898
  connectWithLogging();
1899
+ chrome.alarms.create("xopc-recording-sync", { periodInMinutes: 0.5 });
1900
+ chrome.alarms.onAlarm.addListener((alarm) => {
1901
+ if (alarm.name === "xopc-recording-sync") void syncRecordings();
1902
+ });