@xopcai/xopc 0.0.272 → 0.0.274

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 (151) hide show
  1. package/README.md +1 -5
  2. package/README.zh-CN.md +1 -5
  3. package/dist/browser-ext/dist/assets/{auth-C1QI-uMG.js → auth-O8RiOF3l.js} +265 -100
  4. package/dist/browser-ext/dist/background.js +1 -22
  5. package/dist/browser-ext/dist/sidepanel.html +1 -1
  6. package/dist/browser-ext/dist/sidepanel.js +68 -36
  7. package/dist/browser-ext/manifest.json +1 -12
  8. package/dist/extensions/telegram/xopc.extension.json +1 -1
  9. package/dist/gateway/static/root/assets/{activity-store-DffqjeqQ.js → activity-store-SJZ5fS17.js} +1 -1
  10. package/dist/gateway/static/root/assets/{agent-avatar-dicebear-Cwj4HCYY.js → agent-avatar-dicebear-BckaANxj.js} +1 -1
  11. package/dist/gateway/static/root/assets/{agent-defaults-settings-panel-6CLfLxVc.js → agent-defaults-settings-panel-B8eyC6K1.js} +1 -1
  12. package/dist/gateway/static/root/assets/agents-CSWj_yo1.js +1 -0
  13. package/dist/gateway/static/root/assets/{api-CGLdye4i.js → api-CnJAVHBu.js} +1 -1
  14. package/dist/gateway/static/root/assets/app-link-BiBXVEvp.js +1 -0
  15. package/dist/gateway/static/root/assets/{app-management-settings-panel-BLujSbaw.js → app-management-settings-panel-DY-Zbgmt.js} +1 -1
  16. package/dist/gateway/static/root/assets/{appearance-settings-BXMcueb8.js → appearance-settings-CoJR7ZRl.js} +1 -1
  17. package/dist/gateway/static/root/assets/{apps-page-BEgy4hY3.js → apps-page-CSMvkuot.js} +1 -1
  18. package/dist/gateway/static/root/assets/{archive-plugin-D4bUampf.js → archive-plugin-BwAlRT53.js} +1 -1
  19. package/dist/gateway/static/root/assets/attachment-load-R835uxgL.js +1 -0
  20. package/dist/gateway/static/root/assets/{attachment-preview-renderer-BDTD8YUe.js → attachment-preview-renderer-CfEDFYPE.js} +1 -1
  21. package/dist/gateway/static/root/assets/attachment-process-heavy-C5CqLCsX.js +5 -0
  22. package/dist/gateway/static/root/assets/{automations-page-dQyolNmy.js → automations-page-BEZy0P_u.js} +1 -1
  23. package/dist/gateway/static/root/assets/automations-workspace-CpaI13J7.js +6 -0
  24. package/dist/gateway/static/root/assets/{binary-plugins-bbvp81h2.js → binary-plugins-CESFx3yb.js} +2 -2
  25. package/dist/gateway/static/root/assets/{block-editor-C8e6Wri4.js → block-editor-DDJcf2wR.js} +3 -3
  26. package/dist/gateway/static/root/assets/{browser-automation-inputs-oJWJtQa6.js → browser-automation-inputs-DGCqIPXT.js} +1 -1
  27. package/dist/gateway/static/root/assets/{browser-automations-page-CAcJQSjN.js → browser-automations-page-BH4lSfr5.js} +2 -2
  28. package/dist/gateway/static/root/assets/browser-settings-page-CbTooPd2.js +8 -0
  29. package/dist/gateway/static/root/assets/capabilities-settings-panel-C4LZWHhv.js +8 -0
  30. package/dist/gateway/static/root/assets/channels-settings-BAljmSa7.js +1 -0
  31. package/dist/gateway/static/root/assets/{channels-status-swr-CfOPNnVr.js → channels-status-swr-BTT2sIGg.js} +1 -1
  32. package/dist/gateway/static/root/assets/{chat-terminal-dock-9ZOl92kh.js → chat-terminal-dock-D8U97O95.js} +2 -2
  33. package/dist/gateway/static/root/assets/connectors-page-CVRMjNNr.js +2 -0
  34. package/dist/gateway/static/root/assets/{date-picker-Dj6FRLF9.js → date-picker-7sDSMswL.js} +1 -1
  35. package/dist/gateway/static/root/assets/{dependency-picker-D1q89tVc.js → dependency-picker-DMIC86ZW.js} +1 -1
  36. package/dist/gateway/static/root/assets/{desktop-pet-DfqZBvhp.js → desktop-pet-BtdiY5jK.js} +1 -1
  37. package/dist/gateway/static/root/assets/{desktop-pet-settings-HCEh2MOm.js → desktop-pet-settings-D_B8azDH.js} +1 -1
  38. package/dist/gateway/static/root/assets/device-pairing-wizard-Dmh3Nh4d.js +1 -0
  39. package/dist/gateway/static/root/assets/excel-worksheet-utils-DpdB-DjU.js +1 -0
  40. package/dist/gateway/static/root/assets/{extension-debug-page-C8cAWVoK.js → extension-debug-page-CoFKb01d.js} +1 -1
  41. package/dist/gateway/static/root/assets/{extension-page-5N87Vaym.js → extension-page-CEkNsCAY.js} +1 -1
  42. package/dist/gateway/static/root/assets/{extension-settings-page-D85SAUFP.js → extension-settings-page-Bi2R8P5j.js} +1 -1
  43. package/dist/gateway/static/root/assets/{gateway-config-swr-BjsDnB_n.js → gateway-config-swr-cd8owslz.js} +1 -1
  44. package/dist/gateway/static/root/assets/gateway-settings-q9p3sduo.js +1 -0
  45. package/dist/gateway/static/root/assets/{gateway-startup-retry-BIf0eLWX.js → gateway-startup-retry-CVTa5HSS.js} +1 -1
  46. package/dist/gateway/static/root/assets/{heartbeat-settings-BpDWGE1a.js → heartbeat-settings-DQtelFiU.js} +1 -1
  47. package/dist/gateway/static/root/assets/home-page-COpGnTFZ.js +1 -0
  48. package/dist/gateway/static/root/assets/{image-generation-api-1m8xTVS_.js → image-generation-api-DtMRG1oF.js} +1 -1
  49. package/dist/gateway/static/root/assets/{index-C6PmD0cK.js → index-SxqsTD4i.js} +35 -35
  50. package/dist/gateway/static/root/assets/{keyboard-shortcuts-settings-Byy8mCSq.js → keyboard-shortcuts-settings-BuYmZS1K.js} +1 -1
  51. package/dist/gateway/static/root/assets/local-app-workbench-page-DV_17Zxc.js +2 -0
  52. package/dist/gateway/static/root/assets/{local-apps-page-BPo0wBgM.js → local-apps-page-CExnZyGZ.js} +1 -1
  53. package/dist/gateway/static/root/assets/{locale-store-CRNble2Z.js → locale-store-BkJ7g35d.js} +1 -1
  54. package/dist/gateway/static/root/assets/{logs-page-X5FdLlXK.js → logs-page-CA5ArMoN.js} +2 -2
  55. package/dist/gateway/static/root/assets/management-settings-1UiJPrkX.js +1 -0
  56. package/dist/gateway/static/root/assets/markdown-view-B2IR4E2r.js +201 -0
  57. package/dist/gateway/static/root/assets/{media-plugins-Bzka9qXu.js → media-plugins-nNFOk9HL.js} +1 -1
  58. package/dist/gateway/static/root/assets/{messages-DH_HCgmw.js → messages-BGc-OJWg.js} +2 -2
  59. package/dist/gateway/static/root/assets/note-detail-panel-B5OQ0fBY.js +5 -0
  60. package/dist/gateway/static/root/assets/notes-page-CVPPMVDl.js +8 -0
  61. package/dist/gateway/static/root/assets/{page-tabs-Ys0p65Lt.js → page-tabs-CRombigq.js} +1 -1
  62. package/dist/gateway/static/root/assets/{preview-open-alternatives-iknB5xEp.js → preview-open-alternatives-Bcuj43lk.js} +1 -1
  63. package/dist/gateway/static/root/assets/{proactive-card-BPZDgNNR.js → proactive-card-Cb3Xeoyx.js} +1 -1
  64. package/dist/gateway/static/root/assets/{proactive-page-CxdZR-p2.js → proactive-page-TbF9KVpm.js} +1 -1
  65. package/dist/gateway/static/root/assets/{product-open-page-W1o3f-kz.js → product-open-page-DsQcgZkI.js} +1 -1
  66. package/dist/gateway/static/root/assets/project-detail-page-B9OQ2l9x.js +1 -0
  67. package/dist/gateway/static/root/assets/projects-page-DCqSTNkY.js +1 -0
  68. package/dist/gateway/static/root/assets/{registry-api-DmYFpjsH.js → registry-api-CTq8EECj.js} +1 -1
  69. package/dist/gateway/static/root/assets/remote-access-hub-d9Fg70gX.js +1 -0
  70. package/dist/gateway/static/root/assets/{runtime-tools-settings-panel-DP1XyYO8.js → runtime-tools-settings-panel-BudlvBu-.js} +2 -2
  71. package/dist/gateway/static/root/assets/{schema-form-BMYKdzsH.js → schema-form-Ch3MEX4h.js} +1 -1
  72. package/dist/gateway/static/root/assets/sessions-page-D8RiQ-ag.js +1 -0
  73. package/dist/gateway/static/root/assets/settings-advanced-gate-DVH_l6Hr.js +1 -0
  74. package/dist/gateway/static/root/assets/{settings-form-section-2LGjz7bu.js → settings-form-section-CIA1AA-A.js} +1 -1
  75. package/dist/gateway/static/root/assets/{settings-page-7DjpC9_C.js → settings-page-B0Khoduh.js} +1 -1
  76. package/dist/gateway/static/root/assets/{setup-status-panel-Dj9jFWJt.js → setup-status-panel-COJFl4N_.js} +1 -1
  77. package/dist/gateway/static/root/assets/share-preview-page-Cj9XWg-X.js +2 -0
  78. package/dist/gateway/static/root/assets/shares-settings-_OuE6A0G.js +1 -0
  79. package/dist/gateway/static/root/assets/skill-reload-api-BM4Ezjbx.js +1 -0
  80. package/dist/gateway/static/root/assets/{skills-page-DNEScMZH.js → skills-page-Dyh1kf9t.js} +1 -1
  81. package/dist/gateway/static/root/assets/{skills-page.utils-q88l65YU.js → skills-page.utils-DsQZkMti.js} +1 -1
  82. package/dist/gateway/static/root/assets/{system-settings-panel-8NIssGKj.js → system-settings-panel-BSho_Tpk.js} +1 -1
  83. package/dist/gateway/static/root/assets/task-detail-page-B5HWM12k.js +1 -0
  84. package/dist/gateway/static/root/assets/{theme-store-BXZG3S25.js → theme-store-mV6Cm2vj.js} +1 -1
  85. package/dist/gateway/static/root/assets/{tunnel-api-CWsRPM84.js → tunnel-api-4DUHxSQG.js} +1 -1
  86. package/dist/gateway/static/root/assets/{use-autosave-BwQuoTQU.js → use-autosave-BG8Yx7hj.js} +1 -1
  87. package/dist/gateway/static/root/assets/user-model-page-IjVyv5jj.js +2 -0
  88. package/dist/gateway/static/root/assets/{voice-api-key-field-Bd03Ic0s.js → voice-api-key-field-DYXwDhHq.js} +1 -1
  89. package/dist/gateway/static/root/assets/{voice-config-api-gggUp3u9.js → voice-config-api-D3DdcH4H.js} +1 -1
  90. package/dist/gateway/static/root/assets/{work-discovery-overlay-DF8PR_pK.js → work-discovery-overlay-CQqjzaRo.js} +1 -1
  91. package/dist/gateway/static/root/assets/{workflow-run-setup-panel-BiAAWlir.js → workflow-run-setup-panel-C5H-Fd-U.js} +2 -2
  92. package/dist/gateway/static/root/assets/workflows-page-D1hMyPsY.js +6 -0
  93. package/dist/gateway/static/root/index.html +10 -9
  94. package/dist/package.js +1 -1
  95. package/dist/packages/gateway-contract/src/device-pairing.js +41 -2
  96. package/dist/src/agent/service.js +20 -16
  97. package/dist/src/browser/native-messaging-host.d.ts +1 -1
  98. package/dist/src/browser/native-messaging-host.js +3 -2
  99. package/dist/src/browser/providers/browser-ext-install.d.ts +6 -0
  100. package/dist/src/browser/providers/browser-ext-install.js +16 -1
  101. package/dist/src/cli/commands/gateway/run-foreground.js +8 -5
  102. package/dist/src/cli/commands/gateway/service.js +4 -3
  103. package/dist/src/cli/commands/gateway/token.js +28 -1
  104. package/dist/src/gateway/device-routes.js +4 -3
  105. package/dist/src/gateway/hono/app.js +5 -2
  106. package/dist/src/gateway/hono/routes/agent-stream.js +1 -1
  107. package/dist/src/gateway/hono/routes/browser.js +26 -0
  108. package/dist/src/gateway/hono/routes/device-pairing-approval.d.ts +9 -2
  109. package/dist/src/gateway/hono/routes/device-pairing-approval.js +50 -10
  110. package/dist/src/gateway/hono/routes/devices.d.ts +2 -1
  111. package/dist/src/gateway/hono/routes/devices.js +33 -16
  112. package/dist/src/gateway/hono/routes/session-input-handler.d.ts +1 -1
  113. package/dist/src/gateway/hono/routes/session-input-handler.js +1 -1
  114. package/dist/src/gateway/hono/routes/side-chats.js +1 -1
  115. package/dist/src/gateway/hono/routes/tunnel.js +34 -2
  116. package/dist/src/gateway/rate-limit/buckets.js +2 -1
  117. package/dist/src/gateway/service/run-gateway-agent.js +1 -1
  118. package/dist/src/storage/sqlite/device-pairing-approval.js +4 -4
  119. package/dist/src/tui/tui.js +1 -1
  120. package/dist/src/tunnel/xopc-cloud-registration.d.ts +5 -0
  121. package/dist/src/tunnel/xopc-cloud-registration.js +17 -11
  122. package/package.json +3 -3
  123. package/dist/browser-ext/dist/pairing-handoff.js +0 -51
  124. package/dist/gateway/static/root/assets/agents-B6mu64fY.js +0 -1
  125. package/dist/gateway/static/root/assets/attachment-load-CBGNA2Ys.js +0 -2
  126. package/dist/gateway/static/root/assets/attachment-process-heavy-B2_kr5EA.js +0 -11
  127. package/dist/gateway/static/root/assets/automations-workspace-DxRFrmrV.js +0 -6
  128. package/dist/gateway/static/root/assets/browser-settings-page-TLAlqcPo.js +0 -8
  129. package/dist/gateway/static/root/assets/capabilities-settings-panel-z-K_jrZz.js +0 -8
  130. package/dist/gateway/static/root/assets/channels-settings-Dzrk33aB.js +0 -1
  131. package/dist/gateway/static/root/assets/connectors-page-B1dIoRNi.js +0 -2
  132. package/dist/gateway/static/root/assets/device-pairing-wizard-CPth92s6.js +0 -1
  133. package/dist/gateway/static/root/assets/excel-worksheet-utils-EyvwNT4X.js +0 -1
  134. package/dist/gateway/static/root/assets/gateway-settings-Dqx8PmcT.js +0 -1
  135. package/dist/gateway/static/root/assets/home-page-DJaqpC8-.js +0 -1
  136. package/dist/gateway/static/root/assets/local-app-workbench-page-l2AEiG2C.js +0 -2
  137. package/dist/gateway/static/root/assets/management-settings-DT6NoIjm.js +0 -1
  138. package/dist/gateway/static/root/assets/markdown-view-AglreSOj.js +0 -201
  139. package/dist/gateway/static/root/assets/note-detail-panel-BUJPoaGZ.js +0 -5
  140. package/dist/gateway/static/root/assets/notes-page-BwFpWRuo.js +0 -8
  141. package/dist/gateway/static/root/assets/project-detail-page-BU1RKAcy.js +0 -1
  142. package/dist/gateway/static/root/assets/projects-page-z0X0tL8G.js +0 -1
  143. package/dist/gateway/static/root/assets/remote-access-hub-NNTut2I8.js +0 -1
  144. package/dist/gateway/static/root/assets/sessions-page-B6SsHlz5.js +0 -1
  145. package/dist/gateway/static/root/assets/settings-advanced-gate-BQu8bAcN.js +0 -1
  146. package/dist/gateway/static/root/assets/share-preview-page-BxYdSi8c.js +0 -2
  147. package/dist/gateway/static/root/assets/shares-settings-CMqE0KuK.js +0 -1
  148. package/dist/gateway/static/root/assets/skill-reload-api-BXqSJymN.js +0 -1
  149. package/dist/gateway/static/root/assets/task-detail-page-L6dp9v2y.js +0 -1
  150. package/dist/gateway/static/root/assets/user-model-page-D4BucMrZ.js +0 -2
  151. package/dist/gateway/static/root/assets/workflows-page-DryKUMfA.js +0 -6
