@xopcai/xopc 0.0.272 → 0.0.273

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 (106) 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-DkwOUI6_.js} +101 -53
  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 +24 -22
  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-cF7IX2RP.js} +1 -1
  10. package/dist/gateway/static/root/assets/{agent-avatar-dicebear-Cwj4HCYY.js → agent-avatar-dicebear-BrgxX8nG.js} +1 -1
  11. package/dist/gateway/static/root/assets/{agent-defaults-settings-panel-6CLfLxVc.js → agent-defaults-settings-panel-DvhuRCLA.js} +1 -1
  12. package/dist/gateway/static/root/assets/{agents-B6mu64fY.js → agents-C0By2FIo.js} +1 -1
  13. package/dist/gateway/static/root/assets/{api-CGLdye4i.js → api-CYkdUpkx.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-DfoNzWIa.js} +1 -1
  16. package/dist/gateway/static/root/assets/{appearance-settings-BXMcueb8.js → appearance-settings-czbLdCqj.js} +1 -1
  17. package/dist/gateway/static/root/assets/{apps-page-BEgy4hY3.js → apps-page-BSJXKWcE.js} +1 -1
  18. package/dist/gateway/static/root/assets/{archive-plugin-D4bUampf.js → archive-plugin-D14P13mX.js} +1 -1
  19. package/dist/gateway/static/root/assets/{attachment-load-CBGNA2Ys.js → attachment-load-Dok_B1Gv.js} +1 -1
  20. package/dist/gateway/static/root/assets/{automations-page-dQyolNmy.js → automations-page-DM7ZEewI.js} +1 -1
  21. package/dist/gateway/static/root/assets/{automations-workspace-DxRFrmrV.js → automations-workspace--1TsZf3O.js} +1 -1
  22. package/dist/gateway/static/root/assets/{binary-plugins-bbvp81h2.js → binary-plugins-CxNg6InF.js} +1 -1
  23. package/dist/gateway/static/root/assets/{block-editor-C8e6Wri4.js → block-editor-D22VcYnC.js} +1 -1
  24. package/dist/gateway/static/root/assets/{browser-automation-inputs-oJWJtQa6.js → browser-automation-inputs-6NWTLEpd.js} +1 -1
  25. package/dist/gateway/static/root/assets/{browser-automations-page-CAcJQSjN.js → browser-automations-page-BrqySdbW.js} +1 -1
  26. package/dist/gateway/static/root/assets/{browser-settings-page-TLAlqcPo.js → browser-settings-page-DOHgQer_.js} +1 -1
  27. package/dist/gateway/static/root/assets/{capabilities-settings-panel-z-K_jrZz.js → capabilities-settings-panel-Bpse_5K-.js} +1 -1
  28. package/dist/gateway/static/root/assets/{channels-settings-Dzrk33aB.js → channels-settings-yHsY174W.js} +1 -1
  29. package/dist/gateway/static/root/assets/{channels-status-swr-CfOPNnVr.js → channels-status-swr-BUZimNmp.js} +1 -1
  30. package/dist/gateway/static/root/assets/{chat-terminal-dock-9ZOl92kh.js → chat-terminal-dock-CZPBNpMI.js} +1 -1
  31. package/dist/gateway/static/root/assets/{connectors-page-B1dIoRNi.js → connectors-page-B_wLKGnH.js} +1 -1
  32. package/dist/gateway/static/root/assets/{date-picker-Dj6FRLF9.js → date-picker-Da_0QyLe.js} +1 -1
  33. package/dist/gateway/static/root/assets/{dependency-picker-D1q89tVc.js → dependency-picker-D_djqzy7.js} +1 -1
  34. package/dist/gateway/static/root/assets/{desktop-pet-DfqZBvhp.js → desktop-pet-Celmbw-T.js} +1 -1
  35. package/dist/gateway/static/root/assets/{desktop-pet-settings-HCEh2MOm.js → desktop-pet-settings-CqNNgF45.js} +1 -1
  36. package/dist/gateway/static/root/assets/device-pairing-wizard-B5_w9qlm.js +1 -0
  37. package/dist/gateway/static/root/assets/{extension-debug-page-C8cAWVoK.js → extension-debug-page-ku3kCjvH.js} +1 -1
  38. package/dist/gateway/static/root/assets/{extension-page-5N87Vaym.js → extension-page-C7FpHwL4.js} +1 -1
  39. package/dist/gateway/static/root/assets/{extension-settings-page-D85SAUFP.js → extension-settings-page-OMj2tU2_.js} +1 -1
  40. package/dist/gateway/static/root/assets/{gateway-config-swr-BjsDnB_n.js → gateway-config-swr-6eb1vKE1.js} +1 -1
  41. package/dist/gateway/static/root/assets/{gateway-settings-Dqx8PmcT.js → gateway-settings-CH-6vkgN.js} +1 -1
  42. package/dist/gateway/static/root/assets/{gateway-startup-retry-BIf0eLWX.js → gateway-startup-retry-DUFFCU8H.js} +1 -1
  43. package/dist/gateway/static/root/assets/{heartbeat-settings-BpDWGE1a.js → heartbeat-settings-CSFzOACM.js} +1 -1
  44. package/dist/gateway/static/root/assets/{home-page-DJaqpC8-.js → home-page-BJebAKB-.js} +1 -1
  45. package/dist/gateway/static/root/assets/{image-generation-api-1m8xTVS_.js → image-generation-api-C1cHRBRp.js} +1 -1
  46. package/dist/gateway/static/root/assets/{index-C6PmD0cK.js → index-C6OWYFtQ.js} +9 -9
  47. package/dist/gateway/static/root/assets/{keyboard-shortcuts-settings-Byy8mCSq.js → keyboard-shortcuts-settings-BuvD3iv8.js} +1 -1
  48. package/dist/gateway/static/root/assets/{local-app-workbench-page-l2AEiG2C.js → local-app-workbench-page-CaC4-THD.js} +1 -1
  49. package/dist/gateway/static/root/assets/{local-apps-page-BPo0wBgM.js → local-apps-page-DdJ8zwBp.js} +1 -1
  50. package/dist/gateway/static/root/assets/{locale-store-CRNble2Z.js → locale-store-BBSCIl4I.js} +1 -1
  51. package/dist/gateway/static/root/assets/{logs-page-X5FdLlXK.js → logs-page-BaR2he0K.js} +1 -1
  52. package/dist/gateway/static/root/assets/{management-settings-DT6NoIjm.js → management-settings-r-iYWZFY.js} +1 -1
  53. package/dist/gateway/static/root/assets/markdown-view-c5vtnYe8.js +201 -0
  54. package/dist/gateway/static/root/assets/{media-plugins-Bzka9qXu.js → media-plugins-D_a6GDY6.js} +1 -1
  55. package/dist/gateway/static/root/assets/{messages-DH_HCgmw.js → messages-DjuBdDCn.js} +2 -2
  56. package/dist/gateway/static/root/assets/{note-detail-panel-BUJPoaGZ.js → note-detail-panel-BV6nbgac.js} +3 -3
  57. package/dist/gateway/static/root/assets/{notes-page-BwFpWRuo.js → notes-page-BmLKoWwi.js} +1 -1
  58. package/dist/gateway/static/root/assets/{page-tabs-Ys0p65Lt.js → page-tabs-C7Le9bSI.js} +1 -1
  59. package/dist/gateway/static/root/assets/{preview-open-alternatives-iknB5xEp.js → preview-open-alternatives-B2pOxJBK.js} +1 -1
  60. package/dist/gateway/static/root/assets/{proactive-card-BPZDgNNR.js → proactive-card-BlQ1vCtV.js} +1 -1
  61. package/dist/gateway/static/root/assets/{proactive-page-CxdZR-p2.js → proactive-page-CghKTfYa.js} +1 -1
  62. package/dist/gateway/static/root/assets/{product-open-page-W1o3f-kz.js → product-open-page-DpjiHgwu.js} +1 -1
  63. package/dist/gateway/static/root/assets/{project-detail-page-BU1RKAcy.js → project-detail-page-CV8Lh296.js} +1 -1
  64. package/dist/gateway/static/root/assets/{projects-page-z0X0tL8G.js → projects-page-COweOaJP.js} +1 -1
  65. package/dist/gateway/static/root/assets/{registry-api-DmYFpjsH.js → registry-api-CyQ4k1Ux.js} +1 -1
  66. package/dist/gateway/static/root/assets/{remote-access-hub-NNTut2I8.js → remote-access-hub-ChPPGVU5.js} +1 -1
  67. package/dist/gateway/static/root/assets/{runtime-tools-settings-panel-DP1XyYO8.js → runtime-tools-settings-panel-Bl4ArLHf.js} +1 -1
  68. package/dist/gateway/static/root/assets/{schema-form-BMYKdzsH.js → schema-form-KQFmfWd4.js} +1 -1
  69. package/dist/gateway/static/root/assets/{sessions-page-B6SsHlz5.js → sessions-page-DgmHibOK.js} +1 -1
  70. package/dist/gateway/static/root/assets/settings-advanced-gate-DV7kMco2.js +1 -0
  71. package/dist/gateway/static/root/assets/{settings-form-section-2LGjz7bu.js → settings-form-section-Dbnls9ty.js} +1 -1
  72. package/dist/gateway/static/root/assets/{settings-page-7DjpC9_C.js → settings-page-CDKirYRG.js} +1 -1
  73. package/dist/gateway/static/root/assets/{setup-status-panel-Dj9jFWJt.js → setup-status-panel-SQmtWoOs.js} +1 -1
  74. package/dist/gateway/static/root/assets/{share-preview-page-BxYdSi8c.js → share-preview-page-DwgLjS1m.js} +1 -1
  75. package/dist/gateway/static/root/assets/{shares-settings-CMqE0KuK.js → shares-settings-Cs5bUrYw.js} +1 -1
  76. package/dist/gateway/static/root/assets/skill-reload-api-DyKrjq7F.js +1 -0
  77. package/dist/gateway/static/root/assets/{skills-page-DNEScMZH.js → skills-page-Bd0_ED_p.js} +1 -1
  78. package/dist/gateway/static/root/assets/{skills-page.utils-q88l65YU.js → skills-page.utils-rA-XT-YG.js} +1 -1
  79. package/dist/gateway/static/root/assets/{system-settings-panel-8NIssGKj.js → system-settings-panel-CSkMrHLz.js} +1 -1
  80. package/dist/gateway/static/root/assets/{task-detail-page-L6dp9v2y.js → task-detail-page-CEQU-L_G.js} +1 -1
  81. package/dist/gateway/static/root/assets/{theme-store-BXZG3S25.js → theme-store-DWpIEWen.js} +1 -1
  82. package/dist/gateway/static/root/assets/tunnel-api-COJcGnEi.js +1 -0
  83. package/dist/gateway/static/root/assets/{use-autosave-BwQuoTQU.js → use-autosave-DkQAIAuB.js} +1 -1
  84. package/dist/gateway/static/root/assets/{user-model-page-D4BucMrZ.js → user-model-page-BQAU55lf.js} +1 -1
  85. package/dist/gateway/static/root/assets/{voice-api-key-field-Bd03Ic0s.js → voice-api-key-field-BrqcH0P-.js} +1 -1
  86. package/dist/gateway/static/root/assets/{voice-config-api-gggUp3u9.js → voice-config-api-BJ95Z3pT.js} +1 -1
  87. package/dist/gateway/static/root/assets/{work-discovery-overlay-DF8PR_pK.js → work-discovery-overlay-vfTWKm13.js} +1 -1
  88. package/dist/gateway/static/root/assets/{workflow-run-setup-panel-BiAAWlir.js → workflow-run-setup-panel-CHdr_ng9.js} +1 -1
  89. package/dist/gateway/static/root/assets/{workflows-page-DryKUMfA.js → workflows-page-hwRJD7vR.js} +1 -1
  90. package/dist/gateway/static/root/index.html +10 -9
  91. package/dist/package.js +1 -1
  92. package/dist/src/browser/providers/browser-ext-install.d.ts +6 -0
  93. package/dist/src/browser/providers/browser-ext-install.js +16 -1
  94. package/dist/src/cli/commands/gateway/run-foreground.js +8 -5
  95. package/dist/src/cli/commands/gateway/service.js +4 -3
  96. package/dist/src/cli/commands/gateway/token.js +28 -1
  97. package/dist/src/gateway/device-routes.js +4 -3
  98. package/dist/src/gateway/hono/routes/browser.js +26 -0
  99. package/dist/src/storage/sqlite/device-pairing-approval.js +1 -3
  100. package/package.json +4 -4
  101. package/dist/browser-ext/dist/pairing-handoff.js +0 -51
  102. package/dist/gateway/static/root/assets/device-pairing-wizard-CPth92s6.js +0 -1
  103. package/dist/gateway/static/root/assets/markdown-view-AglreSOj.js +0 -201
  104. package/dist/gateway/static/root/assets/settings-advanced-gate-BQu8bAcN.js +0 -1
  105. package/dist/gateway/static/root/assets/skill-reload-api-BXqSJymN.js +0 -1
  106. package/dist/gateway/static/root/assets/tunnel-api-CWsRPM84.js +0 -1
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>
@@ -5977,12 +5977,12 @@ async function clearBrowserOutboxes() {
5977
5977
  }
