@xopcai/xopc 0.0.264 → 0.0.265

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 (175) 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-DRTTU2H3.js} +1 -1
  10. package/dist/gateway/static/root/assets/{agent-defaults-settings-panel-BZvMqNrU.js → agent-defaults-settings-panel-BsXF7LVi.js} +1 -1
  11. package/dist/gateway/static/root/assets/{agents-6foBELwt.js → agents-DpjWfxYx.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-33Mc81h2.js} +1 -1
  14. package/dist/gateway/static/root/assets/{appearance-settings-C7nBHnkG.js → appearance-settings-C8JECSiJ.js} +1 -1
  15. package/dist/gateway/static/root/assets/{apps-page-B_g-rL5b.js → apps-page-t64FcxIZ.js} +1 -1
  16. package/dist/gateway/static/root/assets/{archive-plugin-DfKK1iJO.js → archive-plugin-PzZ6SzfY.js} +1 -1
  17. package/dist/gateway/static/root/assets/{attachment-load-BOU1pdnQ.js → attachment-load-OhLL6K__.js} +1 -1
  18. package/dist/gateway/static/root/assets/{automations-page-BePzexAH.js → automations-page-BpAYwuvx.js} +1 -1
  19. package/dist/gateway/static/root/assets/{automations-workspace-7-m5pBEZ.js → automations-workspace-B6aTT5IC.js} +1 -1
  20. package/dist/gateway/static/root/assets/{binary-plugins-Cly8XDyt.js → binary-plugins-B5XpGsHg.js} +1 -1
  21. package/dist/gateway/static/root/assets/{block-editor-DzSu62js.js → block-editor-BgEhoTnN.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-CBLunDRH.js} +1 -1
  24. package/dist/gateway/static/root/assets/{browser-settings-page-Dum3mTiz.js → browser-settings-page-CSfpPCuD.js} +1 -1
  25. package/dist/gateway/static/root/assets/{capabilities-settings-panel-Ci6qVS-O.js → capabilities-settings-panel-DftNnICK.js} +2 -2
  26. package/dist/gateway/static/root/assets/{channels-settings-BOGEZnSv.js → channels-settings-CpuWa5qu.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-mnPsS_GJ.js} +1 -1
  29. package/dist/gateway/static/root/assets/{connectors-page-DBvEvupa.js → connectors-page-D3nHGnNV.js} +1 -1
  30. package/dist/gateway/static/root/assets/{cron-expression-BszT_gjK.js → cron-expression-NcGBvIfe.js} +1 -1
  31. package/dist/gateway/static/root/assets/{date-picker-D2MKBJ0G.js → date-picker-BrVcl7H5.js} +1 -1
  32. package/dist/gateway/static/root/assets/{dependency-picker-DqigBx-G.js → dependency-picker--edd48Y9.js} +1 -1
  33. package/dist/gateway/static/root/assets/{desktop-pet-settings-DMPk4CaK.js → desktop-pet-settings-DWredJwO.js} +1 -1
  34. package/dist/gateway/static/root/assets/{desktop-pet-CapspBGG.js → desktop-pet-vuyyb6I6.js} +1 -1
  35. package/dist/gateway/static/root/assets/{extension-debug-page-CXLsDtrW.js → extension-debug-page-CtBW4ebj.js} +1 -1
  36. package/dist/gateway/static/root/assets/{extension-page-B2j87ROw.js → extension-page-CsUrmtW7.js} +1 -1
  37. package/dist/gateway/static/root/assets/{extension-settings-page-t1BApw-u.js → extension-settings-page-BUd31AuK.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-C6FQv7op.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-CDo1Gjp2.js} +1 -1
  43. package/dist/gateway/static/root/assets/{home-page-uItZMiBE.js → home-page-D3_uvVJY.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-Dpu7EKTm.js +74 -0
  46. package/dist/gateway/static/root/assets/{keyboard-shortcuts-settings-Q_CM-ZCs.js → keyboard-shortcuts-settings-CNVmrCrV.js} +1 -1
  47. package/dist/gateway/static/root/assets/{local-app-workbench-page-DaiBWfOt.js → local-app-workbench-page-B0IVDvs-.js} +1 -1
  48. package/dist/gateway/static/root/assets/{local-apps-page-BALMNCSp.js → local-apps-page-DvkarvLt.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-BknY0vZf.js} +1 -1
  51. package/dist/gateway/static/root/assets/{management-settings-BajkJeYl.js → management-settings-DW6hJINQ.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-DYautOWY.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-Dgu03cv-.js} +1 -1
  56. package/dist/gateway/static/root/assets/{note-detail-panel-CCbPg_GP.js → note-detail-panel-Cv9uH6Jq.js} +3 -3
  57. package/dist/gateway/static/root/assets/{notes-page-BdxxcUKa.js → notes-page-DcIAiYJn.js} +1 -1
  58. package/dist/gateway/static/root/assets/{page-tabs-BMYQef5D.js → page-tabs-DF6Q5XvA.js} +1 -1
  59. package/dist/gateway/static/root/assets/{preview-open-alternatives-CKZ-H5hJ.js → preview-open-alternatives-CQJH_jAN.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-D65JfNSw.js} +1 -1
  63. package/dist/gateway/static/root/assets/{projects-page-Wt7gF3Sx.js → projects-page-BfWF7Sz-.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-DA1g4CUD.js} +1 -1
  66. package/dist/gateway/static/root/assets/{runtime-tools-settings-panel-DcUB5h1f.js → runtime-tools-settings-panel-0-bFSBB8.js} +1 -1
  67. package/dist/gateway/static/root/assets/{schema-form-CquWhe7r.js → schema-form-csGbhTUr.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-DXPBNhrZ.js} +1 -1
  70. package/dist/gateway/static/root/assets/settings-advanced-gate-DxeZm67O.js +1 -0
  71. package/dist/gateway/static/root/assets/{settings-form-section-DwswAefc.js → settings-form-section-YdrdY8B3.js} +1 -1
  72. package/dist/gateway/static/root/assets/{settings-page-CoAVmjjs.js → settings-page-Cv9waRn3.js} +1 -1
  73. package/dist/gateway/static/root/assets/{setup-status-panel-C9EIcWtH.js → setup-status-panel-DPf-_05Z.js} +1 -1
  74. package/dist/gateway/static/root/assets/{share-preview-page-DWwy_4zh.js → share-preview-page-DBoEHTPn.js} +1 -1
  75. package/dist/gateway/static/root/assets/{shares-settings-CIUvq27-.js → shares-settings-BQP5yNcO.js} +1 -1
  76. package/dist/gateway/static/root/assets/skill-reload-api-B5T8cgjq.js +1 -0
  77. package/dist/gateway/static/root/assets/{skills-page-CqsgVtVh.js → skills-page-D1G8h2Lt.js} +1 -1
  78. package/dist/gateway/static/root/assets/{skills-page.utils-B-N73248.js → skills-page.utils-BNZYTop7.js} +1 -1
  79. package/dist/gateway/static/root/assets/{system-settings-panel-BndjXuBZ.js → system-settings-panel-ByuppbHi.js} +1 -1
  80. package/dist/gateway/static/root/assets/{task-detail-page-CG6Ha9t7.js → task-detail-page-DNmkrF6y.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-CxWGpbwr.js} +1 -1
  83. package/dist/gateway/static/root/assets/{use-autosave-mj9CUuz1.js → use-autosave-B0l4qubU.js} +1 -1
  84. package/dist/gateway/static/root/assets/{user-model-page-DKeHMXI4.js → user-model-page-DMsI6YI7.js} +1 -1
  85. package/dist/gateway/static/root/assets/{voice-api-key-field-CzxVMYlr.js → voice-api-key-field-Bcz7boJg.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-Dujq1j_u.js} +1 -1
  88. package/dist/gateway/static/root/assets/{workflow-run-setup-panel-C82OXCJK.js → workflow-run-setup-panel-CxNYbLNB.js} +1 -1
  89. package/dist/gateway/static/root/assets/{workflows-page-HLCUV3rQ.js → workflows-page-Dk1jK2nv.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/source-context/browser-page.d.ts +3 -0
  101. package/dist/src/agent/source-context/browser-page.js +39 -0
  102. package/dist/src/agent/source-context/injector.js +9 -2
  103. package/dist/src/agent/source-context/types.d.ts +7 -1
  104. package/dist/src/agent/source-context/types.js +4 -1
  105. package/dist/src/agent/tools/factory.js +9 -1
  106. package/dist/src/browser/automations/schema.d.ts +1 -1
  107. package/dist/src/browser/automations/service.d.ts +1 -1
  108. package/dist/src/browser/native-messaging-host.d.ts +15 -0
  109. package/dist/src/browser/native-messaging-host.js +105 -0
  110. package/dist/src/browser/providers/browser-ext-install.d.ts +17 -1
  111. package/dist/src/browser/providers/browser-ext-install.js +82 -16
  112. package/dist/src/browser/providers/extension.d.ts +4 -0
  113. package/dist/src/browser/providers/extension.js +104 -38
  114. package/dist/src/browser/runtime/browser-runtime.d.ts +2 -1
  115. package/dist/src/browser/runtime/browser-runtime.js +6 -2
  116. package/dist/src/cli/commands/browser.js +13 -3
  117. package/dist/src/cli/commands/doctor/checks/database-schema.js +1 -1
  118. package/dist/src/endpoint-tools/runtime.js +2 -0
  119. package/dist/src/gateway/chat-stream/mapper.js +3 -2
  120. package/dist/src/gateway/hono/app.js +19 -3
  121. package/dist/src/gateway/hono/middleware/auth.d.ts +4 -1
  122. package/dist/src/gateway/hono/middleware/auth.js +12 -1
  123. package/dist/src/gateway/hono/routes/browser-install.js +1 -1
  124. package/dist/src/gateway/hono/routes/browser.js +106 -7
  125. package/dist/src/gateway/hono/routes/devices.js +36 -3
  126. package/dist/src/gateway/hono/routes/endpoint-tools.js +15 -0
  127. package/dist/src/gateway/hono/routes/session-input-handler.d.ts +20 -2
  128. package/dist/src/gateway/hono/routes/session-input-handler.js +18 -0
  129. package/dist/src/gateway/hono/routes/sessions.js +12 -6
  130. package/dist/src/gateway/hono/routes/voice.js +5 -4
  131. package/dist/src/gateway/security/gateway-scopes.d.ts +1 -0
  132. package/dist/src/gateway/security/gateway-scopes.js +10 -1
  133. package/dist/src/gateway/security/origin-check.d.ts +1 -0
  134. package/dist/src/gateway/security/origin-check.js +5 -1
  135. package/dist/src/gateway/service/agent-runner.js +2 -1
  136. package/dist/src/gateway/service/session-input-coordinator.d.ts +1 -0
  137. package/dist/src/gateway/service/session-input-coordinator.js +5 -4
  138. package/dist/src/gateway/service.js +12 -11
  139. package/dist/src/realtime/runtime.js +2 -1
  140. package/dist/src/session/client-history.d.ts +4 -1
  141. package/dist/src/session/client-history.js +6 -3
  142. package/dist/src/storage/sqlite/browser-tab-binding-repository.d.ts +7 -0
  143. package/dist/src/storage/sqlite/browser-tab-binding-repository.js +60 -0
  144. package/dist/src/storage/sqlite/device-access-repository.d.ts +3 -1
  145. package/dist/src/storage/sqlite/device-access-repository.js +4 -3
  146. package/dist/src/storage/sqlite/device-pairing-approval.js +6 -4
  147. package/dist/src/storage/sqlite/device-pairing-repository.d.ts +1 -1
  148. package/dist/src/storage/sqlite/index.d.ts +1 -0
  149. package/dist/src/storage/sqlite/index.js +2 -1
  150. package/dist/src/storage/sqlite/migrations/155_browser_extension.sql +120 -0
  151. package/dist/src/storage/sqlite/migrations/156_browser_tab_bindings.sql +14 -0
  152. package/dist/src/storage/sqlite/migrations/runner.d.ts +1 -1
  153. package/dist/src/storage/sqlite/migrations/runner.js +3 -3
  154. package/dist/src/storage/sqlite/migrations/runner.ts +1 -1
  155. package/dist/src/storage/sqlite/schema.js +1 -1
  156. package/dist/src/voice/realtime/agentBroker.d.ts +54 -0
  157. package/dist/src/voice/realtime/agentBroker.js +116 -0
  158. package/dist/src/voice/realtime/agentEngine.js +62 -15
  159. package/dist/src/voice/realtime/engine.d.ts +2 -1
  160. package/dist/src/voice/realtime/omniEngine.js +36 -7
  161. package/dist/src/voice/realtime/pcmFrameBuffer.d.ts +6 -0
  162. package/dist/src/voice/realtime/pcmFrameBuffer.js +29 -0
  163. package/dist/src/voice/realtime/runtime.js +97 -21
  164. package/dist/src/voice/realtime/runtime.types.d.ts +5 -21
  165. package/dist/src/voice/realtime/turnPolicy.d.ts +36 -0
  166. package/dist/src/voice/realtime/turnPolicy.js +134 -0
  167. package/package.json +2 -2
  168. package/dist/browser-ext/dist/content.js +0 -191
  169. package/dist/browser-ext/dist/popup.js +0 -32
  170. package/dist/browser-ext/popup.html +0 -37
  171. package/dist/gateway/static/root/assets/index-DQtjs2x3.js +0 -74
  172. package/dist/gateway/static/root/assets/settings-advanced-gate-DK1yTYE4.js +0 -1
  173. package/dist/gateway/static/root/assets/skill-reload-api-DfE3zFVw.js +0 -1
  174. package/dist/src/voice/realtime/conversationTurn.d.ts +0 -20
  175. package/dist/src/voice/realtime/conversationTurn.js +0 -83
@@ -3,7 +3,7 @@
3
3
  * Fixed path — gateway upgrades overwrite in place so Chrome sideload paths stay stable.
4
4
  */
5
5
  import type { Config } from '../../config/schema.js';
6
- export declare const BROWSER_EXT_REQUIRED_FILES: readonly ["manifest.json", "popup.html", "dist/background.js", "dist/content.js", "dist/popup.js"];
6
+ export declare const BROWSER_EXT_REQUIRED_FILES: readonly ["manifest.json", "dist/background.js", "dist/sidepanel.html"];
7
7
  export type BrowserExtBundledFrom = 'npm-dist' | 'git-dev' | 'electron-asar' | 'env-override';
8
8
  export interface BrowserExtInstallMeta {
9
9
  xopcVersion: string;
@@ -32,6 +32,11 @@ export interface EnsureBrowserExtResult {
32
32
  xopcVersion: string;
33
33
  copied: boolean;
34
34
  }
35
+ export interface BrowserNativeHostInstallResult {
36
+ installed: boolean;
37
+ manifestPaths: string[];
38
+ reason?: string;
39
+ }
35
40
  /** Validate a directory contains a loadable extension tree. */
36
41
  export declare function validateBrowserExtLayout(dir: string): boolean;
37
42
  export declare function browserExtContentHash(dir: string): string;
@@ -59,6 +64,17 @@ export declare function ensureBrowserExtensionArtifacts(opts?: {
59
64
  force?: boolean;
60
65
  cacheDir?: string;
61
66
  }): Promise<EnsureBrowserExtResult>;
67
+ export declare function browserNativeManifestDirectories(platform?: NodeJS.Platform, home?: string): string[];
68
+ /** Install the local-only bootstrap host used to discover and pair with a Gateway. */
69
+ export declare function installBrowserNativeMessagingHost(opts?: {
70
+ cacheDir?: string;
71
+ cliPath?: string;
72
+ nodePath?: string;
73
+ platform?: NodeJS.Platform;
74
+ home?: string;
75
+ configPath?: string;
76
+ stateDir?: string;
77
+ }): Promise<BrowserNativeHostInstallResult>;
62
78
  /** Gateway startup hook: ensure artifacts only while the extension driver is active. */
63
79
  export declare function ensureBrowserExtensionOnStartup(config: Config | undefined): Promise<void>;
64
80
  export declare function readInstalledExtensionDir(cacheDir?: string): Promise<string | null>;
@@ -1,12 +1,14 @@
1
1
  import { PACKAGE_VERSION, init_package_version } from "../../package-version.js";
2
+ import { resolveStateDir } from "../../config/paths-state.js";
2
3
  import { createLogger } from "../../utils/logger/index.js";
3
4
  import { init_logger } from "../../utils/logger.js";
4
- import { init_paths, resolveBinDir } from "../../config/paths.js";
5
+ import { init_paths, resolveBinDir, resolveConfigPath } from "../../config/paths.js";
5
6
  import { init_write_file_atomic, writeTextAtomic } from "../../infra/write-file-atomic.js";
6
- import { resolvePackageRoot } from "../../infra/update-check.js";
7
7
  import { assertCacheDir } from "../cache-dir-policy.js";
8
+ import { BROWSER_EXTENSION_ID, BROWSER_NATIVE_HOST_NAME } from "../native-messaging-host.js";
9
+ import { resolvePackageRoot } from "../../infra/update-check.js";
8
10
  import { dirname, join } from "node:path";
9
- import { existsSync, mkdirSync, readFileSync, readdirSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
11
+ import { chmodSync, existsSync, mkdirSync, readFileSync, readdirSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
10
12
  import { fileURLToPath } from "node:url";
11
13
  import { spawn } from "node:child_process";
12
14
  import { createHash } from "node:crypto";
@@ -25,16 +27,13 @@ const META_FILENAME = ".meta.json";
25
27
  const STAGING_MAX_AGE_MS = 3600 * 1e3;
26
28
  const INSTALLED_ARTIFACT_NAMES = [
27
29
  "manifest.json",
28
- "popup.html",
29
30
  "dist",
30
31
  "icons"
31
32
  ];
32
33
  const BROWSER_EXT_REQUIRED_FILES = [
33
34
  "manifest.json",
34
- "popup.html",
35
35
  "dist/background.js",
36
- "dist/content.js",
37
- "dist/popup.js"
36
+ "dist/sidepanel.html"
38
37
  ];
39
38
  function moduleDir() {
40
39
  return dirname(fileURLToPath(import.meta.url));
@@ -45,7 +44,16 @@ function validateBrowserExtLayout(dir) {
45
44
  }
46
45
  function browserExtContentHash(dir) {
47
46
  const hash = createHash("sha256");
48
- for (const relativePath of BROWSER_EXT_REQUIRED_FILES) {
47
+ const collectFiles = (relativeDir) => readdirSync(join(dir, relativeDir), { withFileTypes: true }).flatMap((entry) => {
48
+ const relativePath = join(relativeDir, entry.name);
49
+ return entry.isDirectory() ? collectFiles(relativePath) : [relativePath];
50
+ });
51
+ const relativePaths = [
52
+ "manifest.json",
53
+ ...collectFiles("dist"),
54
+ ...collectFiles("icons")
55
+ ].sort();
56
+ for (const relativePath of relativePaths) {
49
57
  hash.update(relativePath);
50
58
  hash.update(readFileSync(join(dir, relativePath)));
51
59
  }
@@ -177,21 +185,25 @@ function copyBundledFile(src, dest) {
177
185
  mkdirSync(dirname(dest), { recursive: true });
178
186
  writeFileSync(dest, readFileSync(src));
179
187
  }
180
- const BROWSER_EXT_DIST_FILES = [
181
- "background.js",
182
- "content.js",
183
- "popup.js"
184
- ];
185
188
  const BROWSER_EXT_ICON_FILES = [
186
189
  "icon-16.png",
187
190
  "icon-32.png",
188
191
  "icon-48.png",
189
192
  "icon-128.png"
190
193
  ];
194
+ function copyBundledDirectory(src, dest) {
195
+ mkdirSync(dest, { recursive: true });
196
+ for (const entry of readdirSync(src, { withFileTypes: true })) {
197
+ const sourcePath = join(src, entry.name);
198
+ const destinationPath = join(dest, entry.name);
199
+ if (entry.isDirectory()) copyBundledDirectory(sourcePath, destinationPath);
200
+ else copyBundledFile(sourcePath, destinationPath);
201
+ }
202
+ }
191
203
  function copyBundledTree(src, dest) {
192
204
  mkdirSync(dest, { recursive: true });
193
- for (const name of ["manifest.json", "popup.html"]) copyBundledFile(join(src, name), join(dest, name));
194
- for (const file of BROWSER_EXT_DIST_FILES) copyBundledFile(join(src, "dist", file), join(dest, "dist", file));
205
+ copyBundledFile(join(src, "manifest.json"), join(dest, "manifest.json"));
206
+ copyBundledDirectory(join(src, "dist"), join(dest, "dist"));
195
207
  for (const icon of BROWSER_EXT_ICON_FILES) {
196
208
  const iconSrc = join(src, "icons", icon);
197
209
  if (existsSync(iconSrc)) copyBundledFile(iconSrc, join(dest, "icons", icon));
@@ -283,6 +295,60 @@ async function ensureBrowserExtensionArtifacts(opts) {
283
295
  copied: true
284
296
  };
285
297
  }
298
+ function shellQuote(value) {
299
+ return `'${value.replaceAll("'", "'\\''")}'`;
300
+ }
301
+ function browserNativeManifestDirectories(platform = process.platform, home = process.env.HOME || process.env.USERPROFILE || "") {
302
+ if (!home) return [];
303
+ if (platform === "darwin") return [
304
+ join(home, "Library/Application Support/Google/Chrome/NativeMessagingHosts"),
305
+ join(home, "Library/Application Support/Chromium/NativeMessagingHosts"),
306
+ join(home, "Library/Application Support/Microsoft Edge/NativeMessagingHosts"),
307
+ join(home, "Library/Application Support/BraveSoftware/Brave-Browser/NativeMessagingHosts")
308
+ ];
309
+ if (platform === "linux") return [
310
+ join(home, ".config/google-chrome/NativeMessagingHosts"),
311
+ join(home, ".config/chromium/NativeMessagingHosts"),
312
+ join(home, ".config/microsoft-edge/NativeMessagingHosts"),
313
+ join(home, ".config/BraveSoftware/Brave-Browser/NativeMessagingHosts")
314
+ ];
315
+ return [];
316
+ }
317
+ /** Install the local-only bootstrap host used to discover and pair with a Gateway. */
318
+ async function installBrowserNativeMessagingHost(opts) {
319
+ const directories = browserNativeManifestDirectories(opts?.platform ?? process.platform, opts?.home);
320
+ if (directories.length === 0) return {
321
+ installed: false,
322
+ manifestPaths: [],
323
+ reason: "Native bootstrap installation is supported on macOS and Linux"
324
+ };
325
+ const cliPath = opts?.cliPath ?? process.argv[1];
326
+ if (!cliPath) throw new Error("Unable to resolve the xopc CLI entry point");
327
+ const hostPath = join((opts?.cacheDir?.trim() ? assertCacheDir(opts.cacheDir) : resolveBinDir()) || resolveBinDir(), "browser-native-host");
328
+ mkdirSync(dirname(hostPath), { recursive: true });
329
+ const stateDir = opts?.stateDir ?? resolveStateDir();
330
+ const configPath = opts?.configPath ?? resolveConfigPath();
331
+ await writeTextAtomic(hostPath, `#!/bin/sh\nexec env XOPC_STATE_DIR=${shellQuote(stateDir)} XOPC_CONFIG_PATH=${shellQuote(configPath)} XOPC_LOG_CONSOLE=false ${shellQuote(opts?.nodePath ?? process.execPath)} ${shellQuote(cliPath)} browser extension native-host\n`);
332
+ chmodSync(hostPath, 493);
333
+ const manifest = JSON.stringify({
334
+ name: BROWSER_NATIVE_HOST_NAME,
335
+ description: "xopc local browser pairing bootstrap",
336
+ path: hostPath,
337
+ type: "stdio",
338
+ allowed_origins: [`chrome-extension://${BROWSER_EXTENSION_ID}/`]
339
+ }, null, 2);
340
+ const manifestPaths = [];
341
+ for (const directory of directories) {
342
+ mkdirSync(directory, { recursive: true });
343
+ const manifestPath = join(directory, `${BROWSER_NATIVE_HOST_NAME}.json`);
344
+ await writeTextAtomic(manifestPath, manifest);
345
+ manifestPaths.push(manifestPath);
346
+ }
347
+ return {
348
+ installed: true,
349
+ manifestPaths
350
+ };
351
+ }
286
352
  /** Gateway startup hook: ensure artifacts only while the extension driver is active. */
287
353
  async function ensureBrowserExtensionOnStartup(config) {
288
354
  if (config?.browser.enabled === false || config?.browser.driver.kind !== "extension") return;
@@ -366,4 +432,4 @@ async function openBrowserExtensionInstallUi(opts) {
366
432
  };
367
433
  }
368
434
  //#endregion
369
- export { BROWSER_EXT_REQUIRED_FILES, browserExtContentHash, browserExtDoctor, computeNeedsRefresh, ensureBrowserExtensionArtifacts, ensureBrowserExtensionOnStartup, openBrowserExtensionInstallUi, readInstalledExtensionDir, resolveBundledBrowserExtDir, resolveInstalledExtensionPath, resolveWindowsExtensionManager, validateBrowserExtLayout };
435
+ export { BROWSER_EXT_REQUIRED_FILES, browserExtContentHash, browserExtDoctor, browserNativeManifestDirectories, computeNeedsRefresh, ensureBrowserExtensionArtifacts, ensureBrowserExtensionOnStartup, installBrowserNativeMessagingHost, openBrowserExtensionInstallUi, readInstalledExtensionDir, resolveBundledBrowserExtDir, resolveInstalledExtensionPath, resolveWindowsExtensionManager, validateBrowserExtLayout };
@@ -31,6 +31,8 @@ export declare class ExtensionBrowserProvider {
31
31
  private connected;
32
32
  private extensionProtocolVersion;
33
33
  private extensionVersion;
34
+ private connectionId;
35
+ private principalId;
34
36
  private readonly config;
35
37
  private connectionWaiters;
36
38
  constructor(config?: ExtensionProviderConfig);
@@ -48,10 +50,12 @@ export declare class ExtensionBrowserProvider {
48
50
  protocolVersion: number | null;
49
51
  expectedProtocolVersion: number;
50
52
  extensionVersion: string | null;
53
+ principalId: string | null;
51
54
  };
52
55
  private protocolMismatchMessage;
53
56
  /** Shutdown the WebSocket server. */
54
57
  shutdown(): Promise<void>;
55
58
  private _handleMessage;
59
+ private _authenticateCandidate;
56
60
  }
57
61
  export declare function isBrowserWireResult(value: unknown): value is BrowserWireResult;
@@ -1,5 +1,10 @@
1
1
  import { createLogger } from "../../utils/logger/index.js";
2
2
  import { init_logger } from "../../utils/logger.js";
3
+ import { deleteBrowserTabBindingsByPrincipal, getBrowserTabBindingById } from "../../storage/sqlite/browser-tab-binding-repository.js";
4
+ import { getDevice } from "../../storage/sqlite/device-access-repository.js";
5
+ import { isChromeExtensionOrigin } from "../../gateway/security/origin-check.js";
6
+ import { browserWireAuthenticationPayload } from "../../../packages/browser-control-contract/src/index.js";
7
+ import crypto from "node:crypto";
3
8
  //#region src/browser/providers/extension.ts
4
9
  /**
5
10
  * Extension browser provider — connects to the xopc Chrome Extension via WebSocket.
@@ -29,6 +34,8 @@ var ExtensionBrowserProvider = class {
29
34
  connected = false;
30
35
  extensionProtocolVersion = null;
31
36
  extensionVersion = null;
37
+ connectionId = null;
38
+ principalId = null;
32
39
  config;
33
40
  connectionWaiters = [];
34
41
  constructor(config) {
@@ -62,20 +69,56 @@ var ExtensionBrowserProvider = class {
62
69
  server: this.server,
63
70
  path: "/browser-ext"
64
71
  });
65
- this.wss.on("connection", (ws) => {
66
- log.info("Chrome Extension connected");
67
- const previousClient = this.clientWs;
68
- this.clientWs = ws;
69
- this.socketConnected = true;
70
- this.handshakeReceived = false;
71
- this.connected = false;
72
- this.extensionProtocolVersion = null;
73
- this.extensionVersion = null;
74
- if (previousClient && previousClient !== ws) try {
75
- previousClient.close();
76
- } catch {}
72
+ this.wss.on("connection", (ws, request) => {
73
+ const origin = request.headers.origin?.toLowerCase();
74
+ if (!isChromeExtensionOrigin(origin)) {
75
+ ws.close(4403, "Chrome extension origin required");
76
+ return;
77
+ }
78
+ const challenge = {
79
+ type: "auth_challenge",
80
+ protocolVersion: 4,
81
+ connectionId: crypto.randomUUID(),
82
+ challenge: crypto.randomBytes(32).toString("base64url"),
83
+ issuedAt: Date.now()
84
+ };
85
+ let authenticated = false;
86
+ const authenticationTimer = setTimeout(() => {
87
+ if (!authenticated) ws.close(4401, "Browser authentication timed out");
88
+ }, 5e3);
89
+ ws.send(JSON.stringify(challenge));
77
90
  ws.on("message", (data) => {
78
- this._handleMessage(data.toString(), ws);
91
+ if (authenticated) {
92
+ this._handleMessage(data.toString(), ws);
93
+ return;
94
+ }
95
+ if (!this._authenticateCandidate(data.toString(), origin, challenge)) {
96
+ clearTimeout(authenticationTimer);
97
+ ws.close(4401, "Browser authentication failed");
98
+ return;
99
+ }
100
+ authenticated = true;
101
+ clearTimeout(authenticationTimer);
102
+ const message = JSON.parse(data.toString());
103
+ const previousClient = this.clientWs;
104
+ this.clientWs = ws;
105
+ this.socketConnected = true;
106
+ this.handshakeReceived = true;
107
+ this.connected = true;
108
+ this.extensionProtocolVersion = message.protocolVersion;
109
+ this.extensionVersion = message.extensionVersion;
110
+ this.connectionId = message.connectionId;
111
+ this.principalId = message.principalId;
112
+ if (previousClient && previousClient !== ws) try {
113
+ previousClient.close(4001, "Connection replaced");
114
+ } catch {}
115
+ for (const waiter of this.connectionWaiters) waiter.resolve();
116
+ this.connectionWaiters = [];
117
+ log.info({
118
+ principalId: message.principalId,
119
+ extensionId: message.extensionId,
120
+ connectionId: message.connectionId
121
+ }, "Chrome extension authenticated");
79
122
  });
80
123
  ws.on("close", (code, reason) => {
81
124
  if (this.clientWs !== ws) return;
@@ -84,12 +127,16 @@ var ExtensionBrowserProvider = class {
84
127
  reason: reason.toString(),
85
128
  pendingCount: this.pending.size
86
129
  }, "Chrome Extension disconnected");
130
+ const disconnectedPrincipalId = this.principalId;
87
131
  this.clientWs = null;
88
132
  this.socketConnected = false;
89
133
  this.handshakeReceived = false;
90
134
  this.connected = false;
91
135
  this.extensionProtocolVersion = null;
92
136
  this.extensionVersion = null;
137
+ this.connectionId = null;
138
+ this.principalId = null;
139
+ if (disconnectedPrincipalId) deleteBrowserTabBindingsByPrincipal(disconnectedPrincipalId);
93
140
  for (const [id, req] of this.pending) {
94
141
  clearTimeout(req.timer);
95
142
  req.reject(/* @__PURE__ */ new Error("Extension disconnected"));
@@ -145,7 +192,7 @@ var ExtensionBrowserProvider = class {
145
192
  /** Wait for the Chrome Extension to connect. */
146
193
  async waitForConnection(timeoutMs) {
147
194
  if (this.connected) return;
148
- if (this.socketConnected && this.handshakeReceived && this.extensionProtocolVersion !== 3) throw new Error(this.protocolMismatchMessage());
195
+ if (this.socketConnected && this.handshakeReceived && this.extensionProtocolVersion !== 4) throw new Error(this.protocolMismatchMessage());
149
196
  const timeout = timeoutMs ?? this.config.connectionTimeout;
150
197
  return new Promise((resolve, reject) => {
151
198
  const timer = setTimeout(() => {
@@ -171,10 +218,15 @@ var ExtensionBrowserProvider = class {
171
218
  const detail = this.socketConnected ? this.protocolMismatchMessage() : "Extension not connected. Ensure the Chrome Extension is installed and connected.";
172
219
  throw new Error(detail);
173
220
  }
221
+ if (input.target?.kind === "attached_tab") {
222
+ const binding = getBrowserTabBindingById(input.target.bindingId);
223
+ if (!binding || binding.principalId !== this.principalId) throw new Error("Attached tab binding does not belong to the connected browser");
224
+ }
174
225
  const id = `cmd_${++this.commandCounter}_${Date.now()}`;
175
226
  const cmd = {
176
227
  id,
177
- protocolVersion: 3,
228
+ protocolVersion: 4,
229
+ connectionId: this.connectionId,
178
230
  input,
179
231
  timeoutMs: timeoutMs ?? this.config.commandTimeout,
180
232
  visualFallback
@@ -207,12 +259,13 @@ var ExtensionBrowserProvider = class {
207
259
  socketConnected: this.socketConnected,
208
260
  connected: this.connected,
209
261
  protocolVersion: this.extensionProtocolVersion,
210
- expectedProtocolVersion: 3,
211
- extensionVersion: this.extensionVersion
262
+ expectedProtocolVersion: 4,
263
+ extensionVersion: this.extensionVersion,
264
+ principalId: this.principalId
212
265
  };
213
266
  }
214
267
  protocolMismatchMessage() {
215
- return `Browser extension protocol mismatch (expected 3, received ${this.extensionProtocolVersion ?? "unknown"}). Reload the extension in Chrome.`;
268
+ return `Browser extension protocol mismatch (expected 4, received ${this.extensionProtocolVersion ?? "unknown"}). Reload the extension in Chrome.`;
216
269
  }
217
270
  /** Shutdown the WebSocket server. */
218
271
  async shutdown() {
@@ -246,32 +299,19 @@ var ExtensionBrowserProvider = class {
246
299
  this.handshakeReceived = false;
247
300
  this.extensionProtocolVersion = null;
248
301
  this.extensionVersion = null;
302
+ this.connectionId = null;
303
+ this.principalId = null;
249
304
  log.info("Extension provider shut down");
250
305
  }
251
306
  _handleMessage(raw, sourceWs) {
252
307
  if (sourceWs !== this.clientWs) return;
253
308
  try {
254
309
  const msg = JSON.parse(raw);
310
+ if (!this.connectionId || msg.connectionId !== this.connectionId) return;
255
311
  if (msg.type === "status") {
256
312
  const status = msg;
257
- this.handshakeReceived = true;
258
- this.extensionProtocolVersion = typeof status.protocolVersion === "number" ? status.protocolVersion : null;
259
- this.extensionVersion = typeof status.extensionVersion === "string" ? status.extensionVersion : null;
260
- this.connected = status.protocolVersion === 3;
261
- if (this.connected) {
262
- for (const waiter of this.connectionWaiters) waiter.resolve();
263
- this.connectionWaiters = [];
264
- log.debug(msg, "Compatible browser extension connected");
265
- } else {
266
- const error = new Error(this.protocolMismatchMessage());
267
- for (const waiter of this.connectionWaiters) waiter.reject(error);
268
- this.connectionWaiters = [];
269
- log.warn({
270
- expectedProtocolVersion: 3,
271
- extensionProtocolVersion: this.extensionProtocolVersion,
272
- extensionVersion: this.extensionVersion
273
- }, "Browser extension protocol mismatch; reload the extension");
274
- }
313
+ if (status.principalId !== this.principalId) return;
314
+ log.debug({ sessionCount: status.sessionCount }, "Browser extension status updated");
275
315
  return;
276
316
  }
277
317
  if (isBrowserWireKeepAlive(msg)) return;
@@ -297,14 +337,40 @@ var ExtensionBrowserProvider = class {
297
337
  log.error({ err: e }, "Failed to parse extension message");
298
338
  }
299
339
  }
340
+ _authenticateCandidate(raw, origin, challenge) {
341
+ try {
342
+ const message = JSON.parse(raw);
343
+ if (message.type !== "authenticate" || message.protocolVersion !== 4 || message.connectionId !== challenge.connectionId || `chrome-extension://${message.extensionId}` !== origin || Math.abs(Date.now() - challenge.issuedAt) > 5e3) return false;
344
+ const device = getDevice(message.principalId);
345
+ if (!device || device.revokedAt !== void 0 || device.platform !== "chrome" || device.extensionId !== message.extensionId) return false;
346
+ const payload = browserWireAuthenticationPayload({
347
+ protocolVersion: message.protocolVersion,
348
+ connectionId: message.connectionId,
349
+ challenge: challenge.challenge,
350
+ issuedAt: challenge.issuedAt,
351
+ principalId: message.principalId,
352
+ extensionId: message.extensionId,
353
+ extensionVersion: message.extensionVersion
354
+ });
355
+ return crypto.verify("sha256", Buffer.from(payload), {
356
+ key: crypto.createPublicKey({
357
+ key: device.publicKeyJwk,
358
+ format: "jwk"
359
+ }),
360
+ dsaEncoding: "ieee-p1363"
361
+ }, Buffer.from(message.signature, "base64url"));
362
+ } catch {
363
+ return false;
364
+ }
365
+ }
300
366
  };
301
367
  function isBrowserWireResult(value) {
302
368
  if (!value || typeof value !== "object") return false;
303
369
  const result = value.result;
304
- return typeof value.id === "string" && Boolean(result) && typeof result === "object" && typeof result.ok === "boolean";
370
+ return typeof value.id === "string" && typeof value.connectionId === "string" && Boolean(result) && typeof result === "object" && typeof result.ok === "boolean";
305
371
  }
306
372
  function isBrowserWireKeepAlive(value) {
307
- return Boolean(value) && typeof value === "object" && value.type === "keepalive" && typeof value.timestamp === "number";
373
+ return Boolean(value) && typeof value === "object" && value.type === "keepalive" && typeof value.connectionId === "string" && typeof value.timestamp === "number";
308
374
  }
309
375
  //#endregion
310
376
  export { ExtensionBrowserProvider, isBrowserWireResult };
@@ -1,4 +1,4 @@
1
- import type { BrowserActionInput, BrowserControlResult } from '@xopcai/browser-control-contract';
1
+ import type { BrowserActionInput, BrowserControlResult, BrowserTarget } from '@xopcai/browser-control-contract';
2
2
  import type { Config } from '../../config/schema.js';
3
3
  import type { BrowserDriver } from '../drivers/browser-driver.js';
4
4
  export interface BrowserRuntimeOptions {
@@ -6,6 +6,7 @@ export interface BrowserRuntimeOptions {
6
6
  createDriver: () => Promise<BrowserDriver>;
7
7
  allowedUploadRoots?: string[];
8
8
  emit?: (type: string, payload: unknown) => void;
9
+ resolveTarget?: (taskKey: string) => BrowserTarget | undefined;
9
10
  }
10
11
  export declare class BrowserRuntime {
11
12
  private readonly options;
@@ -14,10 +14,14 @@ var BrowserRuntime = class {
14
14
  this.evictExpiredSessions();
15
15
  if (!this.options.getConfig().enabled) return failure("DRIVER_UNAVAILABLE", "Browser Control is disabled.");
16
16
  if (signal?.aborted) return failure("ABORTED", "Operation was aborted.");
17
+ const effectiveInput = input.target ? input : {
18
+ ...input,
19
+ target: this.options.resolveTarget?.(taskKey)
20
+ };
17
21
  let session;
18
22
  let driver;
19
23
  try {
20
- session = await this.resolveSession(taskKey, input.sessionId);
24
+ session = await this.resolveSession(taskKey, effectiveInput.sessionId);
21
25
  if (!session) return failure("SESSION_NOT_FOUND", "The browser session does not belong to this task.");
22
26
  driver = await this.getDriver();
23
27
  } catch (error) {
@@ -30,7 +34,7 @@ var BrowserRuntime = class {
30
34
  });
31
35
  let result;
32
36
  try {
33
- result = await this.dispatch(driver, session, input, signal);
37
+ result = await this.dispatch(driver, session, effectiveInput, signal);
34
38
  result = this.constrainResult(result);
35
39
  } catch (error) {
36
40
  result = failure("DRIVER_UNAVAILABLE", error instanceof Error ? error.message : String(error));
@@ -1,7 +1,7 @@
1
1
  import { register } from "../registry.js";
2
2
  import { Command } from "commander";
3
3
  //#region src/cli/commands/browser.ts
4
- function createBrowserCommand(_ctx) {
4
+ function createBrowserCommand(ctx) {
5
5
  const command = new Command("browser").description("Browser Control v2 diagnostics and extension setup");
6
6
  command.command("doctor").description("Check the configured browser driver").action(async () => {
7
7
  const { doctorCli } = await import("./browser-cli-helpers.js");
@@ -13,8 +13,18 @@ function createBrowserCommand(_ctx) {
13
13
  console.log(JSON.stringify(await browserExtDoctor(), null, 2));
14
14
  });
15
15
  extension.command("install").description("Install extension artifacts").action(async () => {
16
- const { ensureBrowserExtensionArtifacts } = await import("../../browser/providers/browser-ext-install.js");
17
- console.log(JSON.stringify(await ensureBrowserExtensionArtifacts(), null, 2));
16
+ const { ensureBrowserExtensionArtifacts, installBrowserNativeMessagingHost } = await import("../../browser/providers/browser-ext-install.js");
17
+ const artifacts = await ensureBrowserExtensionArtifacts();
18
+ const nativeHost = await installBrowserNativeMessagingHost({ configPath: ctx.configPath });
19
+ console.log(JSON.stringify({
20
+ ...artifacts,
21
+ nativeHost
22
+ }, null, 2));
23
+ });
24
+ extension.command("native-host").description("Run the Chrome native messaging bootstrap host").action(async () => {
25
+ process.env.XOPC_LOG_CONSOLE = "false";
26
+ const { runBrowserNativeMessagingHost } = await import("../../browser/native-messaging-host.js");
27
+ await runBrowserNativeMessagingHost(ctx.configPath);
18
28
  });
19
29
  return command;
20
30
  }
@@ -52,7 +52,7 @@ async function checkDatabaseSchema(ctx) {
52
52
  id: "database-schema",
53
53
  label: "Database schema",
54
54
  status: "pass",
55
- message: `SQLite schema v${status.dbVersion} (current release v154).`,
55
+ message: `SQLite schema v${status.dbVersion} (current release v156).`,
56
56
  hints: []
57
57
  };
58
58
  }
@@ -2,6 +2,7 @@ import { resolveStateDir } from "../config/paths-state.js";
2
2
  import { createLogger } from "../utils/logger/index.js";
3
3
  import { init_logger } from "../utils/logger.js";
4
4
  import { deleteEndpointSessionBinding, getEndpointSessionBinding, setEndpointSessionBinding } from "../storage/sqlite/endpoint-session-binding-repository.js";
5
+ import { deleteBrowserTabBindingsByEndpoint } from "../storage/sqlite/browser-tab-binding-repository.js";
5
6
  import { bindEndpointPrincipal, getEndpointPrincipal, touchEndpointPrincipal } from "../storage/sqlite/endpoint-principal-repository.js";
6
7
  import { finishEndpointToolInvocationAudit, startEndpointToolInvocationAudit } from "../storage/sqlite/endpoint-tool-invocation-repository.js";
7
8
  import "../storage/sqlite/index.js";
@@ -75,6 +76,7 @@ var EndpointToolRuntime = class {
75
76
  remove(endpointId, connectionId) {
76
77
  if (!this.registry.remove(endpointId, connectionId)) return;
77
78
  this.invocations.failEndpoint(endpointId);
79
+ deleteBrowserTabBindingsByEndpoint(endpointId);
78
80
  log.info({
79
81
  endpointId,
80
82
  connectionId
@@ -56,7 +56,7 @@ var ChatStreamMapper = class {
56
56
  status: event.status === "error" ? "error" : "complete",
57
57
  ...typeof event.message === "string" && event.message ? { message: event.message } : {}
58
58
  })];
59
- case "user_message": return [this.make("user_message", { message: userMessageFromEvent(event) })];
59
+ case "user_message": return [this.make("user_message", { message: userMessageFromEvent(event, this.opts.runId) })];
60
60
  case "user_transcript": return [this.make("user_transcript", {
61
61
  text: String(event.text ?? ""),
62
62
  media: event.media
@@ -427,9 +427,10 @@ var ChatStreamMapper = class {
427
427
  }
428
428
  }
429
429
  };
430
- function userMessageFromEvent(event) {
430
+ function userMessageFromEvent(event, turnId) {
431
431
  return {
432
432
  role: "user",
433
+ turnId,
433
434
  content: event.content ?? [],
434
435
  attachments: event.media,
435
436
  metadata: userMessageDisplayMetadata(event.metadata),
@@ -1,5 +1,6 @@
1
1
  import { createLogger } from "../../utils/logger/index.js";
2
2
  import { init_logger } from "../../utils/logger.js";
3
+ import { listDevices } from "../../storage/sqlite/device-access-repository.js";
3
4
  import { resolveAllowedBrowserOrigins, resolveGatewayServiceListenPort } from "../host.js";
4
5
  import { resolveGatewayEffectiveHost } from "../../config/gateway-bind.js";
5
6
  import { loadTunnelState } from "../../tunnel/tunnel-state.js";
@@ -7,7 +8,7 @@ import { resolveReverseProxyPublicUrl } from "../public-url.js";
7
8
  import { maxSessionInputRequestBodyBytes } from "../chat-limits.js";
8
9
  import { isLoopbackIpAddress, isTrustedProxyAddress } from "../client-ip.js";
9
10
  import { buildGatewayConsoleCspHeader } from "../security/csp.js";
10
- import { checkBrowserOrigin } from "../security/origin-check.js";
11
+ import { checkBrowserOrigin, isChromeExtensionOrigin } from "../security/origin-check.js";
11
12
  import { auth } from "./middleware/auth.js";
12
13
  import { gatewayScopes } from "./middleware/scopes.js";
13
14
  import { createChannelRateLimitMiddleware, createChatRateLimitMiddleware, createMediaRateLimitMiddleware, createStrictRateLimitMiddleware, createTaskRateLimitMiddleware, createXopcCloudPollRateLimitMiddleware } from "./middleware/strict-rate-limit.js";
@@ -52,6 +53,19 @@ function createHonoApp(config) {
52
53
  tunnelPublicUrl: loadTunnelState()?.publicUrl,
53
54
  reverseProxyPublicUrl: resolveReverseProxyPublicUrl(service.currentConfig)
54
55
  });
56
+ let loggedUnavailableDeviceStore = false;
57
+ const resolvePairedBrowserExtensionOrigins = () => {
58
+ try {
59
+ return listDevices().filter((device) => device.platform === "chrome" && !device.revokedAt && device.extensionId).map((device) => `chrome-extension://${device.extensionId}`);
60
+ } catch (cause) {
61
+ if (!loggedUnavailableDeviceStore) {
62
+ loggedUnavailableDeviceStore = true;
63
+ log.warn({ err: cause }, "Browser extension origins unavailable until the device store opens");
64
+ }
65
+ return [];
66
+ }
67
+ };
68
+ const resolveAllBrowserOrigins = () => [...resolveBrowserOrigins(), ...resolvePairedBrowserExtensionOrigins()];
55
69
  /**
56
70
  * TCP source for the in-flight request, normalized for trusted-proxy checks.
57
71
  * Returns undefined when the runtime doesn't expose conninfo (tests, mocks).
@@ -84,9 +98,10 @@ function createHonoApp(config) {
84
98
  registerSiteShareMiddleware(app, service);
85
99
  app.use(cors({
86
100
  origin: (origin) => {
87
- const allowed = resolveBrowserOrigins();
101
+ const allowed = resolveAllBrowserOrigins();
88
102
  if (!origin) return allowed[0] ?? `http://127.0.0.1:${gatewayPort}`;
89
103
  const normalized = origin.toLowerCase();
104
+ if (isChromeExtensionOrigin(normalized)) return origin;
90
105
  if (allowed.find((entry) => entry.toLowerCase() === normalized)) return origin;
91
106
  return allowed.includes("*") ? "*" : "";
92
107
  },
@@ -123,10 +138,11 @@ function createHonoApp(config) {
123
138
  if (isExtensionGatewayUiAssetPath(c.req.path)) return next();
124
139
  const origin = c.req.header("origin");
125
140
  if (!origin || origin.trim().toLowerCase() === "null") return next();
141
+ if (isChromeExtensionOrigin(origin) && (c.req.path.startsWith("/api/device-pairing/") || c.req.path === "/api/device-auth/refresh")) return next();
126
142
  const result = checkBrowserOrigin({
127
143
  requestHost: c.req.header("host"),
128
144
  origin,
129
- allowedOrigins: resolveBrowserOrigins(),
145
+ allowedOrigins: resolveAllBrowserOrigins(),
130
146
  allowHostHeaderOriginFallback,
131
147
  autoAllowSameHostFromTrustedProxy: isRequestFromTrustedProxy(c),
132
148
  isLocalClient: false
@@ -19,4 +19,7 @@ export declare function auth(config?: AuthConfig): import("hono").MiddlewareHand
19
19
  }, 429, "json">) | (Response & import("hono").TypedResponse<{
20
20
  error: string;
21
21
  code: string;
22
- }, 401, "json">)>;
22
+ }, 401, "json">) | (Response & import("hono").TypedResponse<{
23
+ error: string;
24
+ code: string;
25
+ }, 403, "json">)>;