package/README.md CHANGED
@@ -28,14 +28,10 @@
28
28
 
29
29
  <p align="center">
30
30
  <a href="https://xopcai.github.io/xopc/xopc-desktop.mp4">
31
- <img src="docs/public/xopc-desktop-preview.gif" alt="xopc desktop onboarding demo" width="960">
31
+ <img src="docs/public/xopc-desktop-preview.gif" alt="xopc desktop app demo" width="960">
32
32
  </a>
33
33
  </p>
34
34
 
35
- <p align="center">
36
- <a href="https://xopcai.github.io/xopc/xopc-desktop.mp4"><strong>Watch the full onboarding video (1:02) →</strong></a>
37
- </p>
38
-
39
35
  > After setup, try: **“One thing I want to move forward this week is ____. Help me find the smallest credible next step.”**
40
36
 
41
37
  <details>
package/README.zh-CN.md CHANGED
@@ -28,14 +28,10 @@
28
28
 
29
29
  <p align="center">
30
30
  <a href="https://xopcai.github.io/xopc/xopc-desktop.mp4">
31
- <img src="docs/public/xopc-desktop-preview.gif" alt="xopc 桌面端 onboarding 演示" width="960">
31
+ <img src="docs/public/xopc-desktop-preview.gif" alt="xopc 桌面应用演示" width="960">
32
32
  </a>
