@xopcai/xopc 0.0.264 → 0.0.266

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 (209) hide show
  1. package/dist/browser-ext/dist/assets/auth-CknJ00ae.js +5844 -0
  2. package/dist/browser-ext/dist/assets/sidepanel-C69P3v9u.css +113 -0
  3. package/dist/browser-ext/dist/background.js +200 -68
  4. package/dist/browser-ext/dist/sidepanel.html +14 -0
  5. package/dist/browser-ext/dist/sidepanel.js +18832 -0
  6. package/dist/browser-ext/manifest.json +20 -20
  7. package/dist/extensions/telegram/xopc.extension.json +1 -1
  8. package/dist/gateway/static/root/assets/{activity-store-BYBCpt7_.js → activity-store-Bhyy6ENz.js} +1 -1
  9. package/dist/gateway/static/root/assets/{agent-avatar-dicebear-D20aBHPJ.js → agent-avatar-dicebear-Qrp5ALCF.js} +1 -1
  10. package/dist/gateway/static/root/assets/{agent-defaults-settings-panel-BZvMqNrU.js → agent-defaults-settings-panel-DwinST48.js} +1 -1
  11. package/dist/gateway/static/root/assets/{agents-6foBELwt.js → agents-Dlr0YyNt.js} +1 -1
  12. package/dist/gateway/static/root/assets/{api-YbtjDbuG.js → api-B2y7ZjZk.js} +1 -1
  13. package/dist/gateway/static/root/assets/{app-management-settings-panel-4ps1RrDU.js → app-management-settings-panel-bLNvz93c.js} +1 -1
  14. package/dist/gateway/static/root/assets/{appearance-settings-C7nBHnkG.js → appearance-settings-D5qgIepF.js} +1 -1
  15. package/dist/gateway/static/root/assets/{apps-page-B_g-rL5b.js → apps-page-BQVlXhIb.js} +1 -1
  16. package/dist/gateway/static/root/assets/{archive-plugin-DfKK1iJO.js → archive-plugin-BEA7VAA4.js} +1 -1
  17. package/dist/gateway/static/root/assets/{attachment-load-BOU1pdnQ.js → attachment-load-B1RyF3uu.js} +1 -1
  18. package/dist/gateway/static/root/assets/{automations-page-BePzexAH.js → automations-page-BfH0FdKa.js} +1 -1
  19. package/dist/gateway/static/root/assets/{automations-workspace-7-m5pBEZ.js → automations-workspace-C37AlD3E.js} +1 -1
  20. package/dist/gateway/static/root/assets/{binary-plugins-Cly8XDyt.js → binary-plugins-EU884ZXi.js} +1 -1
  21. package/dist/gateway/static/root/assets/{block-editor-DzSu62js.js → block-editor-Bjr3naHb.js} +1 -1
  22. package/dist/gateway/static/root/assets/{browser-automation-inputs-8bHj7Fv2.js → browser-automation-inputs-s5I78Iko.js} +1 -1
  23. package/dist/gateway/static/root/assets/{browser-automations-page-CzL7Aj2k.js → browser-automations-page-CDrYrNGy.js} +1 -1
  24. package/dist/gateway/static/root/assets/{browser-settings-page-Dum3mTiz.js → browser-settings-page-DOmUkj2k.js} +1 -1
  25. package/dist/gateway/static/root/assets/{capabilities-settings-panel-Ci6qVS-O.js → capabilities-settings-panel-slt1o04n.js} +2 -2
  26. package/dist/gateway/static/root/assets/{channels-settings-BOGEZnSv.js → channels-settings-oHTTX6Jy.js} +1 -1
  27. package/dist/gateway/static/root/assets/{channels-status-swr-BR8oQ_eP.js → channels-status-swr-_OsWr_vS.js} +1 -1
  28. package/dist/gateway/static/root/assets/{chat-terminal-dock-BF9ZXa5Z.js → chat-terminal-dock-DEYQdv6n.js} +1 -1
  29. package/dist/gateway/static/root/assets/{connectors-page-DBvEvupa.js → connectors-page-olrbxHIT.js} +1 -1
  30. package/dist/gateway/static/root/assets/{cron-expression-BszT_gjK.js → cron-expression-DFrbDWpP.js} +1 -1
  31. package/dist/gateway/static/root/assets/{date-picker-D2MKBJ0G.js → date-picker-CYriYKpH.js} +1 -1
  32. package/dist/gateway/static/root/assets/{dependency-picker-DqigBx-G.js → dependency-picker-XY3qUErs.js} +1 -1
  33. package/dist/gateway/static/root/assets/{desktop-pet-CapspBGG.js → desktop-pet-Dsc__ynA.js} +1 -1
  34. package/dist/gateway/static/root/assets/{desktop-pet-settings-DMPk4CaK.js → desktop-pet-settings-EpuX3C08.js} +1 -1
  35. package/dist/gateway/static/root/assets/{extension-debug-page-CXLsDtrW.js → extension-debug-page-B_NeLV4K.js} +1 -1
  36. package/dist/gateway/static/root/assets/{extension-page-B2j87ROw.js → extension-page-D3JC6mLB.js} +1 -1
  37. package/dist/gateway/static/root/assets/{extension-settings-page-t1BApw-u.js → extension-settings-page-CXTQt1lp.js} +1 -1
  38. package/dist/gateway/static/root/assets/{files-BArWTfr7.js → files-Be5viEFY.js} +1 -1
  39. package/dist/gateway/static/root/assets/{gateway-config-swr-BFSesvAC.js → gateway-config-swr-OObCK8jM.js} +1 -1
  40. package/dist/gateway/static/root/assets/{gateway-settings-BFAecoGy.js → gateway-settings-yeYm_UPE.js} +1 -1
  41. package/dist/gateway/static/root/assets/{gateway-startup-retry-D1giYgI-.js → gateway-startup-retry-D_TxKN_A.js} +1 -1
  42. package/dist/gateway/static/root/assets/{heartbeat-settings-CyD9zDcU.js → heartbeat-settings-coh1LFsg.js} +1 -1
  43. package/dist/gateway/static/root/assets/{home-page-uItZMiBE.js → home-page-CAgoBZqp.js} +1 -1
  44. package/dist/gateway/static/root/assets/{image-generation-api-BeB-OPqO.js → image-generation-api-fPU9yGsw.js} +1 -1
  45. package/dist/gateway/static/root/assets/index-Cn__9Un2.js +74 -0
  46. package/dist/gateway/static/root/assets/{keyboard-shortcuts-settings-Q_CM-ZCs.js → keyboard-shortcuts-settings-Cu1bI3Bg.js} +1 -1
  47. package/dist/gateway/static/root/assets/{local-app-workbench-page-DaiBWfOt.js → local-app-workbench-page-C9s2lFaI.js} +1 -1
  48. package/dist/gateway/static/root/assets/{local-apps-page-BALMNCSp.js → local-apps-page-CJ03Umjr.js} +1 -1
  49. package/dist/gateway/static/root/assets/{locale-store-RJHYFoq9.js → locale-store-DFQX9kTl.js} +1 -1
  50. package/dist/gateway/static/root/assets/{logs-page-C-TS7oFg.js → logs-page-CY_DQKJ2.js} +1 -1
  51. package/dist/gateway/static/root/assets/{management-settings-BajkJeYl.js → management-settings-YPEemeMo.js} +1 -1
  52. package/dist/gateway/static/root/assets/{markdown-view-BxoQJqHx.js → markdown-view-D-5jHRWg.js} +1 -1
  53. package/dist/gateway/static/root/assets/{media-plugins-C_8Hl6KG.js → media-plugins-BjT6jlrA.js} +1 -1
  54. package/dist/gateway/static/root/assets/{messages-Cf1h5WXB.js → messages-Dj8QuS6e.js} +2 -2
  55. package/dist/gateway/static/root/assets/{mobile-pairing-wizard-CsWIZrZW.js → mobile-pairing-wizard-A8eLtV6v.js} +1 -1
  56. package/dist/gateway/static/root/assets/{note-detail-panel-CCbPg_GP.js → note-detail-panel-DHT2QksJ.js} +3 -3
  57. package/dist/gateway/static/root/assets/{notes-page-BdxxcUKa.js → notes-page-BRFQiivM.js} +1 -1
  58. package/dist/gateway/static/root/assets/{page-tabs-BMYQef5D.js → page-tabs-DNUwulnl.js} +1 -1
  59. package/dist/gateway/static/root/assets/{preview-open-alternatives-CKZ-H5hJ.js → preview-open-alternatives-B33KsKkJ.js} +1 -1
  60. package/dist/gateway/static/root/assets/{product-delivery-CV4pDOtG.js → product-delivery-CCC4FJ3Y.js} +1 -1
  61. package/dist/gateway/static/root/assets/{product-open-page-zXJIptXs.js → product-open-page-CIGLPFm7.js} +1 -1
  62. package/dist/gateway/static/root/assets/{project-detail-page-DRhhxhZB.js → project-detail-page-CItEFVt9.js} +1 -1
  63. package/dist/gateway/static/root/assets/{projects-page-Wt7gF3Sx.js → projects-page-Bmy-c_lL.js} +1 -1
  64. package/dist/gateway/static/root/assets/{registry-api-BSfFaxVN.js → registry-api-CsZLatKo.js} +1 -1
  65. package/dist/gateway/static/root/assets/{remote-access-hub-B6de5Zrb.js → remote-access-hub-H6B1cA2V.js} +1 -1
  66. package/dist/gateway/static/root/assets/{runtime-tools-settings-panel-DcUB5h1f.js → runtime-tools-settings-panel-BsCovC39.js} +1 -1
  67. package/dist/gateway/static/root/assets/{schema-form-CquWhe7r.js → schema-form-NsPb_Vam.js} +1 -1
  68. package/dist/gateway/static/root/assets/{schemas-BHS7bo06.js → schemas-BaskVM5g.js} +2 -2
  69. package/dist/gateway/static/root/assets/{sessions-page-BNWO2EVF.js → sessions-page-CyYLSXk_.js} +1 -1
  70. package/dist/gateway/static/root/assets/settings-advanced-gate-3T4rPk4A.js +1 -0
  71. package/dist/gateway/static/root/assets/{settings-form-section-DwswAefc.js → settings-form-section-BzPIPe1-.js} +1 -1
  72. package/dist/gateway/static/root/assets/{settings-page-CoAVmjjs.js → settings-page-4idpMhsw.js} +1 -1
  73. package/dist/gateway/static/root/assets/{setup-status-panel-C9EIcWtH.js → setup-status-panel-D2ds_TTD.js} +1 -1
  74. package/dist/gateway/static/root/assets/{share-preview-page-DWwy_4zh.js → share-preview-page-Djo3VTaC.js} +1 -1
  75. package/dist/gateway/static/root/assets/{shares-settings-CIUvq27-.js → shares-settings-fhRzNfRp.js} +1 -1
  76. package/dist/gateway/static/root/assets/skill-reload-api-Ct8tUju_.js +1 -0
  77. package/dist/gateway/static/root/assets/{skills-page-CqsgVtVh.js → skills-page-riM7FytZ.js} +1 -1
  78. package/dist/gateway/static/root/assets/{skills-page.utils-B-N73248.js → skills-page.utils-B3G8LBb-.js} +1 -1
  79. package/dist/gateway/static/root/assets/{system-settings-panel-BndjXuBZ.js → system-settings-panel-CW91jjnB.js} +1 -1
  80. package/dist/gateway/static/root/assets/{task-detail-page-CG6Ha9t7.js → task-detail-page-BBXKvl0k.js} +1 -1
  81. package/dist/gateway/static/root/assets/{theme-store-C5ssn64t.js → theme-store-zRT6vaZc.js} +1 -1
  82. package/dist/gateway/static/root/assets/{tunnel-api-BxZdcp3h.js → tunnel-api-DILER0Yb.js} +1 -1
  83. package/dist/gateway/static/root/assets/{use-autosave-mj9CUuz1.js → use-autosave-GxnP8vLO.js} +1 -1
  84. package/dist/gateway/static/root/assets/{user-model-page-DKeHMXI4.js → user-model-page-I1H4XRfA.js} +1 -1
  85. package/dist/gateway/static/root/assets/{voice-api-key-field-CzxVMYlr.js → voice-api-key-field-CI3sXyl4.js} +1 -1
  86. package/dist/gateway/static/root/assets/{voice-config-api-COA0V1cK.js → voice-config-api-DpJEu2RQ.js} +1 -1
  87. package/dist/gateway/static/root/assets/{work-discovery-overlay-DMiPK8dp.js → work-discovery-overlay-DZC9Exn3.js} +1 -1
  88. package/dist/gateway/static/root/assets/{workflow-run-setup-panel-C82OXCJK.js → workflow-run-setup-panel-CaBaQER2.js} +1 -1
  89. package/dist/gateway/static/root/assets/{workflows-page-HLCUV3rQ.js → workflows-page-C-_Dxihw.js} +1 -1
  90. package/dist/gateway/static/root/index.html +12 -12
  91. package/dist/package.js +1 -1
  92. package/dist/packages/browser-control-contract/src/index.js +14 -0
  93. package/dist/packages/endpoint-tools-protocol/src/index.js +2 -1
  94. package/dist/packages/gateway-contract/src/browser-page-context.js +43 -0
  95. package/dist/packages/gateway-contract/src/browser-tab-binding.js +29 -0
  96. package/dist/packages/gateway-contract/src/device-pairing.js +18 -2
  97. package/dist/packages/realtime-protocol/src/index.js +1 -0
  98. package/dist/packages/realtime-protocol/src/voice-audio.js +70 -9
  99. package/dist/packages/realtime-protocol/src/voice.js +65 -17
  100. package/dist/src/agent/agent-manager.js +4 -4
  101. package/dist/src/agent/background-review/run-background-review.js +1 -1
  102. package/dist/src/agent/embedded/model-runtime.js +1 -1
  103. package/dist/src/agent/embedded/run-for-session.js +1 -1
  104. package/dist/src/agent/image/tool-model-config.js +1 -1
  105. package/dist/src/agent/mcp/oauth/mcp-oauth-store.js +1 -1
  106. package/dist/src/agent/service.js +2 -2
  107. package/dist/src/agent/session/session-inspector.js +1 -1
  108. package/dist/src/agent/skills/hub-lock.js +1 -1
  109. package/dist/src/agent/source-context/browser-page.d.ts +3 -0
  110. package/dist/src/agent/source-context/browser-page.js +39 -0
  111. package/dist/src/agent/source-context/injector.js +9 -2
  112. package/dist/src/agent/source-context/types.d.ts +7 -1
  113. package/dist/src/agent/source-context/types.js +4 -1
  114. package/dist/src/agent/tasks/task-judge-service.js +3 -3
  115. package/dist/src/agent/tools/factory.js +9 -1
  116. package/dist/src/ai-assist/text-assist.js +1 -1
  117. package/dist/src/auth/credentials.js +2 -2
  118. package/dist/src/auth/oauth/xopc-cloud.js +1 -1
  119. package/dist/src/auth/profiles/store.js +1 -1
  120. package/dist/src/auth/sync-provider-auth.js +2 -2
  121. package/dist/src/browser/automations/schema.d.ts +1 -1
  122. package/dist/src/browser/automations/service.d.ts +1 -1
  123. package/dist/src/browser/native-messaging-host.d.ts +15 -0
  124. package/dist/src/browser/native-messaging-host.js +105 -0
  125. package/dist/src/browser/providers/browser-ext-install.d.ts +17 -1
  126. package/dist/src/browser/providers/browser-ext-install.js +82 -16
  127. package/dist/src/browser/providers/extension.d.ts +4 -0
  128. package/dist/src/browser/providers/extension.js +104 -38
  129. package/dist/src/browser/runtime/browser-runtime.d.ts +2 -1
  130. package/dist/src/browser/runtime/browser-runtime.js +6 -2
  131. package/dist/src/cli/commands/browser.js +13 -3
  132. package/dist/src/cli/commands/doctor/checks/database-schema.js +1 -1
  133. package/dist/src/cli/commands/doctor/checks/provider-auth.js +1 -1
  134. package/dist/src/cli/commands/models.js +1 -1
  135. package/dist/src/config/index.js +4 -4
  136. package/dist/src/config/loader.js +2 -2
  137. package/dist/src/config/models-json.js +1 -1
  138. package/dist/src/endpoint-tools/runtime.js +2 -0
  139. package/dist/src/extensions/lockfile.js +1 -1
  140. package/dist/src/gateway/agents-admin.js +1 -1
  141. package/dist/src/gateway/chat-stream/mapper.js +3 -2
  142. package/dist/src/gateway/file-path-classifier.js +1 -1
  143. package/dist/src/gateway/hono/app.js +19 -3
  144. package/dist/src/gateway/hono/lib/config-payload.js +1 -1
  145. package/dist/src/gateway/hono/middleware/auth.d.ts +4 -1
  146. package/dist/src/gateway/hono/middleware/auth.js +12 -1
  147. package/dist/src/gateway/hono/routes/browser-install.js +1 -1
  148. package/dist/src/gateway/hono/routes/browser.js +106 -7
  149. package/dist/src/gateway/hono/routes/devices.js +36 -3
  150. package/dist/src/gateway/hono/routes/endpoint-tools.js +15 -0
  151. package/dist/src/gateway/hono/routes/models.js +1 -1
  152. package/dist/src/gateway/hono/routes/session-input-handler.d.ts +20 -2
  153. package/dist/src/gateway/hono/routes/session-input-handler.js +18 -0
  154. package/dist/src/gateway/hono/routes/sessions.js +12 -6
  155. package/dist/src/gateway/hono/routes/voice.js +5 -4
  156. package/dist/src/gateway/security/gateway-scopes.d.ts +1 -0
  157. package/dist/src/gateway/security/gateway-scopes.js +10 -1
  158. package/dist/src/gateway/security/origin-check.d.ts +1 -0
  159. package/dist/src/gateway/security/origin-check.js +5 -1
  160. package/dist/src/gateway/service/agent-runner.js +2 -1
  161. package/dist/src/gateway/service/config-coordinator.js +1 -1
  162. package/dist/src/gateway/service/session-input-coordinator.d.ts +1 -0
  163. package/dist/src/gateway/service/session-input-coordinator.js +5 -4
  164. package/dist/src/gateway/service.js +22 -21
  165. package/dist/src/providers/auth-runtime/resolve-auth.js +1 -1
  166. package/dist/src/providers/index.js +2 -2
  167. package/dist/src/providers/model-registry.js +1 -1
  168. package/dist/src/providers/register-runtime-providers.js +1 -1
  169. package/dist/src/realtime/runtime.js +2 -1
  170. package/dist/src/session/client-history.d.ts +4 -1
  171. package/dist/src/session/client-history.js +6 -3
  172. package/dist/src/storage/sqlite/browser-tab-binding-repository.d.ts +7 -0
  173. package/dist/src/storage/sqlite/browser-tab-binding-repository.js +60 -0
  174. package/dist/src/storage/sqlite/device-access-repository.d.ts +3 -1
  175. package/dist/src/storage/sqlite/device-access-repository.js +4 -3
  176. package/dist/src/storage/sqlite/device-pairing-approval.js +6 -4
  177. package/dist/src/storage/sqlite/device-pairing-repository.d.ts +1 -1
  178. package/dist/src/storage/sqlite/index.d.ts +1 -0
  179. package/dist/src/storage/sqlite/index.js +2 -1
  180. package/dist/src/storage/sqlite/migrations/155_browser_extension.sql +120 -0
  181. package/dist/src/storage/sqlite/migrations/156_browser_tab_bindings.sql +14 -0
  182. package/dist/src/storage/sqlite/migrations/runner.d.ts +1 -1
  183. package/dist/src/storage/sqlite/migrations/runner.js +3 -3
  184. package/dist/src/storage/sqlite/migrations/runner.ts +1 -1
  185. package/dist/src/storage/sqlite/schema.js +1 -1
  186. package/dist/src/tui/tui-startup-resources.js +1 -1
  187. package/dist/src/tui/tui.js +1 -1
  188. package/dist/src/voice/realtime/agentBroker.d.ts +54 -0
  189. package/dist/src/voice/realtime/agentBroker.js +116 -0
  190. package/dist/src/voice/realtime/agentEngine.js +62 -15
  191. package/dist/src/voice/realtime/engine.d.ts +2 -1
  192. package/dist/src/voice/realtime/omniEngine.js +36 -7
  193. package/dist/src/voice/realtime/pcmFrameBuffer.d.ts +6 -0
  194. package/dist/src/voice/realtime/pcmFrameBuffer.js +29 -0
  195. package/dist/src/voice/realtime/runtime.js +97 -21
  196. package/dist/src/voice/realtime/runtime.types.d.ts +5 -21
  197. package/dist/src/voice/realtime/turnPolicy.d.ts +36 -0
  198. package/dist/src/voice/realtime/turnPolicy.js +134 -0
  199. package/dist/src/work-discovery/service.js +1 -1
  200. package/dist/src/workflows/service/workflow-run-service.js +1 -1
  201. package/package.json +2 -2
  202. package/dist/browser-ext/dist/content.js +0 -191
  203. package/dist/browser-ext/dist/popup.js +0 -32
  204. package/dist/browser-ext/popup.html +0 -37
  205. package/dist/gateway/static/root/assets/index-DQtjs2x3.js +0 -74
  206. package/dist/gateway/static/root/assets/settings-advanced-gate-DK1yTYE4.js +0 -1
  207. package/dist/gateway/static/root/assets/skill-reload-api-DfE3zFVw.js +0 -1
  208. package/dist/src/voice/realtime/conversationTurn.d.ts +0 -20
  209. package/dist/src/voice/realtime/conversationTurn.js +0 -83