5978
5978
 
5979
5979
  const PROFILE_KEY = "xopc.browser.profile";
5980
- const PENDING_PAIRING_LINK_KEY = "xopc.browser.pending-pairing-link";
5981
5980
  const AUTO_CONNECT_KEY = "xopc.browser.auto-connect";
5982
5981
  const KEY_DATABASE = "xopc-browser-identity";
5983
5982
  const KEY_STORE = "identity";
5984
5983
  const KEY_NAME = "device-key";
5985
5984
  const NATIVE_HOST_NAME = "ai.xopc.browser";
5985
+ const GATEWAY_REQUEST_TIMEOUT_MS = 8e3;
5986
5986
  let refreshTask;
5987
5987
  async function browserPublicKeyThumbprint(jwk) {
5988
5988
  const canonical = JSON.stringify({ crv: jwk.crv, kty: jwk.kty, x: jwk.x, y: jwk.y });
@@ -6070,13 +6070,6 @@ function parseBrowserPairingLink(value) {
6070
6070
  }
6071
6071
  return payload;
6072
6072
  }
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
6073
  function openIdentityDatabase() {
6081
6074
  return new Promise((resolve, reject) => {
6082
6075
  const request = indexedDB.open(KEY_DATABASE, 1);
@@ -6153,13 +6146,13 @@ async function verifyGateway(publicKey, payload, signature) {
6153
6146
  return false;
6154
6147
  }
6155
6148
  }
6156
- async function requestGatewayOriginPermission(origin) {
6157
- 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
- }
6149
+ function requestGatewayOriginPermissions(origins) {
6150
+ const patterns = origins.filter((origin) => {
6151
+ const url = new URL(origin);
6152
+ return !(url.protocol === "http:" && ["127.0.0.1", "localhost"].includes(url.hostname));
6153
+ }).map((origin) => `${new URL(origin).origin}/*`);
6154
+ if (!patterns.length) return Promise.resolve(true);
6155
+ return chrome.permissions.request({ origins: patterns });
6163
6156
  }
6164
6157
  class GatewayHttpError extends Error {
6165
6158
  constructor(message, status) {
@@ -6170,11 +6163,22 @@ class GatewayHttpError extends Error {
6170
6163
  status;
6171
6164
  }
6172
6165
  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
- });
6166
+ const controller = new AbortController();
6167
+ const timeout = setTimeout(() => controller.abort(), GATEWAY_REQUEST_TIMEOUT_MS);
6168
+ let response;
6169
+ try {
6170
+ response = await fetch(`${origin}${path}`, {
6171
+ method: "POST",
6172
+ headers: { "Content-Type": "application/json" },
6173
+ body: JSON.stringify(body),
6174
+ signal: controller.signal
6175
+ });
6176
+ } catch (cause) {
6177
+ if (controller.signal.aborted) throw new Error(`Gateway did not respond within ${GATEWAY_REQUEST_TIMEOUT_MS / 1e3} seconds`);
6178
+ throw cause;
6179
+ } finally {
6180
+ clearTimeout(timeout);
6181
+ }
6178
6182
  const json = await response.json().catch(() => ({}));
6179
6183
  if (!response.ok) {
6180
6184
  throw new GatewayHttpError(
@@ -6217,7 +6221,22 @@ async function signedPairingRequest(pair, payload, origin, requestId, action, co
6217
6221
  }
6218
6222
  return decoded;
6219
6223
  }
6220
- async function refreshAccessToken(gatewayUrl, refreshToken, pair, gateway) {
6224
+ function shouldTryNextRoute(cause) {
6225
+ return !(cause instanceof GatewayHttpError) || cause.status >= 500 || [404, 408, 425, 429].includes(cause.status);
6226
+ }
6227
+ async function signedPairingRequestAcrossRoutes(pair, payload, origins, requestId, action, completion) {
6228
+ let lastRouteError;
6229
+ for (const origin of origins) {
6230
+ try {
6231
+ return { origin, result: await signedPairingRequest(pair, payload, origin, requestId, action, completion) };
6232
+ } catch (cause) {
6233
+ if (!shouldTryNextRoute(cause)) throw cause;
6234
+ lastRouteError = cause;
6235
+ }
6236
+ }
6237
+ throw lastRouteError instanceof Error ? lastRouteError : new Error("No Gateway route could be reached");
6238
+ }
6239
+ async function refreshAccessToken(gatewayUrls, refreshToken, pair, gateway) {
6221
6240
  const nextRefreshToken = createRefreshToken();
6222
6241
  const requestId = crypto.randomUUID();
6223
6242
  const timestamp = Date.now();
@@ -6230,14 +6249,30 @@ ${nonce}
6230
6249
  ${requestId}
6231
6250
  ${nextRefreshToken}`;
6232
6251
  const signature = await sign(pair, proof);
6233
- const response = await post(gatewayUrl, "/api/device-auth/refresh", {
6252
+ const body = {
6234
6253
  refreshToken,
6235
6254
  nextRefreshToken,
6236
6255
  requestId,
6237
6256
  timestamp,
6238
6257
  nonce,
6239
6258
  signature
6240
- });
6259
+ };
6260
+ let gatewayUrl;
6261
+ let response;
6262
+ let lastRouteError;
6263
+ for (const origin of gatewayUrls) {
6264
+ try {
6265
+ response = await post(origin, "/api/device-auth/refresh", body);
6266
+ gatewayUrl = origin;
6267
+ break;
6268
+ } catch (cause) {
6269
+ if (!shouldTryNextRoute(cause)) throw cause;
6270
+ lastRouteError = cause;
6271
+ }
6272
+ }
6273
+ if (!response || !gatewayUrl) {
6274
+ throw lastRouteError instanceof Error ? lastRouteError : new Error("No Gateway route could be reached");
6275
+ }
6241
6276
  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
6277
  const responseProof = decodeJson(response.signedPayload);
6243
6278
  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,55 +6280,63 @@ ${nextRefreshToken}`;
6245
6280
  if (!payload?.accessToken || !payload.accessTokenExpiresAt || payload.refreshToken !== nextRefreshToken) {
6246
6281
  throw new Error("Gateway returned invalid browser credentials");
6247
6282
  }
6248
- return payload;
6283
+ return { ...payload, gatewayUrl };
6249
6284
  }
6250
6285
  function wait(milliseconds) {
6251
6286
  return new Promise((resolve) => setTimeout(resolve, milliseconds));
6252
6287
  }
6253
6288
  async function pairGateway(link, onApproval) {
6254
6289
  const payload = parseBrowserPairingLink(link);
6290
+ const origins = [...new Set(payload.routes.map((route) => new URL(route.url).origin))];
6291
+ const permissionTask = requestGatewayOriginPermissions(origins);
6292
+ if (!await permissionTask) throw new Error("Gateway site permission was not granted");
6255
6293
  const pair = await getOrCreateKeyPair();
6256
6294
  const requestId = crypto.randomUUID();
6257
6295
  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
- }
6273
- }
6274
- if (!origin || !result) {
6275
- throw lastRouteError instanceof Error ? lastRouteError : new Error("No Gateway route could be reached");
6276
- }
6296
+ let { origin, result } = await signedPairingRequestAcrossRoutes(
6297
+ pair,
6298
+ payload,
6299
+ origins,
6300
+ requestId,
6301
+ "request"
6302
+ );
6277
6303
  if (result.request.status === "pending") onApproval(result.request.confirmationCode);
6278
6304
  while (result.request.status === "pending") {
6279
6305
  await wait(1500);
6280
- result = await signedPairingRequest(pair, payload, origin, requestId, "status");
6306
+ const response = await signedPairingRequestAcrossRoutes(
6307
+ pair,
6308
+ payload,
6309
+ [origin, ...origins.filter((candidate) => candidate !== origin)],
6310
+ requestId,
6311
+ "status"
6312
+ );
6313
+ ({ origin, result } = response);
6281
6314
  }
6282
6315
  if (result.request.status !== "approved" && result.request.status !== "completed") {
6283
6316
  throw new Error(`Pairing ${result.request.status}`);
6284
6317
  }
6285
6318
  if (result.request.status !== "completed") {
6286
- result = await signedPairingRequest(pair, payload, origin, requestId, "complete", {
6287
- idempotencyKey: crypto.randomUUID(),
6288
- initialRefreshToken
6289
- });
6319
+ const response = await signedPairingRequestAcrossRoutes(
6320
+ pair,
6321
+ payload,
6322
+ [origin, ...origins.filter((candidate) => candidate !== origin)],
6323
+ requestId,
6324
+ "complete",
6325
+ {
6326
+ idempotencyKey: crypto.randomUUID(),
6327
+ initialRefreshToken
6328
+ }
6329
+ );
6330
+ ({ origin, result } = response);
6290
6331
  }
6291
6332
  if (!result.request.deviceId) throw new Error("Gateway did not register the browser");
6292
- const tokens = await refreshAccessToken(origin, initialRefreshToken, pair, payload);
6333
+ const orderedOrigins = [origin, ...origins.filter((candidate) => candidate !== origin)];
6334
+ const tokens = await refreshAccessToken(orderedOrigins, initialRefreshToken, pair, payload);
6293
6335
  const profile = {
6294
6336
  gatewayId: payload.gatewayId,
6295
6337
  gatewayName: result.gateway.name,
6296
- gatewayUrl: origin,
6338
+ gatewayUrl: tokens.gatewayUrl,
6339
+ gatewayUrls: [tokens.gatewayUrl, ...orderedOrigins.filter((candidate) => candidate !== tokens.gatewayUrl)],
6297
6340
  gatewayPublicKey: payload.gatewayPublicKey,
6298
6341
  deviceId: result.request.deviceId,
6299
6342
  refreshToken: tokens.refreshToken,
@@ -6314,8 +6357,13 @@ async function getAccessProfile() {
6314
6357
  if (profile.accessTokenExpiresAt > Date.now() + 3e4) return profile;
6315
6358
  if (refreshTask) return refreshTask;
6316
6359
  refreshTask = (async () => {
6317
- const tokens = await refreshAccessToken(profile.gatewayUrl, profile.refreshToken, await getOrCreateKeyPair(), profile);
6318
- const next = { ...profile, ...tokens };
6360
+ const gatewayUrls = profile.gatewayUrls?.length ? profile.gatewayUrls : [profile.gatewayUrl];
6361
+ const tokens = await refreshAccessToken(gatewayUrls, profile.refreshToken, await getOrCreateKeyPair(), profile);
6362
+ const next = {
6363
+ ...profile,
6364
+ ...tokens,
6365
+ gatewayUrls: [tokens.gatewayUrl, ...gatewayUrls.filter((candidate) => candidate !== tokens.gatewayUrl)]
6366
+ };
6319
6367
  await chrome.storage.local.set({ [PROFILE_KEY]: next });
6320
6368
  return next;
6321
6369
  })();
@@ -6403,4 +6451,4 @@ async function revokeAndForgetProfile() {
6403
6451
  }
6404
6452
  }
6405
6453
 
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 };
6454
+ export { ENDPOINT_PROTOCOL_VERSION as E, PENDING_CONTEXT_KEY as P, RealtimeClient as R, TAB_BINDING_PREFIX as T, activeTabId as a, captureTabWithPermission as b, canonicalJson as c, createBrowserEndpointHello as d, currentTabDescriptor as e, deleteBrowserOutbox as f, discoverLocalGateway as g, endpointToolContentSchema as h, gatewayFetch as i, getAccessProfile as j, readProfile as k, registerBrowserEndpoint as l, revokeAndForgetProfile as m, runWithTabSiteAccess as n, pairGateway as p, 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 { h as endpointToolContentSchema, E as ENDPOINT_PROTOCOL_VERSION, c as canonicalJson, T as TAB_BINDING_PREFIX, b as captureTabWithPermission, P as PENDING_CONTEXT_KEY, k as readProfile, l as registerBrowserEndpoint, j as getAccessProfile, R as RealtimeClient, d as createBrowserEndpointHello, i as gatewayFetch } from './assets/auth-DkwOUI6_.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-DkwOUI6_.js">
9
9
  <link rel="stylesheet" crossorigin href="./assets/sidepanel-C86O7q5Y.css">
10
10
  </head>
11
11
  <body>
@@ -1,4 +1,4 @@
1
- import { R as RealtimeClient, j as gatewayFetch, b as activeTabId, r as readBrowserOutbox, w as writeBrowserOutbox, g as deleteBrowserOutbox, f as currentTabDescriptor, T as TAB_BINDING_PREFIX, k as getAccessProfile, q as runWithTabSiteAccess, P as PENDING_CONTEXT_KEY, d as captureTabWithPermission, p as pairGateway, s as setAutoConnectEnabled, t as takePendingPairingLink, m as readProfile, h as discoverLocalGateway, a as PENDING_PAIRING_LINK_KEY, o as revokeAndForgetProfile } from './assets/auth-C1QI-uMG.js';
1
+ import { R as RealtimeClient, i as gatewayFetch, a as activeTabId, r as readBrowserOutbox, w as writeBrowserOutbox, f as deleteBrowserOutbox, e as currentTabDescriptor, T as TAB_BINDING_PREFIX, j as getAccessProfile, n as runWithTabSiteAccess, P as PENDING_CONTEXT_KEY, b as captureTabWithPermission, p as pairGateway, s as setAutoConnectEnabled, k as readProfile, g as discoverLocalGateway, m as revokeAndForgetProfile } from './assets/auth-DkwOUI6_.js';
2
2
 
3
3
  true &&(function polyfill() {
4
4
  const relList = document.createElement("link").relList;
@@ -27544,6 +27544,22 @@ function watchSidePanelTheme(gatewayUrl) {
27544
27544
  async function reconnectBrowserBridge() {
27545
27545
  await chrome.runtime.sendMessage({ type: "browser/reconnect" }).catch(() => void 0);
27546
27546
  }
27547
+ function pairingErrorMessage(cause) {
27548
+ const message = cause instanceof Error ? cause.message : String(cause);
27549
+ if (message === "Gateway site permission was not granted") {
27550
+ return "Chrome needs access to the Gateway address. Select Connect again and allow the site permission.";
27551
+ }
27552
+ if (message === "PAIRING_EXPIRED" || message === "Pairing expired" || message === "Pairing link has expired") {
27553
+ return "This invitation expired. Create and copy a new invitation from Device access.";
27554
+ }
27555
+ if (message === "PAIRING_DEVICE_MISMATCH") {
27556
+ return "This invitation was created for a phone. Create a browser invitation instead.";
27557
+ }
27558
+ if (message === "No Gateway route could be reached") {
27559
+ return "None of the Gateway addresses responded. Check that the Gateway is online and its secure connection is active.";
27560
+ }
27561
+ return message;
27562
+ }
27547
27563
  function SidePanelApp() {
27548
27564
  const [profile, setProfile] = reactExports.useState();
27549
27565
  const [pairingLink, setPairingLink] = reactExports.useState("");
@@ -27558,6 +27574,7 @@ function SidePanelApp() {
27558
27574
  pairingStarted.current = true;
27559
27575
  setState("pairing");
27560
27576
  setError("");
27577
+ setConfirmationCode("");
27561
27578
  setPairingLink(link);
27562
27579
  try {
27563
27580
  const paired = await pairGateway(link, setConfirmationCode);
@@ -27566,7 +27583,7 @@ function SidePanelApp() {
27566
27583
  setProfile(paired);
27567
27584
  setState("online");
27568
27585
  } catch (cause) {
27569
- setError(cause instanceof Error ? cause.message : String(cause));
27586
+ setError(pairingErrorMessage(cause));
27570
27587
  setState("unpaired");
27571
27588
  } finally {
27572
27589
  pairingStarted.current = false;
@@ -27576,11 +27593,6 @@ function SidePanelApp() {
27576
27593
  if (initializationStarted.current) return;
27577
27594
  initializationStarted.current = true;
27578
27595
  void (async () => {
27579
- const handedOff = await takePendingPairingLink();
27580
- if (handedOff) {
27581
- await connect(handedOff);
27582
- return;
27583
- }
27584
27596
  const stored = await readProfile();
27585
27597
  if (stored) {
27586
27598
  await reconnectBrowserBridge();
@@ -27597,16 +27609,6 @@ function SidePanelApp() {
27597
27609
  await connect(local.pairingLink);
27598
27610
  })();
27599
27611
  }, [connect]);
27600
- reactExports.useEffect(() => {
27601
- const handleStorageChange = (changes, area) => {
27602
- if (area !== "session" || !changes[PENDING_PAIRING_LINK_KEY]?.newValue) return;
27603
- void takePendingPairingLink().then((link) => {
27604
- if (link) void connect(link);
27605
- });
27606
- };
27607
- chrome.storage.onChanged.addListener(handleStorageChange);
27608
- return () => chrome.storage.onChanged.removeListener(handleStorageChange);
27609
- }, [connect]);
27610
27612
  reactExports.useEffect(() => watchSidePanelTheme(profile?.gatewayUrl), [profile?.gatewayUrl]);
27611
27613
  async function disconnect() {
27612
27614
  await revokeAndForgetProfile();
@@ -27641,18 +27643,18 @@ function SidePanelApp() {
27641
27643
  state !== "online" ? /* @__PURE__ */ jsxRuntimeExports.jsxs("section", { className: "center", children: [
27642
27644
  state === "loading" ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "muted", children: "Loading…" }) : null,
27643
27645
  state === "unpaired" || state === "pairing" ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "card", children: [
27644
- /* @__PURE__ */ jsxRuntimeExports.jsx("h1", { children: "Connect to xopc" }),
27646
+ /* @__PURE__ */ jsxRuntimeExports.jsx("h1", { children: "Connect a Gateway" }),
27645
27647
  localPairingLink ? /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
27646
27648
  /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "muted", children: "A local xopc Gateway was found. xopc connects automatically on this computer." }),
27647
27649
  /* @__PURE__ */ jsxRuntimeExports.jsx("button", { className: "primary local-connect", onClick: () => void connect(localPairingLink), disabled: state === "pairing", children: state === "pairing" ? "Connecting…" : "Retry local connection" }),
27648
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "pairing-divider", children: "or use a pairing link" })
27650
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "pairing-divider", children: "or paste an invitation" })
27649
27651
  ] }) : /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
27650
- /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "muted", children: "Connect to a local Gateway, or paste a pairing link for another Gateway." }),
27652
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "muted", children: "Connect locally, or paste the one-time invitation copied from Device access." }),
27651
27653
  /* @__PURE__ */ jsxRuntimeExports.jsx("button", { className: "primary local-connect", onClick: () => void connectLocalGateway(), disabled: state === "pairing", children: state === "pairing" ? "Connecting…" : "Connect local Gateway" }),
27652
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "pairing-divider", children: "or use a pairing link" })
27654
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "pairing-divider", children: "or paste an invitation" })
27653
27655
  ] }),