33
33
  </p>
34
34
 
35
- <p align="center">
36
- <a href="https://xopcai.github.io/xopc/xopc-desktop.mp4"><strong>观看完整 onboarding 视频(1:02)→</strong></a>
37
- </p>
38
-
39
35
  > 安装后可以直接告诉它:**“这周我最想推进的一件事是 ____。帮我找到最小但可信的下一步。”**
40
36
 
41
37
  <details>
@@ -5655,7 +5655,34 @@ class RealtimeClient {
5655
5655
  }
5656
5656
  }
5657
5657
 
5658
- _enum(["mobile", "browser"]);
5658
+ const DEVICE_PAIRING_VERSION = 3;
5659
+ const BROWSER_PAIRING_INVITATION_VERSION = 1;
5660
+ const BROWSER_PAIRING_INVITATION_PREFIX = `XOPC-BROWSER-INVITE-V${BROWSER_PAIRING_INVITATION_VERSION}:`;
5661
+ const devicePairingTargetKindSchema = _enum(["mobile", "browser"]);
5662
+ const devicePairingRouteSchema = strictObject({
5663
+ id: string().trim().min(1).max(80),
5664
+ kind: _enum(["xopc-secure-link", "tailscale", "custom-https", "local-browser"]),
5665
+ url: string().url()
5666
+ }).superRefine((route, context) => {
5667
+ const url = new URL(route.url);
5668
+ const isOrigin = !url.username && !url.password && url.pathname === "/" && !url.search && !url.hash;
5669
+ const valid = route.kind === "local-browser" ? url.protocol === "http:" && url.hostname === "127.0.0.1" && isOrigin : url.protocol === "https:" && isOrigin;
5670
+ if (!valid) context.addIssue({ code: "custom", path: ["url"], message: "Pairing route must be a secure origin" });
5671
+ });
5672
+ const devicePairingInvitationPayloadSchema = strictObject({
5673
+ version: literal(DEVICE_PAIRING_VERSION),
5674
+ targetKind: devicePairingTargetKindSchema,
5675
+ pairingToken: string().regex(/^xopc_pair_[0-9a-f-]{36}_[A-Za-z0-9_-]{43}$/i),
5676
+ gatewayId: string().uuid(),
5677
+ gatewayName: string().trim().min(1).max(120),
5678
+ gatewayPublicKey: string().regex(/^[A-Za-z0-9_-]{43}$/),
5679
+ routes: array(devicePairingRouteSchema).min(1).max(8),
5680
+ expiresAt: number().int().positive()
5681
+ }).superRefine((payload, context) => {
5682
+ if (payload.targetKind === "mobile" && payload.routes.some((route) => route.kind === "local-browser")) {
5683
+ context.addIssue({ code: "custom", path: ["routes"], message: "Mobile invitations require remote routes" });
5684
+ }
5685
+ });
5659
5686
  const devicePairingKeySchema = strictObject({
5660
5687
  kty: literal("EC"),
5661
5688
  crv: literal("P-256"),
@@ -5691,6 +5718,18 @@ devicePairingProofSchema.extend({
5691
5718
  idempotencyKey: string().uuid(),
5692
5719
  initialRefreshToken: initialDeviceRefreshTokenSchema
5693
5720
  });
5721
+ function isRetryableDevicePairingHttpStatus(status) {
5722
+ return status >= 500 || [404, 408, 425, 429].includes(status);
5723
+ }
5724
+ function readBrowserPairingInvitation(value) {
5725
+ const invitation = value.trim();
5726
+ if (!invitation.startsWith(BROWSER_PAIRING_INVITATION_PREFIX)) {
5727
+ throw new Error("Browser pairing invitation is invalid");
5728
+ }
5729
+ const encodedPayload = invitation.slice(BROWSER_PAIRING_INVITATION_PREFIX.length);
5730
+ if (!/^[A-Za-z0-9_-]+$/.test(encodedPayload)) throw new Error("Browser pairing invitation is invalid");
5731
+ return encodedPayload;
5732
+ }
5694
5733
  function canonical(value) {
5695
5734
  if (Array.isArray(value)) return `[${value.map(canonical).join(",")}]`;
5696
5735
  if (value !== null && typeof value === "object") {
@@ -5977,12 +6016,13 @@ async function clearBrowserOutboxes() {
5977
6016
  }
5978
6017
 
5979
6018
  const PROFILE_KEY = "xopc.browser.profile";
5980
- const PENDING_PAIRING_LINK_KEY = "xopc.browser.pending-pairing-link";
6019
+ const PAIRING_JOURNAL_KEY = "xopc.browser.pairing";
5981
6020
  const AUTO_CONNECT_KEY = "xopc.browser.auto-connect";
5982
6021
  const KEY_DATABASE = "xopc-browser-identity";
5983
6022
  const KEY_STORE = "identity";
5984
6023
  const KEY_NAME = "device-key";
5985
6024
  const NATIVE_HOST_NAME = "ai.xopc.browser";
6025
+ const GATEWAY_REQUEST_TIMEOUT_MS = 8e3;
5986
6026
  let refreshTask;
5987
6027
  async function browserPublicKeyThumbprint(jwk) {
5988
6028
  const canonical = JSON.stringify({ crv: jwk.crv, kty: jwk.kty, x: jwk.x, y: jwk.y });
@@ -6014,13 +6054,13 @@ async function discoverLocalGateway(options) {
6014
6054
  nonce
6015
6055
  }
6016
6056
  );
6017
- if (response.ok !== true || typeof response.gatewayUrl !== "string" || typeof response.pairingLink !== "string" || typeof response.expiresAt !== "number") {
6057
+ if (response.ok !== true || typeof response.gatewayUrl !== "string" || typeof response.invitation !== "string" || typeof response.expiresAt !== "number") {
6018
6058
  if (options?.force) throw new Error(response.error || "The local xopc enrollment host returned an invalid response");
6019
6059
  return void 0;
6020
6060
  }
6021
6061
  return {
6022
6062
  gatewayUrl: response.gatewayUrl,
6023
- pairingLink: response.pairingLink,
6063
+ invitation: response.invitation,
6024
6064
  expiresAt: response.expiresAt
6025
6065
  };
6026
6066
  } catch (cause) {
@@ -6049,34 +6089,14 @@ function randomNonce(bytes = 24) {
6049
6089
  function createRefreshToken() {
6050
6090
  return `xopc_rt_${crypto.randomUUID()}_${randomNonce(32)}`;
6051
6091
  }
6052
- function parseBrowserPairingLink(value) {
6053
- const url = new URL(value.trim());
6054
- if (url.protocol !== "https:" || url.hostname !== "link.xopc.ai" || url.pathname !== "/connect") {
6055
- throw new Error("Pairing link is not from xopc");
6056
- }
6057
- const encoded = new URLSearchParams(url.hash.slice(1)).get("p");
6058
- if (!encoded) throw new Error("Pairing link is invalid");
6059
- const payload = decodeJson(encoded);
6060
- if (payload.version !== 3 || payload.targetKind !== "browser" || !payload.gatewayId || !payload.pairingToken || !payload.gatewayPublicKey || !Array.isArray(payload.routes)) {
6061
- throw new Error("Pairing link version is not supported");
6062
- }
6063
- if (payload.expiresAt <= Date.now()) throw new Error("Pairing link has expired");
6064
- if (!payload.routes.length) throw new Error("Pairing link has no Gateway route");
6065
- for (const route of payload.routes) {
6066
- const routeUrl = new URL(route.url);
6067
- if (!route.id || !["http:", "https:"].includes(routeUrl.protocol)) {
6068
- throw new Error("Pairing link has an invalid Gateway route");
6069
- }
6070
- }
6092
+ function parseBrowserPairingInvitation(value, allowExpired = false) {
6093
+ const encoded = readBrowserPairingInvitation(value);
6094
+ const parsed = devicePairingInvitationPayloadSchema.safeParse(decodeJson(encoded));
6095
+ if (!parsed.success || parsed.data.targetKind !== "browser") throw new Error("Pairing invitation version is not supported");
6096
+ const payload = parsed.data;
6097
+ if (!allowExpired && payload.expiresAt <= Date.now()) throw new Error("Pairing invitation has expired");
6071
6098
  return payload;
6072
6099
  }
6073
- async function takePendingPairingLink() {
6074
- const stored = await chrome.storage.session.get(PENDING_PAIRING_LINK_KEY);
6075
- const link = stored[PENDING_PAIRING_LINK_KEY];
6076
- if (typeof link !== "string") return void 0;
6077
- await chrome.storage.session.remove(PENDING_PAIRING_LINK_KEY);
6078
- return link;
6079
- }
6080
6100
  function openIdentityDatabase() {
6081
6101
  return new Promise((resolve, reject) => {
6082
6102
  const request = indexedDB.open(KEY_DATABASE, 1);
@@ -6153,13 +6173,18 @@ async function verifyGateway(publicKey, payload, signature) {
6153
6173
  return false;
6154
6174
  }
6155
6175
  }
6156
- async function requestGatewayOriginPermission(origin) {
6176
+ function requestGatewayOriginPermissions(origins) {
6177
+ const patterns = origins.filter((origin) => {
6178
+ const url = new URL(origin);
6179
+ return !(url.protocol === "http:" && ["127.0.0.1", "localhost"].includes(url.hostname));
6180
+ }).map((origin) => `${new URL(origin).origin}/*`);
6181
+ if (!patterns.length) return Promise.resolve(true);
6182
+ return chrome.permissions.request({ origins: patterns });
6183
+ }
6184
+ async function hasGatewayOriginPermission(origin) {
6157
6185
  const url = new URL(origin);
6158
- const pattern = `${url.origin}/*`;
6159
- if (await chrome.permissions.contains({ origins: [pattern] })) return;
6160
- if (!await chrome.permissions.request({ origins: [pattern] })) {
6161
- throw new Error("Gateway site permission was not granted");
6162
- }
6186
+ if (url.protocol === "http:" && ["127.0.0.1", "localhost"].includes(url.hostname)) return true;
6187
+ return chrome.permissions.contains({ origins: [`${url.origin}/*`] });
6163
6188
  }
6164
6189
  class GatewayHttpError extends Error {
6165
6190
  constructor(message, status) {
@@ -6170,11 +6195,22 @@ class GatewayHttpError extends Error {
6170
6195
  status;
6171
6196
  }
6172
6197
  async function post(origin, path, body) {
6173
- const response = await fetch(`${origin}${path}`, {
6174
- method: "POST",
6175
- headers: { "Content-Type": "application/json" },
6176
- body: JSON.stringify(body)
6177
- });
6198
+ const controller = new AbortController();
6199
+ const timeout = setTimeout(() => controller.abort(), GATEWAY_REQUEST_TIMEOUT_MS);
6200
+ let response;
6201
+ try {
6202
+ response = await fetch(`${origin}${path}`, {
6203
+ method: "POST",
6204
+ headers: { "Content-Type": "application/json" },
6205
+ body: JSON.stringify(body),
6206
+ signal: controller.signal
6207
+ });
6208
+ } catch (cause) {
6209
+ if (controller.signal.aborted) throw new Error(`Gateway did not respond within ${GATEWAY_REQUEST_TIMEOUT_MS / 1e3} seconds`);
6210
+ throw cause;
6211
+ } finally {
6212
+ clearTimeout(timeout);
6213
+ }
6178
6214
  const json = await response.json().catch(() => ({}));
6179
6215
  if (!response.ok) {
6180
6216
  throw new GatewayHttpError(
@@ -6217,9 +6253,31 @@ async function signedPairingRequest(pair, payload, origin, requestId, action, co
6217
6253
  }
6218
6254
  return decoded;
6219
6255
  }
6220
- async function refreshAccessToken(gatewayUrl, refreshToken, pair, gateway) {
6221
- const nextRefreshToken = createRefreshToken();
6222
- const requestId = crypto.randomUUID();
6256
+ function shouldTryNextRoute(cause) {
6257
+ return !(cause instanceof GatewayHttpError) || isRetryableDevicePairingHttpStatus(cause.status);
6258
+ }
6259
+ function isRouteFailure(cause) {
6260
+ if (cause instanceof GatewayHttpError) return isRetryableDevicePairingHttpStatus(cause.status);
6261
+ const message = cause instanceof Error ? cause.message : String(cause);
6262
+ return /network|fetch|respond|identity|route/i.test(message);
6263
+ }
6264
+ async function signedPairingRequestAcrossRoutes(pair, payload, origins, requestId, action, completion) {
6265
+ let lastRouteError;
6266
+ for (const origin of origins) {
6267
+ try {
6268
+ return { origin, result: await signedPairingRequest(pair, payload, origin, requestId, action, completion) };
6269
+ } catch (cause) {
6270
+ if (!shouldTryNextRoute(cause)) throw cause;
6271
+ lastRouteError = cause;
6272
+ }
6273
+ }
6274
+ throw lastRouteError instanceof Error ? lastRouteError : new Error("No Gateway route could be reached");
6275
+ }
6276
+ async function refreshAccessToken(gatewayUrls, refreshToken, pair, gateway, operation = {
6277
+ requestId: crypto.randomUUID(),
6278
+ nextRefreshToken: createRefreshToken()
6279
+ }) {
6280
+ const { nextRefreshToken, requestId } = operation;
6223
6281
  const timestamp = Date.now();
6224
6282
  const nonce = randomNonce();
6225
6283
  const credentialId = refreshToken.slice("xopc_rt_".length).split("_")[0];
@@ -6230,14 +6288,30 @@ ${nonce}
6230
6288
  ${requestId}
6231
6289
  ${nextRefreshToken}`;
6232
6290
  const signature = await sign(pair, proof);
6233
- const response = await post(gatewayUrl, "/api/device-auth/refresh", {
6291
+ const body = {
6234
6292
  refreshToken,
6235
6293
  nextRefreshToken,
6236
6294
  requestId,
6237
6295
  timestamp,
6238
6296
  nonce,
6239
6297
  signature
6240
- });
6298
+ };
6299
+ let gatewayUrl;
6300
+ let response;
6301
+ let lastRouteError;
6302
+ for (const origin of gatewayUrls) {
6303
+ try {
6304
+ response = await post(origin, "/api/device-auth/refresh", body);
6305
+ gatewayUrl = origin;
6306
+ break;
6307
+ } catch (cause) {
6308
+ if (!shouldTryNextRoute(cause)) throw cause;
6309
+ lastRouteError = cause;
6310
+ }
6311
+ }
6312
+ if (!response || !gatewayUrl) {
6313
+ throw lastRouteError instanceof Error ? lastRouteError : new Error("No Gateway route could be reached");
6314
+ }
6241
6315
  if (typeof response.signedPayload !== "string" || typeof response.signature !== "string" || !await verifyGateway(gateway.gatewayPublicKey, response.signedPayload, response.signature)) throw new Error("Gateway identity could not be verified");
6242
6316
  const responseProof = decodeJson(response.signedPayload);
6243
6317
  if (responseProof.purpose !== "device-refresh-v3" || responseProof.gatewayId !== gateway.gatewayId || responseProof.requestId !== requestId || responseProof.nonce !== nonce || !(responseProof.expiresAt > Date.now()) || responseProof.expiresAt > Date.now() + 6e4) throw new Error("Gateway refresh response does not match this request");
@@ -6245,68 +6319,154 @@ ${nextRefreshToken}`;
6245
6319
  if (!payload?.accessToken || !payload.accessTokenExpiresAt || payload.refreshToken !== nextRefreshToken) {
6246
6320
  throw new Error("Gateway returned invalid browser credentials");
6247
6321
  }
6248
- return payload;
6322
+ return { ...payload, gatewayUrl };
6249
6323
  }
6250
6324
  function wait(milliseconds) {
6251
6325
  return new Promise((resolve) => setTimeout(resolve, milliseconds));
6252
6326
  }
6253
- async function pairGateway(link, onApproval) {
6254
- const payload = parseBrowserPairingLink(link);
6327
+ async function pairGateway(invitation, onApproval, requestedOrigin) {
6328
+ const payload = parseBrowserPairingInvitation(invitation, true);
6329
+ const origins = [...new Set(payload.routes.map((route) => new URL(route.url).origin))];
6330
+ const selectedOrigin = requestedOrigin && origins.includes(requestedOrigin) ? requestedOrigin : origins[0];
6331
+ const permissionTask = requestGatewayOriginPermissions([selectedOrigin]);
6332
+ if (!await permissionTask) throw new Error("Gateway site permission was not granted");
6333
+ const permittedOrigins = (await Promise.all(origins.map(async (origin) => ({
6334
+ origin,
6335
+ permitted: await hasGatewayOriginPermission(origin)
6336
+ })))).filter((candidate) => candidate.permitted).map((candidate) => candidate.origin);
6255
6337
  const pair = await getOrCreateKeyPair();
6256
- const requestId = crypto.randomUUID();
6257
- const initialRefreshToken = createRefreshToken();
6258
- let origin;
6259
- let result;
6260
- let lastRouteError;
6261
- for (const candidate of [...new Set(payload.routes.map((route) => new URL(route.url).origin))]) {
6262
- await requestGatewayOriginPermission(candidate);
6263
- try {
6264
- result = await signedPairingRequest(pair, payload, candidate, requestId, "request");
6265
- origin = candidate;
6266
- break;
6267
- } catch (cause) {
6268
- if (cause instanceof GatewayHttpError && cause.status < 500 && ![404, 408, 425, 429].includes(cause.status)) {
6269
- throw cause;
6270
- }
6271
- lastRouteError = cause;
6272
- }
6338
+ const stored = await chrome.storage.local.get(PAIRING_JOURNAL_KEY);
6339
+ let journal = stored[PAIRING_JOURNAL_KEY];
6340
+ if (journal && journal.payload.pairingToken !== payload.pairingToken) {
6341
+ throw new Error("PAIRING_ALREADY_PENDING");
6273
6342
  }
6274
- if (!origin || !result) {
6275
- throw lastRouteError instanceof Error ? lastRouteError : new Error("No Gateway route could be reached");
6276
- }
6277
- if (result.request.status === "pending") onApproval(result.request.confirmationCode);
6278
- while (result.request.status === "pending") {
6279
- await wait(1500);
6280
- result = await signedPairingRequest(pair, payload, origin, requestId, "status");
6343
+ if (!journal) {
6344
+ if (payload.expiresAt <= Date.now()) throw new Error("Pairing invitation has expired");
6345
+ journal = {
6346
+ invitation: invitation.trim(),
6347
+ payload,
6348
+ requestId: crypto.randomUUID(),
6349
+ idempotencyKey: crypto.randomUUID(),
6350
+ initialRefreshToken: createRefreshToken()
6351
+ };
6352
+ await chrome.storage.local.set({ [PAIRING_JOURNAL_KEY]: journal });
6281
6353
  }
6282
- if (result.request.status !== "approved" && result.request.status !== "completed") {
6283
- throw new Error(`Pairing ${result.request.status}`);
6354
+ delete journal.nextOrigin;
6355
+ await chrome.storage.local.set({ [PAIRING_JOURNAL_KEY]: journal });
6356
+ const requestId = journal.requestId;
6357
+ const availableOrigins = journal.origin && permittedOrigins.includes(journal.origin) ? [journal.origin, ...permittedOrigins.filter((candidate) => candidate !== journal.origin)] : [selectedOrigin, ...permittedOrigins.filter((candidate) => candidate !== selectedOrigin)];
6358
+ try {
6359
+ let { origin, result } = await signedPairingRequestAcrossRoutes(
6360
+ pair,
6361
+ journal.payload,
6362
+ availableOrigins,
6363
+ requestId,
6364
+ "request"
6365
+ );
6366
+ journal.origin = origin;
6367
+ await chrome.storage.local.set({ [PAIRING_JOURNAL_KEY]: journal });
6368
+ if (result.request.status === "pending") onApproval(result.request.confirmationCode);
6369
+ while (result.request.status === "pending") {
6370
+ await wait(1500);
6371
+ const response = await signedPairingRequestAcrossRoutes(
6372
+ pair,
6373
+ journal.payload,
6374
+ [origin, ...permittedOrigins.filter((candidate) => candidate !== origin)],
6375
+ requestId,
6376
+ "status"
6377
+ );
6378
+ ({ origin, result } = response);
6379
+ journal.origin = origin;
6380
+ await chrome.storage.local.set({ [PAIRING_JOURNAL_KEY]: journal });
6381
+ }
6382
+ if (result.request.status !== "approved" && result.request.status !== "completed") {
6383
+ await chrome.storage.local.remove(PAIRING_JOURNAL_KEY);
6384
+ throw new Error(`Pairing ${result.request.status}`);
6385
+ }
6386
+ if (result.request.status !== "completed") {
6387
+ const response = await signedPairingRequestAcrossRoutes(
6388
+ pair,
6389
+ journal.payload,
6390
+ [origin, ...permittedOrigins.filter((candidate) => candidate !== origin)],
6391
+ requestId,
6392
+ "complete",
6393
+ {
6394
+ idempotencyKey: journal.idempotencyKey,
6395
+ initialRefreshToken: journal.initialRefreshToken
6396
+ }
6397
+ );
6398
+ ({ origin, result } = response);
6399
+ }
6400
+ if (!result.request.deviceId) throw new Error("Gateway did not register the browser");
6401
+ journal.origin = origin;
6402
+ journal.completed = { deviceId: result.request.deviceId, gatewayName: result.gateway.name };
6403
+ journal.refresh ??= { requestId: crypto.randomUUID(), nextRefreshToken: createRefreshToken() };
6404
+ await chrome.storage.local.set({ [PAIRING_JOURNAL_KEY]: journal });
6405
+ const orderedOrigins = [origin, ...permittedOrigins.filter((candidate) => candidate !== origin)];
6406
+ const tokens = await refreshAccessToken(
6407
+ orderedOrigins,
6408
+ journal.initialRefreshToken,
6409
+ pair,
6410
+ journal.payload,
6411
+ journal.refresh
6412
+ );
6413
+ const profile = {
6414
+ gatewayId: journal.payload.gatewayId,
6415
+ gatewayName: journal.completed.gatewayName,
6416
+ gatewayUrl: tokens.gatewayUrl,
6417
+ gatewayUrls: [tokens.gatewayUrl, ...orderedOrigins.filter((candidate) => candidate !== tokens.gatewayUrl)],
6418
+ gatewayPublicKey: journal.payload.gatewayPublicKey,
6419
+ deviceId: journal.completed.deviceId,
6420
+ refreshToken: tokens.refreshToken,
6421
+ accessToken: tokens.accessToken,
6422
+ accessTokenExpiresAt: tokens.accessTokenExpiresAt
6423
+ };
6424
+ await chrome.storage.local.set({ [PROFILE_KEY]: profile });
6425
+ await chrome.storage.local.remove(PAIRING_JOURNAL_KEY);
6426
+ return profile;
6427
+ } catch (cause) {
6428
+ const nextOrigin = origins.find((origin) => !permittedOrigins.includes(origin));
6429
+ if (nextOrigin && isRouteFailure(cause)) {
6430
+ journal.nextOrigin = nextOrigin;
6431
+ await chrome.storage.local.set({ [PAIRING_JOURNAL_KEY]: journal });
6432
+ throw new Error(`PAIRING_ROUTE_PERMISSION_REQUIRED:${nextOrigin}`);
6433
+ }
6434
+ throw cause;
6284
6435
  }
6285
- if (result.request.status !== "completed") {
6286
- result = await signedPairingRequest(pair, payload, origin, requestId, "complete", {
6287
- idempotencyKey: crypto.randomUUID(),
6288
- initialRefreshToken
6289
- });
6436
+ }
6437
+ async function readPendingBrowserPairing() {
6438
+ const stored = await chrome.storage.local.get(PAIRING_JOURNAL_KEY);
6439
+ const journal = stored[PAIRING_JOURNAL_KEY];
6440
+ return journal ? { invitation: journal.invitation, ...journal.nextOrigin ? { nextOrigin: journal.nextOrigin } : {} } : void 0;
6441
+ }
6442
+ async function cancelPendingBrowserPairing() {
6443
+ const stored = await chrome.storage.local.get(PAIRING_JOURNAL_KEY);
6444
+ const journal = stored[PAIRING_JOURNAL_KEY];
6445
+ if (!journal) return;
6446
+ const origins = [...new Set(journal.payload.routes.map((route) => new URL(route.url).origin))];
6447
+ const permittedOrigins = (await Promise.all(origins.map(async (origin) => ({
6448
+ origin,
6449
+ permitted: await hasGatewayOriginPermission(origin)
6450
+ })))).filter((candidate) => candidate.permitted).map((candidate) => candidate.origin);
6451
+ try {
6452
+ await signedPairingRequestAcrossRoutes(
6453
+ await getOrCreateKeyPair(),
6454
+ journal.payload,
6455
+ journal.origin && permittedOrigins.includes(journal.origin) ? [journal.origin, ...permittedOrigins.filter((candidate) => candidate !== journal.origin)] : permittedOrigins,
6456
+ journal.requestId,
6457
+ "cancel"
6458
+ );
6459
+ } catch (cause) {
6460
+ if (!(cause instanceof GatewayHttpError) || !["PAIRING_NOT_FOUND", "PAIRING_EXPIRED", "PAIRING_CANCELLED"].includes(cause.message)) throw cause;
6290
6461
  }
6291
- if (!result.request.deviceId) throw new Error("Gateway did not register the browser");
6292
- const tokens = await refreshAccessToken(origin, initialRefreshToken, pair, payload);
6293
- const profile = {
6294
- gatewayId: payload.gatewayId,
6295
- gatewayName: result.gateway.name,
6296
- gatewayUrl: origin,
6297
- gatewayPublicKey: payload.gatewayPublicKey,
6298
- deviceId: result.request.deviceId,
6299
- refreshToken: tokens.refreshToken,
6300
- accessToken: tokens.accessToken,
6301
- accessTokenExpiresAt: tokens.accessTokenExpiresAt
6302
- };
6303
- await chrome.storage.local.set({ [PROFILE_KEY]: profile });
6304
- return profile;
6462
+ await chrome.storage.local.remove(PAIRING_JOURNAL_KEY);
6305
6463
  }
6306
6464
  async function readProfile() {
6307
- const stored = await chrome.storage.local.get(PROFILE_KEY);
6465
+ const stored = await chrome.storage.local.get([PROFILE_KEY, PAIRING_JOURNAL_KEY]);
6308
6466
  const profile = stored[PROFILE_KEY];
6309
- return profile?.gatewayId && profile.gatewayUrl && profile.refreshToken ? profile : void 0;
6467
+ if (!profile?.gatewayId || !profile.gatewayUrl || !profile.refreshToken) return void 0;
6468
+ if (stored[PAIRING_JOURNAL_KEY]) await chrome.storage.local.remove(PAIRING_JOURNAL_KEY);
6469
+ return profile;
6310
6470
  }
6311
6471
  async function getAccessProfile() {
6312
6472
  const profile = await readProfile();
@@ -6314,8 +6474,13 @@ async function getAccessProfile() {
6314
6474
  if (profile.accessTokenExpiresAt > Date.now() + 3e4) return profile;
6315
6475
  if (refreshTask) return refreshTask;
6316
6476
  refreshTask = (async () => {
6317
- const tokens = await refreshAccessToken(profile.gatewayUrl, profile.refreshToken, await getOrCreateKeyPair(), profile);
6318
- const next = { ...profile, ...tokens };
6477
+ const gatewayUrls = profile.gatewayUrls?.length ? profile.gatewayUrls : [profile.gatewayUrl];
6478
+ const tokens = await refreshAccessToken(gatewayUrls, profile.refreshToken, await getOrCreateKeyPair(), profile);
6479
+ const next = {
6480
+ ...profile,
6481
+ ...tokens,
6482
+ gatewayUrls: [tokens.gatewayUrl, ...gatewayUrls.filter((candidate) => candidate !== tokens.gatewayUrl)]
6483
+ };
6319
6484
  await chrome.storage.local.set({ [PROFILE_KEY]: next });
6320
6485
  return next;
6321
6486
  })();
@@ -6380,7 +6545,7 @@ async function createBrowserEndpointHello(tools = []) {
6380
6545
  };
6381
6546
  }
6382
6547
  async function forgetProfile() {
6383
- await chrome.storage.local.remove(PROFILE_KEY);
6548
+ await chrome.storage.local.remove([PROFILE_KEY, PAIRING_JOURNAL_KEY]);
6384
6549
  }
6385
6550
  async function revokeAndForgetProfile() {
6386
6551
  try {
@@ -6403,4 +6568,4 @@ async function revokeAndForgetProfile() {
6403
6568
  }
6404
6569
  }
6405
6570
 
6406
- export { ENDPOINT_PROTOCOL_VERSION as E, PENDING_CONTEXT_KEY as P, RealtimeClient as R, TAB_BINDING_PREFIX as T, PENDING_PAIRING_LINK_KEY as a, activeTabId as b, canonicalJson as c, captureTabWithPermission as d, createBrowserEndpointHello as e, currentTabDescriptor as f, deleteBrowserOutbox as g, discoverLocalGateway as h, endpointToolContentSchema as i, gatewayFetch as j, getAccessProfile as k, parseBrowserPairingLink as l, readProfile as m, registerBrowserEndpoint as n, revokeAndForgetProfile as o, pairGateway as p, runWithTabSiteAccess as q, readBrowserOutbox as r, setAutoConnectEnabled as s, takePendingPairingLink as t, writeBrowserOutbox as w };
6571
+ export { ENDPOINT_PROTOCOL_VERSION as E, PENDING_CONTEXT_KEY as P, RealtimeClient as R, TAB_BINDING_PREFIX as T, activeTabId as a, canonicalJson as b, cancelPendingBrowserPairing as c, captureTabWithPermission as d, createBrowserEndpointHello as e, currentTabDescriptor as f, deleteBrowserOutbox as g, discoverLocalGateway as h, endpointToolContentSchema as i, gatewayFetch as j, getAccessProfile as k, readPendingBrowserPairing as l, readProfile as m, registerBrowserEndpoint as n, revokeAndForgetProfile as o, pairGateway as p, runWithTabSiteAccess as q, readBrowserOutbox as r, setAutoConnectEnabled as s, writeBrowserOutbox as w };
@@ -1,4 +1,4 @@
1
- import { i as endpointToolContentSchema, E as ENDPOINT_PROTOCOL_VERSION, c as canonicalJson, T as TAB_BINDING_PREFIX, d as captureTabWithPermission, P as PENDING_CONTEXT_KEY, l as parseBrowserPairingLink, a as PENDING_PAIRING_LINK_KEY, m as readProfile, n as registerBrowserEndpoint, k as getAccessProfile, R as RealtimeClient, e as createBrowserEndpointHello, j as gatewayFetch } from './assets/auth-C1QI-uMG.js';
1
+ import { i as endpointToolContentSchema, E as ENDPOINT_PROTOCOL_VERSION, b as canonicalJson, T as TAB_BINDING_PREFIX, d as captureTabWithPermission, P as PENDING_CONTEXT_KEY, m as readProfile, n as registerBrowserEndpoint, k as getAccessProfile, R as RealtimeClient, e as createBrowserEndpointHello, j as gatewayFetch } from './assets/auth-O8RiOF3l.js';
2
2
 
3
3
  const BROWSER_EXTENSION_PROTOCOL_VERSION = 4;
4
4
  const BROWSER_CONTROL_ENDPOINT_TOOL_NAME = "browser.control";
@@ -1253,22 +1253,6 @@ function connectWithLogging() {
1253
1253
  if (generation !== connectionGeneration && !realtime) connectWithLogging();
1254
1254
  });
1255
1255
  }
1256
- async function stagePairingInvite(message, sender) {
1257
- if (typeof message.pairingLink !== "string" || !sender.url || !sender.tab?.id) {
1258
- throw new Error("Pairing handoff is missing its source tab");
1259
- }
1260
- const source = new URL(sender.url);
1261
- const invitation = new URL(message.pairingLink);
1262
- if (source.href !== invitation.href || source.protocol !== "https:" || source.hostname !== "link.xopc.ai" || source.pathname !== "/connect") {
1263
- throw new Error("Pairing handoff source is not trusted");
1264
- }
1265
- parseBrowserPairingLink(invitation.href);
1266
- const tasks = [
1267
- chrome.storage.session.set({ [PENDING_PAIRING_LINK_KEY]: invitation.href })
1268
- ];
1269
- if (message.open) tasks.push(chrome.sidePanel.open({ tabId: sender.tab.id }));
1270
- await Promise.all(tasks);
1271
- }
1272
1256
  chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
1273
1257
  if (message.type === "browser/get-status") {
1274
1258
  sendResponse({ connected: Boolean(endpointClaim), transport: "gateway-realtime", error: lastConnectionError });
@@ -1278,11 +1262,6 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
1278
1262
  disconnect();
1279
1263
  connectWithLogging();
1280
1264
  sendResponse({ ok: true });
1281
- } else if (message.type === "browser/stage-pairing-invite") {
1282
- void stagePairingInvite(message, sender).then(() => sendResponse({ ok: true })).catch((cause) => sendResponse({
1283
- ok: false,
1284
- error: cause instanceof Error ? cause.message : String(cause)
1285
- }));
1286
1265
  } else {
1287
1266
  sendResponse({ ok: false, error: `Unknown message: ${message.type}` });
1288
1267
  }
@@ -5,7 +5,7 @@
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <title>xopc</title>
7
7
  <script type="module" crossorigin src="./sidepanel.js"></script>
8
- <link rel="modulepreload" crossorigin href="./assets/auth-C1QI-uMG.js">
8
+ <link rel="modulepreload" crossorigin href="./assets/auth-O8RiOF3l.js">
9
9
  <link rel="stylesheet" crossorigin href="./assets/sidepanel-C86O7q5Y.css">
10
10
  </head>
11
11
  <body>