@@ -0,0 +1,113 @@
1
+ :root {
2
+ color-scheme: light dark;
3
+ font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
4
+ background: #f8fafc;
5
+ color: #0f172a;
6
+ }
7
+
8
+ * { box-sizing: border-box; }
9
+ html, body, #root { height: 100%; margin: 0; }
10
+ button, input, textarea { font: inherit; }
11
+ button { cursor: pointer; }
12
+
13
+ .shell { min-height: 100%; display: flex; flex-direction: column; background: #fff; }
14
+ .header { height: 56px; padding: 0 16px; display: flex; align-items: center; gap: 10px; border-bottom: 1px solid #e2e8f0; }
15
+ .brand { font-size: 17px; font-weight: 650; flex: 1; }
16
+ .status { display: inline-flex; align-items: center; gap: 6px; font-size: 12px; color: #64748b; }
17
+ .status-dot { width: 8px; height: 8px; border-radius: 999px; background: #94a3b8; }
18
+ .status-dot.online { background: #22c55e; }
19
+ .status-dot.error { background: #ef4444; }
20
+ .center { flex: 1; display: grid; place-items: center; padding: 24px; }
21
+ .card { width: min(100%, 420px); border: 1px solid #e2e8f0; border-radius: 16px; padding: 20px; background: #fff; }
22
+ .card h1 { margin: 0 0 8px; font-size: 20px; }
23
+ .muted { color: #64748b; line-height: 1.5; font-size: 13px; }
24
+ .field { display: grid; gap: 6px; margin-top: 14px; font-size: 13px; }
25
+ .field input, .field textarea { width: 100%; border: 1px solid #cbd5e1; border-radius: 9px; padding: 10px 12px; background: transparent; }
26
+ .primary { border: 0; border-radius: 9px; background: #2563eb; color: #fff; padding: 10px 14px; font-weight: 600; }
27
+ .secondary { border: 1px solid #cbd5e1; border-radius: 9px; background: transparent; padding: 8px 12px; }
28
+ .actions { display: flex; gap: 8px; margin-top: 16px; }
29
+ .local-connect { width: 100%; margin-top: 12px; }
30
+ .pairing-divider { display: flex; align-items: center; gap: 8px; margin-top: 16px; color: #94a3b8; font-size: 11px; }
31
+ .pairing-divider::before, .pairing-divider::after { content: ""; height: 1px; flex: 1; background: #e2e8f0; }
32
+ .error-text { color: #dc2626; font-size: 13px; margin-top: 10px; }
33
+ .approval-code { font: 700 28px ui-monospace, monospace; letter-spacing: 5px; margin: 18px 0; }
34
+ .disconnect-link { position: absolute; top: 17px; right: 96px; border: 0; background: transparent; color: #64748b; font-size: 11px; }
35
+ .chat { min-height: 0; flex: 1; display: flex; flex-direction: column; position: relative; }
36
+ .chat-toolbar { height: 52px; border-bottom: 1px solid #e2e8f0; display: flex; align-items: center; padding: 0 12px; gap: 8px; }
37
+ .session-trigger { flex: 1; display: flex; justify-content: center; gap: 7px; border: 0; background: transparent; font-weight: 600; min-width: 0; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
38
+ .icon-button { width: 32px; height: 32px; border-radius: 8px; border: 1px solid #e2e8f0; background: transparent; }
39
+ .session-menu { position: absolute; z-index: 5; top: 108px; left: 12px; right: 12px; max-height: min(60vh, 500px); border: 1px solid #cbd5e1; border-radius: 12px; background: #fff; padding: 10px; box-shadow: 0 14px 40px rgb(15 23 42 / 14%); }
40
+ .session-menu > input { width: 100%; border: 0; border-bottom: 1px solid #e2e8f0; padding: 9px; outline: none; background: transparent; }
41
+ .session-list { overflow: auto; max-height: 380px; padding-top: 5px; }
42
+ .session-list button { width: 100%; border: 0; background: transparent; border-radius: 7px; display: flex; justify-content: space-between; gap: 12px; padding: 9px; text-align: left; }
43
+ .session-list button:hover { background: #f1f5f9; }
44
+ .session-list span { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
45
+ .session-list small { color: #94a3b8; }
46
+ .messages { flex: 1; overflow-y: auto; padding: 18px 16px 150px; }
47
+ .empty-chat { height: 100%; display: grid; place-content: center; text-align: center; color: #64748b; }
48
+ .mark { font-size: 40px; color: #94a3b8; }
49
+ .message { margin: 0 0 20px; line-height: 1.55; }
50
+ .message.user { margin-left: 15%; background: #f1f5f9; border-radius: 14px; padding: 10px 12px; }
51
+ .message-role { font-size: 11px; font-weight: 700; color: #64748b; margin-bottom: 4px; text-transform: uppercase; letter-spacing: .04em; }
52
+ .message-content { white-space: pre-wrap; overflow-wrap: anywhere; }
53
+ .source-badge { display: inline-block; max-width: 100%; margin-bottom: 6px; border: 1px solid #bfdbfe; border-radius: 999px; padding: 3px 8px; color: #1d4ed8; font-size: 11px; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
54
+ .caret { display: inline-block; width: 6px; height: 1em; margin-left: 2px; vertical-align: -2px; background: #2563eb; animation: blink 1s steps(1) infinite; }
55
+ @keyframes blink { 50% { opacity: 0; } }
56
+ .composer { position: absolute; left: 12px; right: 12px; bottom: 12px; border: 1px solid #cbd5e1; border-radius: 16px; background: #fff; padding: 10px; box-shadow: 0 8px 30px rgb(15 23 42 / 10%); }
57
+ .composer textarea { width: 100%; resize: none; border: 0; outline: none; background: transparent; }
58
+ .tab-mention-menu { position: absolute; z-index: 6; left: 0; right: 0; bottom: calc(100% + 8px); max-height: min(45vh, 320px); overflow-y: auto; border: 1px solid #cbd5e1; border-radius: 12px; background: #fff; padding: 5px; box-shadow: 0 14px 40px rgb(15 23 42 / 14%); }
59
+ .tab-mention-menu button { width: 100%; display: grid; gap: 2px; border: 0; border-radius: 8px; padding: 8px 10px; background: transparent; color: inherit; text-align: left; }
60
+ .tab-mention-menu button:hover, .tab-mention-menu button.selected { background: #eff6ff; }
61
+ .tab-mention-menu span, .tab-mention-menu small { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
62
+ .tab-mention-menu span { font-size: 12px; font-weight: 600; }
63
+ .tab-mention-menu small, .tab-mention-empty { color: #64748b; font-size: 11px; }
64
+ .tab-mention-empty { padding: 10px; }
65
+ .composer-row { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 6px; margin-top: 5px; }
66
+ .composer-tools { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; min-width: 0; }
67
+ .composer-tools button { border: 0; background: transparent; padding: 3px; color: #475569; font-size: 11px; }
68
+ .context-chip { display: flex; align-items: center; gap: 5px; margin-bottom: 7px; border: 1px solid #bfdbfe; border-radius: 8px; padding: 6px 7px; color: #1e40af; background: #eff6ff; font-size: 11px; }
69
+ .context-chip span { min-width: 0; flex: 1; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
70
+ .context-chip button { border: 0; background: transparent; color: inherit; padding: 2px 4px; }
71
+ .context-chip.stale { border-color: #fbbf24; background: #fffbeb; color: #92400e; }
72
+ .attachment-chip { display: flex; align-items: center; gap: 5px; margin-bottom: 7px; border: 1px solid #cbd5e1; border-radius: 8px; padding: 6px 7px; color: #475569; background: #f8fafc; font-size: 11px; }
73
+ .attachment-chip span { min-width: 0; flex: 1; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
74
+ .attachment-chip button { border: 0; background: transparent; color: inherit; }
75
+ .file-input { display: none; }
76
+ .connection-label { color: #94a3b8; font-size: 11px; }
77
+ .model-controls { display: flex; align-items: center; gap: 4px; margin-left: auto; }
78
+ .model-controls select { min-width: 0; max-width: 120px; border: 0; background: transparent; color: #64748b; font-size: 11px; }
79
+ .send-button, .stop-button { border: 0; border-radius: 999px; min-width: 34px; height: 34px; background: #2563eb; color: #fff; font-weight: 700; }
80
+ .send-button:disabled { background: #94a3b8; cursor: default; }
81
+ .stop-button { padding: 0 13px; }
82
+ .composer-error { color: #dc2626; font-size: 12px; margin-bottom: 6px; }
83
+ .clarification { margin: 0 0 10px; border: 1px solid #bfdbfe; border-radius: 11px; padding: 10px; background: #eff6ff; }
84
+ .clarification strong { display: block; font-size: 12px; color: #1d4ed8; }
85
+ .clarification p { margin: 6px 0 8px; font-size: 13px; line-height: 1.45; }
86
+ .clarification > button, .clarification-actions button, .clarification-answer button { margin: 0 6px 6px 0; border: 1px solid #93c5fd; border-radius: 7px; padding: 5px 8px; background: #fff; color: #1e3a8a; font-size: 12px; }
87
+ .clarification-answer { display: flex; gap: 6px; }
88
+ .clarification-answer input { min-width: 0; flex: 1; border: 1px solid #93c5fd; border-radius: 7px; padding: 6px 8px; background: #fff; }
89
+ .clarification-actions { margin-top: 3px; }
90
+ .clarification-actions button { border-color: transparent; background: transparent; color: #64748b; }
91
+
92
+ @media (prefers-color-scheme: dark) {
93
+ :root { background: #0f172a; color: #e2e8f0; }
94
+ .shell, .card { background: #111827; }
95
+ .header, .card { border-color: #334155; }
96
+ .field input, .field textarea, .secondary { border-color: #475569; color: #e2e8f0; }
97
+ .muted, .status { color: #94a3b8; }
98
+ .chat-toolbar { border-color: #334155; }
99
+ .session-menu, .composer, .tab-mention-menu { background: #111827; border-color: #475569; }
100
+ .session-menu > input { border-color: #334155; color: #e2e8f0; }
101
+ .session-list button { color: #e2e8f0; }
102
+ .session-list button:hover, .message.user { background: #1e293b; }
103
+ .tab-mention-menu button:hover, .tab-mention-menu button.selected { background: #1e293b; }
104
+ .clarification { border-color: #1e40af; background: #172554; }
105
+ .clarification strong { color: #93c5fd; }
106
+ .clarification > button, .clarification-answer button { border-color: #1d4ed8; background: #1e3a8a; color: #dbeafe; }
107
+ .clarification-answer input { border-color: #1d4ed8; background: #0f172a; color: #e2e8f0; }
108
+ .source-badge { border-color: #1e40af; color: #93c5fd; }
109
+ .composer-tools button { color: #94a3b8; }
110
+ .context-chip { border-color: #1e40af; background: #172554; color: #bfdbfe; }
111
+ .context-chip.stale { border-color: #92400e; background: #451a03; color: #fde68a; }
112
+ .attachment-chip { border-color: #475569; background: #1e293b; color: #cbd5e1; }
113
+ }
@@ -1,4 +1,17 @@
1
- const BROWSER_EXTENSION_PROTOCOL_VERSION = 3;
1
+ import { T as TAB_BINDING_PREFIX, d as captureTabPage, P as PENDING_CONTEXT_KEY, r as readProfile, u as signBrowserBridgeChallenge } from './assets/auth-CknJ00ae.js';
2
+
3
+ const BROWSER_EXTENSION_PROTOCOL_VERSION = 4;
4
+ function browserWireAuthenticationPayload(input) {
5
+ return [
6
+ "xopc-browser-extension-v4",
7
+ input.connectionId,
8
+ input.challenge,
9
+ String(input.issuedAt),
10
+ input.principalId,
11
+ input.extensionId,
12
+ input.extensionVersion
13
+ ].join("\n");
14
+ }
2
15
 
3
16
  const XOPC_EXT_PORT = 19820;
4
17
  const XOPC_EXT_HOST = "127.0.0.1";
@@ -116,16 +129,6 @@ chrome.debugger.onDetach.addListener((source) => {
116
129
  if (source.tabId) attached.delete(source.tabId);
117
130
  });
118
131
 
119
- async function sendContentMessage(tabId, message) {
120
- try {
121
- await chrome.tabs.sendMessage(tabId, message);
122
- } catch {
123
- }
124
- }
125
- async function hideOverlayOnTab(tabId) {
126
- await sendContentMessage(tabId, { type: "content/hide-overlay" });
127
- }
128
-
129
132
  const log$1 = createLogger("SessionManager");
130
133
  const XOPC_TAB_GROUP_TITLE = "xopc";
131
134
  const automationSessions = /* @__PURE__ */ new Map();
@@ -262,9 +265,6 @@ async function closeSession(workspace = DEFAULT_WORKSPACE) {
262
265
  if (!session) return;
263
266
  if (session.idleTimer) clearTimeout(session.idleTimer);
264
267
  const tabs = await chrome.tabs.query({ groupId: session.groupId }).catch(() => []);
265
- for (const t of tabs) {
266
- if (t.id != null) await hideOverlayOnTab(t.id);
267
- }
268
268
  for (const t of tabs) {
269
269
  if (t.id != null) await chrome.tabs.remove(t.id).catch(() => {
270
270
  });
@@ -272,18 +272,58 @@ async function closeSession(workspace = DEFAULT_WORKSPACE) {
272
272
  automationSessions.delete(workspace);
273
273
  log$1.info("Closed automation session", { workspace });
274
274
  }
275
- async function closeAllSessions() {
276
- for (const workspace of [...automationSessions.keys()]) {
277
- await closeSession(workspace);
278
- }
279
- }
280
275
 
281
276
  const observations = /* @__PURE__ */ new Map();
277
+ async function markerDigest(tabId) {
278
+ const marker = await evaluate(tabId, `({ url: location.href, timeOrigin: performance.timeOrigin })`);
279
+ const url = new URL(marker.url);
280
+ url.username = "";
281
+ url.password = "";
282
+ url.hash = "";
283
+ const bytes = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(`${url.toString()}
284
+ ${marker.timeOrigin}`));
285
+ return [...new Uint8Array(bytes)].map((byte) => byte.toString(16).padStart(2, "0")).join("");
286
+ }
287
+ async function resolveAttachedTarget(input) {
288
+ if (input.target?.kind !== "attached_tab") return void 0;
289
+ const key = `${TAB_BINDING_PREFIX}${input.target.bindingId}`;
290
+ const stored = await chrome.storage.session.get(key);
291
+ const binding = stored[key];
292
+ if (!binding || binding.id !== input.target.bindingId || binding.expiresAt <= Date.now()) {
293
+ return fail("SESSION_NOT_FOUND", "The attached tab binding is unavailable or expired.");
294
+ }
295
+ if (input.action === "navigate" || input.action === "tabs" || input.action === "sequence") {
296
+ return fail("INVALID_INPUT", `${input.action} is unavailable for an attached tab.`);
297
+ }
298
+ const needsAct = !["observe", "wait", "close"].includes(input.action);
299
+ if (needsAct && binding.mode !== "act") {
300
+ return fail("APPROVAL_REQUIRED", "This tab is attached in read-only mode. Enable Control tab in the side panel.");
301
+ }
302
+ const tabId = Number(binding.tabId);
303
+ const tab = Number.isInteger(tabId) ? await chrome.tabs.get(tabId).catch(() => null) : null;
304
+ if (!tab || String(tab.windowId) !== binding.windowId || !tab.url) {
305
+ return fail("SESSION_NOT_FOUND", "The attached tab is no longer available.");
306
+ }
307
+ let origin;
308
+ try {
309
+ origin = new URL(tab.url).origin;
310
+ } catch {
311
+ return fail("UNSUPPORTED_PAGE", "The attached tab URL is not supported.");
312
+ }
313
+ if (origin !== binding.urlOrigin) {
314
+ return fail("STALE_OBSERVATION", "The attached tab navigated to another site. Bind it again.");
315
+ }
316
+ if (await markerDigest(tabId) !== binding.documentId) {
317
+ return fail("STALE_OBSERVATION", "The attached tab document changed. Bind it again.");
318
+ }
319
+ return { binding, tabId, stateKey: `attached:${binding.id}` };
320
+ }
282
321
  async function executeBrowserCommand(command) {
283
322
  const startedAt = Date.now();
284
323
  if (command.protocolVersion !== BROWSER_EXTENSION_PROTOCOL_VERSION) {
285
324
  return {
286
325
  id: command.id,
326
+ connectionId: command.connectionId,
287
327
  result: fail(
288
328
  "DRIVER_UNAVAILABLE",
289
329
  `Browser protocol mismatch. Expected ${BROWSER_EXTENSION_PROTOCOL_VERSION}; reload the xopc extension.`
@@ -292,10 +332,11 @@ async function executeBrowserCommand(command) {
292
332
  }
293
333
  try {
294
334
  const result = await execute(command.input, command.timeoutMs, command.visualFallback, startedAt);
295
- return { id: command.id, result };
335
+ return { id: command.id, connectionId: command.connectionId, result };
296
336
  } catch (error) {
297
337
  return {
298
338
  id: command.id,
339
+ connectionId: command.connectionId,
299
340
  result: {
300
341
  ok: false,
301
342
  error: {
@@ -309,31 +350,34 @@ async function executeBrowserCommand(command) {
309
350
  async function execute(input, timeoutMs, visualFallback, startedAt) {
310
351
  const sessionId = input.sessionId;
311
352
  if (!sessionId) return fail("INVALID_INPUT", "Extension actions require a session id.");
353
+ const attached = await resolveAttachedTarget(input);
354
+ if (attached && !("binding" in attached)) return attached;
355
+ const stateKey = attached?.stateKey ?? sessionId;
312
356
  if (input.action === "close") {
313
- await closeSession(sessionId);
314
- observations.delete(sessionId);
357
+ if (!attached) await closeSession(sessionId);
358
+ observations.delete(stateKey);
315
359
  return success("close", "read", startedAt);
316
360
  }
317
361
  if (input.action === "observe") {
318
362
  const mode = input.visual === "always" ? "always" : input.visual === "never" || !visualFallback ? "never" : "auto";
319
- const observation2 = await observe(sessionId, mode);
363
+ const observation2 = await observe(sessionId, mode, attached);
320
364
  return success("observe", "read", startedAt, observation2);
321
365
  }
322
366
  if (input.action === "navigate") {
323
- const tabId2 = await getActiveTabId(sessionId);
367
+ const tabId2 = attached?.tabId ?? await getActiveTabId(sessionId);
324
368
  await chrome.tabs.update(tabId2, { url: input.url });
325
369
  await waitForTabLoad(tabId2, timeoutMs);
326
- resetWindowIdleTimer(sessionId);
327
- const observation2 = await observe(sessionId, visualFallback ? "auto" : "never");
370
+ if (!attached) resetWindowIdleTimer(sessionId);
371
+ const observation2 = await observe(sessionId, visualFallback ? "auto" : "never", attached);
328
372
  return verified(input, observation2, startedAt, "read");
329
373
  }
330
374
  if (input.action === "tabs") return tabs(sessionId, input, startedAt, timeoutMs);
331
375
  if (input.action === "sequence") return fail("INVALID_INPUT", "Sequences are expanded by the browser runtime.");
332
- const current = observations.get(sessionId);
376
+ const current = observations.get(stateKey);
333
377
  if (!current || current.revision !== input.revision) {
334
- return fail("STALE_OBSERVATION", "The page changed. Observe it again.", await observe(sessionId, "never"));
378
+ return fail("STALE_OBSERVATION", "The page changed. Observe it again.", await observe(sessionId, "never", attached));
335
379
  }
336
- const tabId = await getActiveTabId(sessionId);
380
+ const tabId = attached?.tabId ?? await getActiveTabId(sessionId);
337
381
  const ref = "ref" in input ? input.ref : void 0;
338
382
  if (ref && !current.nodes.some((node) => node.ref === ref)) {
339
383
  return fail("TARGET_NOT_FOUND", `Target ${ref} was not found.`);
@@ -388,11 +432,11 @@ async function execute(input, timeoutMs, visualFallback, startedAt) {
388
432
  await sendCommand(tabId, "DOM.setFileInputFiles", { nodeId: query.nodeIds[0], files: input.paths });
389
433
  }
390
434
  await new Promise((resolve) => setTimeout(resolve, 200));
391
- const observation = await observe(sessionId, visualFallback ? "auto" : "never");
435
+ const observation = await observe(sessionId, visualFallback ? "auto" : "never", attached);
392
436
  return verified(input, observation, startedAt, "draft");
393
437
  }
394
- async function observe(sessionId, visual) {
395
- const tabId = await getActiveTabId(sessionId);
438
+ async function observe(sessionId, visual, attached) {
439
+ const tabId = attached?.tabId ?? await getActiveTabId(sessionId);
396
440
  const tab = await chrome.tabs.get(tabId);
397
441
  const raw = await evaluate(tabId, `(() => {
398
442
  const selector = 'a[href],button,input:not([type="hidden"]),textarea,select,summary,[contenteditable="true"],[role="button"],[role="link"],[role="checkbox"],[role="radio"],[role="combobox"],[role="menuitem"],[role="option"],[role="tab"],[tabindex]:not([tabindex="-1"])';
@@ -428,10 +472,16 @@ async function observe(sessionId, visual) {
428
472
  }
429
473
  return { marker: location.href + '|' + performance.timeOrigin, nodes, hasVisualSurface: !!document.querySelector('canvas,embed[type="application/pdf"],object[type="application/pdf"]') };
430
474
  })()`);
431
- const prior = observations.get(sessionId);
432
- const state = prior?.documentMarker === raw.marker ? { ...prior, revision: prior.revision + 1, nodes: raw.nodes } : { documentMarker: raw.marker, documentId: crypto.randomUUID(), revision: 1, nodes: raw.nodes };
475
+ const stateKey = attached?.stateKey ?? sessionId;
476
+ const prior = observations.get(stateKey);
477
+ const state = prior?.documentMarker === raw.marker ? { ...prior, revision: prior.revision + 1, nodes: raw.nodes } : {
478
+ documentMarker: raw.marker,
479
+ documentId: attached && !prior ? attached.binding.documentId : crypto.randomUUID(),
480
+ revision: 1,
481
+ nodes: raw.nodes
482
+ };
433
483
  const changes = diff(prior?.documentMarker === raw.marker ? prior.nodes : [], raw.nodes);
434
- observations.set(sessionId, state);
484
+ observations.set(stateKey, state);
435
485
  const capture = visual === "always" || visual === "auto" && (raw.nodes.length === 0 || raw.hasVisualSurface);
436
486
  const image = capture ? await captureScreenshot(tabId, { format: "jpeg", quality: 70}) : void 0;
437
487
  return {
@@ -541,45 +591,92 @@ let reconnectTimer = null;
541
591
  let keepAliveTimer = null;
542
592
  let reconnectDelay = WS_RECONNECT_BASE_DELAY;
543
593
  let intentionalClose = false;
544
- function connect() {
594
+ let authenticatedConnectionId = null;
595
+ async function authenticate(socket, challenge) {
596
+ if (challenge.protocolVersion !== BROWSER_EXTENSION_PROTOCOL_VERSION) {
597
+ socket.close(4406, "Browser protocol mismatch");
598
+ return;
599
+ }
600
+ const profile = await readProfile();
601
+ if (!profile) {
602
+ socket.close(4401, "Browser is not paired");
603
+ return;
604
+ }
605
+ const extensionVersion = chrome.runtime.getManifest().version;
606
+ const unsigned = {
607
+ connectionId: challenge.connectionId,
608
+ challenge: challenge.challenge,
609
+ issuedAt: challenge.issuedAt,
610
+ principalId: profile.deviceId,
611
+ extensionId: chrome.runtime.id,
612
+ extensionVersion
613
+ };
614
+ const message = {
615
+ type: "authenticate",
616
+ protocolVersion: BROWSER_EXTENSION_PROTOCOL_VERSION,
617
+ connectionId: challenge.connectionId,
618
+ principalId: profile.deviceId,
619
+ extensionId: chrome.runtime.id,
620
+ extensionVersion,
621
+ signature: await signBrowserBridgeChallenge(browserWireAuthenticationPayload(unsigned))
622
+ };
623
+ socket.send(JSON.stringify(message));
624
+ authenticatedConnectionId = challenge.connectionId;
625
+ sendStatus(profile.deviceId);
626
+ startKeepAlive();
627
+ }
628
+ async function connect() {
629
+ if (!await readProfile()) return;
545
630
  if (ws && (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING)) return;
546
631
  intentionalClose = false;
632
+ authenticatedConnectionId = null;
547
633
  const socket = new WebSocket(XOPC_EXT_WS_URL);
548
634
  ws = socket;
549
635
  socket.onopen = () => {
550
- if (ws !== socket) {
551
- socket.close();
552
- return;
553
- }
554
- reconnectDelay = WS_RECONNECT_BASE_DELAY;
555
- sendStatus();
556
- startKeepAlive();
557
- log.info("Connected to xopc daemon");
636
+ if (ws !== socket) socket.close();
558
637
  };
559
638
  socket.onmessage = async (event) => {
560
639
  if (ws !== socket) return;
561
640
  try {
562
- const command = JSON.parse(String(event.data));
563
- const result = await executeBrowserCommand(command);
641
+ const value = JSON.parse(String(event.data));
642
+ if (value.type === "auth_challenge") {
643
+ await authenticate(socket, value);
644
+ reconnectDelay = WS_RECONNECT_BASE_DELAY;
645
+ log.info("Authenticated with xopc browser bridge");
646
+ return;
647
+ }
648
+ if (!authenticatedConnectionId || value.connectionId !== authenticatedConnectionId) {
649
+ throw new Error("Browser command arrived before authentication or for a stale connection");
650
+ }
651
+ const result = await executeBrowserCommand(value);
564
652
  if (socket.readyState === WebSocket.OPEN) socket.send(JSON.stringify(result));
565
653
  } catch (error) {
566
- log.error("Failed to process browser command", { error: error instanceof Error ? error.message : String(error) });
654
+ log.error("Failed to process browser bridge message", {
655
+ error: error instanceof Error ? error.message : String(error)
656
+ });
567
657
  }
568
658
  };
569
659
  socket.onclose = (event) => {
570
660
  if (ws !== socket) return;
571
661
  stopKeepAlive();
572
662
  ws = null;
573
- log.warn("Disconnected from xopc daemon", { code: event.code, reason: event.reason, wasClean: event.wasClean });
574
- if (!intentionalClose) scheduleReconnect();
663
+ authenticatedConnectionId = null;
664
+ log.warn("Disconnected from xopc browser bridge", {
665
+ code: event.code,
666
+ reason: event.reason,
667
+ wasClean: event.wasClean
668
+ });
669
+ if (!intentionalClose && event.code !== 4401) scheduleReconnect();
575
670
  };
576
671
  }
577
- function sendStatus() {
578
- if (ws?.readyState !== WebSocket.OPEN) return;
672
+ function sendStatus(principalId) {
673
+ if (ws?.readyState !== WebSocket.OPEN || !authenticatedConnectionId) return;
579
674
  const status = {
580
675
  type: "status",
581
676
  protocolVersion: BROWSER_EXTENSION_PROTOCOL_VERSION,
582
677
  extensionVersion: chrome.runtime.getManifest().version,
678
+ connectionId: authenticatedConnectionId,
679
+ principalId,
583
680
  connected: true,
584
681
  sessionCount: automationSessions.size
585
682
  };
@@ -588,11 +685,15 @@ function sendStatus() {
588
685
  function startKeepAlive() {
589
686
  stopKeepAlive();
590
687
  keepAliveTimer = setInterval(() => {
591
- if (ws?.readyState !== WebSocket.OPEN) {
688
+ if (ws?.readyState !== WebSocket.OPEN || !authenticatedConnectionId) {
592
689
  stopKeepAlive();
593
690
  return;
594
691
  }
595
- const message = { type: "keepalive", timestamp: Date.now() };
692
+ const message = {
693
+ type: "keepalive",
694
+ connectionId: authenticatedConnectionId,
695
+ timestamp: Date.now()
696
+ };
596
697
  ws.send(JSON.stringify(message));
597
698
  }, WS_KEEPALIVE_INTERVAL);
598
699
  }
@@ -605,7 +706,7 @@ function scheduleReconnect() {
605
706
  reconnectTimer = setTimeout(() => {
606
707
  reconnectTimer = null;
607
708
  reconnectDelay = Math.min(reconnectDelay * 1.5, WS_RECONNECT_MAX_DELAY);
608
- connect();
709
+ void connect();
609
710
  }, reconnectDelay);
610
711
  }
611
712
  function disconnect() {
@@ -615,26 +716,57 @@ function disconnect() {
615
716
  reconnectTimer = null;
616
717
  ws?.close();
617
718
  ws = null;
719
+ authenticatedConnectionId = null;
618
720
  }
619
721
  chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => {
620
- if (message.type === "popup/get-status") sendResponse({ connected: ws?.readyState === WebSocket.OPEN, url: XOPC_EXT_WS_URL });
621
- else if (message.type === "popup/connect") {
622
- connect();
623
- sendResponse({ ok: true });
624
- } else if (message.type === "popup/disconnect") {
722
+ if (message.type === "browser/get-status") {
723
+ sendResponse({ connected: Boolean(authenticatedConnectionId), url: XOPC_EXT_WS_URL });
724
+ } else if (message.type === "browser/reconnect") {
625
725
  disconnect();
726
+ intentionalClose = false;
727
+ void connect();
626
728
  sendResponse({ ok: true });
627
- } else sendResponse({ ok: false, error: `Unknown message: ${message.type}` });
729
+ } else {
730
+ sendResponse({ ok: false, error: `Unknown message: ${message.type}` });
731
+ }
628
732
  return true;
629
733
  });
630
- chrome.runtime.onInstalled.addListener(connect);
631
- chrome.runtime.onStartup.addListener(connect);
734
+ chrome.contextMenus.onClicked.addListener((info, tab) => {
735
+ if (info.menuItemId !== "xopc-ask-selection" || !tab?.id) return;
736
+ const tabId = tab.id;
737
+ void captureTabPage(tabId, "selection").then(async (context) => {
738
+ await chrome.storage.session.set({
739
+ [PENDING_CONTEXT_KEY]: { context, tabId, source: "current_selection" }
740
+ });
741
+ await chrome.sidePanel.open({ tabId });
742
+ }).catch((error) => log.warn("Could not attach selected page text", {
743
+ error: error instanceof Error ? error.message : String(error)
744
+ }));
745
+ });
746
+ chrome.runtime.onInstalled.addListener(() => {
747
+ void chrome.sidePanel.setPanelBehavior({ openPanelOnActionClick: true });
748
+ chrome.contextMenus.removeAll(() => {
749
+ chrome.contextMenus.create({
750
+ id: "xopc-ask-selection",
751
+ title: "Ask xopc about “%s”",
752
+ contexts: ["selection"]
753
+ });
754
+ });
755
+ void connect();
756
+ });
757
+ chrome.runtime.onStartup.addListener(() => {
758
+ void connect();
759
+ });
760
+ chrome.storage.onChanged.addListener((changes, area) => {
761
+ if (area === "local" && changes["xopc.browser.profile"]) {
762
+ disconnect();
763
+ intentionalClose = false;
764
+ void connect();
765
+ }
766
+ });
632
767
  chrome.alarms.onAlarm.addListener((alarm) => {
633
- if (alarm.name === WS_WATCHDOG_ALARM && !intentionalClose) connect();
768
+ if (alarm.name === WS_WATCHDOG_ALARM && !intentionalClose) void connect();
634
769
  });
635
770
  void chrome.alarms.create(WS_WATCHDOG_ALARM, { periodInMinutes: WS_WATCHDOG_PERIOD_MINUTES });
636
- connect();
637
- self.addEventListener("beforeunload", () => {
638
- disconnect();
639
- void closeAllSessions();
640
- });
771
+ void chrome.sidePanel.setPanelBehavior({ openPanelOnActionClick: true });
772
+ void connect();
@@ -0,0 +1,14 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>xopc</title>
7
+ <script type="module" crossorigin src="/sidepanel.js"></script>
8
+ <link rel="modulepreload" crossorigin href="/assets/auth-CknJ00ae.js">
9
+ <link rel="stylesheet" crossorigin href="/assets/sidepanel-C69P3v9u.css">
10
+ </head>
11
+ <body>
12
+ <div id="root"></div>
13
+ </body>
14
+ </html>