27654
27656
  /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "field", children: [
27655
- "Pairing link",
27657
+ "One-time invitation",
27656
27658
  /* @__PURE__ */ jsxRuntimeExports.jsx("textarea", { rows: 4, value: pairingLink, onChange: (event) => setPairingLink(event.target.value), disabled: state === "pairing" })
27657
27659
  ] }),
27658
27660
  confirmationCode ? /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
@@ -2,7 +2,7 @@
2
2
  "manifest_version": 3,
3
3
  "minimum_chrome_version": "120",
4
4
  "name": "xopc",
5
- "version": "0.0.272",
5
+ "version": "0.0.273",
6
6
  "description": "Chat with xopc and explicitly share or control the current page.",
7
7
  "key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqnpr4vpLC/nmvy+wiOWk3SWlp2nEZkFijfk8ivGw7StNZgCK90nOMvwhdEtlkk3GOWDvdVnq8HxCsM3UNQ8FnGUyerpH1FJXqXuradxeHf8ZxFjiQU/mIWfnKbUuONWQORon3/pigyoXoK/VUXgg0Wbm/rbVwsS0bv+25R6m+mZgBspOLeKuoH7ivhL+48WV6POtMBsFPZweY26477EM7ukh+56Ff5ENevB8ymxBBn7qK5ZoeZd2a23U8ioX5TsHfe6fnrjitPWjIxD8G0NoKQxcaa/Mo+xs+T6UiLKxP0wjz5bm7jB5H+CXtkRJqzBWUB0h+RSLJckzZfA8BAJrRwIDAQAB",
8
8
  "permissions": [
@@ -28,17 +28,6 @@
28
28
  "service_worker": "dist/background.js",
29
29
  "type": "module"
30
30
  },
31
- "content_scripts": [
32
- {
33
- "matches": [
34
- "https://link.xopc.ai/connect*"
35
- ],
36
- "js": [
37
- "dist/pairing-handoff.js"
38
- ],
39
- "run_at": "document_idle"
40
- }
41
- ],
42
31
  "side_panel": {
43
32
  "default_path": "dist/sidepanel.html"
44
33
  },
@@ -2,7 +2,7 @@
2
2
  "id": "telegram",
3
3
  "name": "Telegram Channel",
4
4
  "description": "Bundled Telegram Bot channel (private workspace sources; ships inside @xopcai/xopc dist/)",
5
- "version": "0.0.272",
5
+ "version": "0.0.273",
6
6
  "kind": "channel",
7
7
  "main": "src/index.js",
8
8
  "channels": [
@@ -1 +1 @@
1
- import{p as e}from"./locale-store-CRNble2Z.js";var t=`xopc.activity-center.v1`,n=80,r=720*60*60*1e3;function i(e){return e===`error`?`failed`:e===`warning`?`attention`:`done`}function a(){if(!(typeof window>`u`))return window.location.hash.replace(/^#/,``)||`${window.location.pathname}${window.location.search}`||void 0}function o(){try{let e=globalThis.localStorage?.getItem(t);if(!e)return[];let i=JSON.parse(e);if(!Array.isArray(i.items))return[];let a=Date.now()-r,o=i.items.filter(e=>e&&typeof e.id==`string`&&typeof e.title==`string`).filter(e=>Number.isFinite(e.updatedAt)&&e.updatedAt>=a).slice(0,n);return o.length!==i.items.length&&s(o),o}catch{return[]}}function s(e){try{globalThis.localStorage?.setItem(t,JSON.stringify({items:e}))}catch{}}function c(e){return`${e.toString(36)}-${Math.random().toString(36).slice(2,8)}`}var l=e(e=>({items:o(),add:t=>e(e=>{let r=t.title.trim();if(!r)return e;let o=Date.now(),l=t.tone??`info`,u=t.dedupeKey?e.items.findIndex(e=>e.dedupeKey===t.dedupeKey):-1,d=[{id:u>=0?e.items[u].id:c(o),tone:l,status:t.status??i(l),title:r,message:t.message?.trim()||void 0,source:t.source?.trim()||void 0,href:t.href??a(),dedupeKey:t.dedupeKey,createdAt:u>=0?e.items[u].createdAt:o,updatedAt:o,read:t.read??!1,occurrences:u>=0?e.items[u].occurrences+1:1},...u>=0?e.items.filter((e,t)=>t!==u):e.items].slice(0,n);return s(d),{items:d}}),markAllRead:()=>e(e=>{let t=e.items.map(e=>({...e,read:!0}));return s(t),{items:t}}),remove:t=>e(e=>{let n=e.items.filter(e=>e.id!==t);return s(n),{items:n}}),clearFinished:()=>e(e=>{let t=e.items.filter(e=>e.status!==`done`);return s(t),{items:t}}),clear:()=>{s([]),e({items:[]})}}));function u(e){l.getState().add(e)}export{u as t};
1
+ import{p as e}from"./locale-store-BBSCIl4I.js";var t=`xopc.activity-center.v1`,n=80,r=720*60*60*1e3;function i(e){return e===`error`?`failed`:e===`warning`?`attention`:`done`}function a(){if(!(typeof window>`u`))return window.location.hash.replace(/^#/,``)||`${window.location.pathname}${window.location.search}`||void 0}function o(){try{let e=globalThis.localStorage?.getItem(t);if(!e)return[];let i=JSON.parse(e);if(!Array.isArray(i.items))return[];let a=Date.now()-r,o=i.items.filter(e=>e&&typeof e.id==`string`&&typeof e.title==`string`).filter(e=>Number.isFinite(e.updatedAt)&&e.updatedAt>=a).slice(0,n);return o.length!==i.items.length&&s(o),o}catch{return[]}}function s(e){try{globalThis.localStorage?.setItem(t,JSON.stringify({items:e}))}catch{}}function c(e){return`${e.toString(36)}-${Math.random().toString(36).slice(2,8)}`}var l=e(e=>({items:o(),add:t=>e(e=>{let r=t.title.trim();if(!r)return e;let o=Date.now(),l=t.tone??`info`,u=t.dedupeKey?e.items.findIndex(e=>e.dedupeKey===t.dedupeKey):-1,d=[{id:u>=0?e.items[u].id:c(o),tone:l,status:t.status??i(l),title:r,message:t.message?.trim()||void 0,source:t.source?.trim()||void 0,href:t.href??a(),dedupeKey:t.dedupeKey,createdAt:u>=0?e.items[u].createdAt:o,updatedAt:o,read:t.read??!1,occurrences:u>=0?e.items[u].occurrences+1:1},...u>=0?e.items.filter((e,t)=>t!==u):e.items].slice(0,n);return s(d),{items:d}}),markAllRead:()=>e(e=>{let t=e.items.map(e=>({...e,read:!0}));return s(t),{items:t}}),remove:t=>e(e=>{let n=e.items.filter(e=>e.id!==t);return s(n),{items:n}}),clearFinished:()=>e(e=>{let t=e.items.filter(e=>e.status!==`done`);return s(t),{items:t}}),clear:()=>{s([]),e({items:[]})}}));function u(e){l.getState().add(e)}export{u as t};