@xopcai/xopc 0.0.266 → 0.0.267

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 (115) hide show
  1. package/dist/browser-ext/dist/assets/{auth-CknJ00ae.js → auth-D_IL6naL.js} +603 -203
  2. package/dist/browser-ext/dist/assets/sidepanel-CEPaVLjr.css +155 -0
  3. package/dist/browser-ext/dist/background.js +698 -169
  4. package/dist/browser-ext/dist/sidepanel.html +3 -3
  5. package/dist/browser-ext/dist/sidepanel.js +148 -391
  6. package/dist/browser-ext/manifest.json +3 -6
  7. package/dist/extensions/telegram/xopc.extension.json +1 -1
  8. package/dist/gateway/static/root/assets/{agent-avatar-dicebear-Qrp5ALCF.js → agent-avatar-dicebear-ZrSAMchm.js} +1 -1
  9. package/dist/gateway/static/root/assets/{agent-defaults-settings-panel-DwinST48.js → agent-defaults-settings-panel-DiNFN8qS.js} +1 -1
  10. package/dist/gateway/static/root/assets/{agents-Dlr0YyNt.js → agents-Bu6Hf59K.js} +1 -1
  11. package/dist/gateway/static/root/assets/{app-management-settings-panel-bLNvz93c.js → app-management-settings-panel-D-L6yjS7.js} +1 -1
  12. package/dist/gateway/static/root/assets/{appearance-settings-D5qgIepF.js → appearance-settings-BW5eb53j.js} +1 -1
  13. package/dist/gateway/static/root/assets/{apps-page-BQVlXhIb.js → apps-page-CiQIdz3N.js} +1 -1
  14. package/dist/gateway/static/root/assets/{archive-plugin-BEA7VAA4.js → archive-plugin-DwXvu1p9.js} +1 -1
  15. package/dist/gateway/static/root/assets/{attachment-load-B1RyF3uu.js → attachment-load-DqU2BYBp.js} +1 -1
  16. package/dist/gateway/static/root/assets/{automations-page-BfH0FdKa.js → automations-page-C7qoj9Sd.js} +1 -1
  17. package/dist/gateway/static/root/assets/{automations-workspace-C37AlD3E.js → automations-workspace-CIARTLHO.js} +1 -1
  18. package/dist/gateway/static/root/assets/{binary-plugins-EU884ZXi.js → binary-plugins-CfplDak2.js} +1 -1
  19. package/dist/gateway/static/root/assets/{block-editor-Bjr3naHb.js → block-editor-CzLs38ru.js} +1 -1
  20. package/dist/gateway/static/root/assets/{browser-automations-page-CDrYrNGy.js → browser-automations-page-_SKi8s9b.js} +1 -1
  21. package/dist/gateway/static/root/assets/browser-settings-page-DTdx66sX.js +8 -0
  22. package/dist/gateway/static/root/assets/{capabilities-settings-panel-slt1o04n.js → capabilities-settings-panel-Ck9U0Olx.js} +1 -1
  23. package/dist/gateway/static/root/assets/{channels-settings-oHTTX6Jy.js → channels-settings-I_vDpbnl.js} +1 -1
  24. package/dist/gateway/static/root/assets/{chat-terminal-dock-DEYQdv6n.js → chat-terminal-dock-snvAu3hJ.js} +1 -1
  25. package/dist/gateway/static/root/assets/{connectors-page-olrbxHIT.js → connectors-page-BmKTnzo1.js} +1 -1
  26. package/dist/gateway/static/root/assets/{cron-expression-DFrbDWpP.js → cron-expression-BhGZSbT6.js} +1 -1
  27. package/dist/gateway/static/root/assets/{date-picker-CYriYKpH.js → date-picker-Cep5f9b-.js} +1 -1
  28. package/dist/gateway/static/root/assets/{dependency-picker-XY3qUErs.js → dependency-picker-BzSEBQku.js} +1 -1
  29. package/dist/gateway/static/root/assets/{desktop-pet-Dsc__ynA.js → desktop-pet-BwX_6Gah.js} +1 -1
  30. package/dist/gateway/static/root/assets/{desktop-pet-settings-EpuX3C08.js → desktop-pet-settings-WrbVsU2b.js} +1 -1
  31. package/dist/gateway/static/root/assets/{extension-debug-page-B_NeLV4K.js → extension-debug-page-BdXSwtAZ.js} +1 -1
  32. package/dist/gateway/static/root/assets/{extension-page-D3JC6mLB.js → extension-page-I95ukbRb.js} +1 -1
  33. package/dist/gateway/static/root/assets/{extension-settings-page-CXTQt1lp.js → extension-settings-page-8zUisBay.js} +1 -1
  34. package/dist/gateway/static/root/assets/{gateway-settings-yeYm_UPE.js → gateway-settings-DGj5b9Yv.js} +1 -1
  35. package/dist/gateway/static/root/assets/{heartbeat-settings-coh1LFsg.js → heartbeat-settings-BGQxU-Ug.js} +1 -1
  36. package/dist/gateway/static/root/assets/{home-page-CAgoBZqp.js → home-page-BZPpK0A3.js} +1 -1
  37. package/dist/gateway/static/root/assets/{index-Cn__9Un2.js → index-D2OtD9Wa.js} +7 -7
  38. package/dist/gateway/static/root/assets/{keyboard-shortcuts-settings-Cu1bI3Bg.js → keyboard-shortcuts-settings-BUe_om99.js} +1 -1
  39. package/dist/gateway/static/root/assets/{local-app-workbench-page-C9s2lFaI.js → local-app-workbench-page-CbQnffS9.js} +1 -1
  40. package/dist/gateway/static/root/assets/{local-apps-page-CJ03Umjr.js → local-apps-page-BZaLzfms.js} +1 -1
  41. package/dist/gateway/static/root/assets/{logs-page-CY_DQKJ2.js → logs-page-WWCHqW_x.js} +1 -1
  42. package/dist/gateway/static/root/assets/{management-settings-YPEemeMo.js → management-settings-Duf8w-Bl.js} +1 -1
  43. package/dist/gateway/static/root/assets/{media-plugins-BjT6jlrA.js → media-plugins-2-QfzHmg.js} +1 -1
  44. package/dist/gateway/static/root/assets/{mobile-pairing-wizard-A8eLtV6v.js → mobile-pairing-wizard-DRaVIAt1.js} +1 -1
  45. package/dist/gateway/static/root/assets/{note-detail-panel-DHT2QksJ.js → note-detail-panel-BT-fWGxX.js} +3 -3
  46. package/dist/gateway/static/root/assets/{notes-page-BRFQiivM.js → notes-page-C4705PzN.js} +1 -1
  47. package/dist/gateway/static/root/assets/{page-tabs-DNUwulnl.js → page-tabs-DaZ7QpNO.js} +1 -1
  48. package/dist/gateway/static/root/assets/{preview-open-alternatives-B33KsKkJ.js → preview-open-alternatives-CWEuqCmv.js} +1 -1
  49. package/dist/gateway/static/root/assets/{project-detail-page-CItEFVt9.js → project-detail-page-BZegJ6an.js} +1 -1
  50. package/dist/gateway/static/root/assets/{projects-page-Bmy-c_lL.js → projects-page-B7hUHeLI.js} +1 -1
  51. package/dist/gateway/static/root/assets/{remote-access-hub-H6B1cA2V.js → remote-access-hub-BE9hZpdh.js} +1 -1
  52. package/dist/gateway/static/root/assets/{runtime-tools-settings-panel-BsCovC39.js → runtime-tools-settings-panel-CslkSM-m.js} +1 -1
  53. package/dist/gateway/static/root/assets/{schema-form-NsPb_Vam.js → schema-form-B2gvVrBq.js} +1 -1
  54. package/dist/gateway/static/root/assets/{sessions-page-CyYLSXk_.js → sessions-page-DmAAPMo1.js} +1 -1
  55. package/dist/gateway/static/root/assets/settings-advanced-gate-CvBocGDA.js +1 -0
  56. package/dist/gateway/static/root/assets/{settings-form-section-BzPIPe1-.js → settings-form-section-CmlnuSO3.js} +1 -1
  57. package/dist/gateway/static/root/assets/{settings-page-4idpMhsw.js → settings-page-DWWgyelT.js} +1 -1
  58. package/dist/gateway/static/root/assets/{setup-status-panel-D2ds_TTD.js → setup-status-panel-CJrN3m1o.js} +1 -1
  59. package/dist/gateway/static/root/assets/{share-preview-page-Djo3VTaC.js → share-preview-page-C12oyIKk.js} +1 -1
  60. package/dist/gateway/static/root/assets/{shares-settings-fhRzNfRp.js → shares-settings-D4BtSiLq.js} +1 -1
  61. package/dist/gateway/static/root/assets/{skill-reload-api-Ct8tUju_.js → skill-reload-api-CzKGn056.js} +1 -1
  62. package/dist/gateway/static/root/assets/{skills-page-riM7FytZ.js → skills-page-yhW7LLqk.js} +1 -1
  63. package/dist/gateway/static/root/assets/{skills-page.utils-B3G8LBb-.js → skills-page.utils-FaTlq8t-.js} +1 -1
  64. package/dist/gateway/static/root/assets/{system-settings-panel-CW91jjnB.js → system-settings-panel-CMrkoJLN.js} +1 -1
  65. package/dist/gateway/static/root/assets/{task-detail-page-BBXKvl0k.js → task-detail-page-BUaO62cy.js} +1 -1
  66. package/dist/gateway/static/root/assets/{tunnel-api-DILER0Yb.js → tunnel-api-C2tAZaJz.js} +1 -1
  67. package/dist/gateway/static/root/assets/{use-autosave-GxnP8vLO.js → use-autosave-BsDRt4QI.js} +1 -1
  68. package/dist/gateway/static/root/assets/{user-model-page-I1H4XRfA.js → user-model-page-Dy8h1SSJ.js} +1 -1
  69. package/dist/gateway/static/root/assets/{voice-api-key-field-CI3sXyl4.js → voice-api-key-field-CS5heYMP.js} +1 -1
  70. package/dist/gateway/static/root/assets/{work-discovery-overlay-DZC9Exn3.js → work-discovery-overlay-BeSz5Th5.js} +1 -1
  71. package/dist/gateway/static/root/assets/{workflow-run-setup-panel-CaBaQER2.js → workflow-run-setup-panel-BebW0CDK.js} +1 -1
  72. package/dist/gateway/static/root/assets/{workflows-page-C-_Dxihw.js → workflows-page-CU8jFgPF.js} +1 -1
  73. package/dist/gateway/static/root/index.html +1 -1
  74. package/dist/package.js +1 -1
  75. package/dist/packages/browser-control-contract/src/index.js +30 -1
  76. package/dist/src/agent/external-tools/endpoint-provider.js +3 -1
  77. package/dist/src/agent/tools/factory.js +1 -1
  78. package/dist/src/browser/automations/runtime-service.d.ts +2 -0
  79. package/dist/src/browser/automations/runtime-service.js +1 -1
  80. package/dist/src/browser/drivers/create-driver.d.ts +2 -1
  81. package/dist/src/browser/drivers/create-driver.js +4 -6
  82. package/dist/src/browser/drivers/extension-driver.d.ts +7 -2
  83. package/dist/src/browser/enrollment.d.ts +8 -0
  84. package/dist/src/browser/enrollment.js +25 -0
  85. package/dist/src/browser/extension-identity.d.ts +2 -0
  86. package/dist/src/browser/extension-identity.js +5 -0
  87. package/dist/src/browser/native-messaging-host.d.ts +6 -3
  88. package/dist/src/browser/native-messaging-host.js +23 -5
  89. package/dist/src/browser/providers/browser-ext-install.d.ts +4 -1
  90. package/dist/src/browser/providers/browser-ext-install.js +46 -7
  91. package/dist/src/browser/providers/extension.js +1 -1
  92. package/dist/src/browser/providers/realtime-extension.d.ts +29 -0
  93. package/dist/src/browser/providers/realtime-extension.js +83 -0
  94. package/dist/src/cli/commands/doctor/checks/database-schema.js +1 -1
  95. package/dist/src/endpoint-tools/policy.js +10 -3
  96. package/dist/src/gateway/hono/app.js +6 -1
  97. package/dist/src/gateway/hono/routes/browser.js +20 -30
  98. package/dist/src/gateway/security/origin-check.js +5 -0
  99. package/dist/src/gateway/service/config-coordinator.d.ts +0 -2
  100. package/dist/src/gateway/service/config-coordinator.js +0 -2
  101. package/dist/src/gateway/service.d.ts +0 -11
  102. package/dist/src/gateway/service.js +7 -85
  103. package/dist/src/storage/sqlite/device-pairing-approval.js +24 -3
  104. package/dist/src/storage/sqlite/device-pairing-repository.d.ts +10 -1
  105. package/dist/src/storage/sqlite/device-pairing-repository.js +6 -5
  106. package/dist/src/storage/sqlite/migrations/157_browser_endpoint_principals.sql +50 -0
  107. package/dist/src/storage/sqlite/migrations/158_browser_native_enrollment.sql +4 -0
  108. package/dist/src/storage/sqlite/migrations/runner.d.ts +1 -1
  109. package/dist/src/storage/sqlite/migrations/runner.js +3 -3
  110. package/dist/src/storage/sqlite/migrations/runner.ts +1 -1
  111. package/dist/src/storage/sqlite/schema.js +1 -1
  112. package/package.json +3 -3
  113. package/dist/browser-ext/dist/assets/sidepanel-C69P3v9u.css +0 -113
  114. package/dist/gateway/static/root/assets/browser-settings-page-DOmUkj2k.js +0 -8
  115. package/dist/gateway/static/root/assets/settings-advanced-gate-3T4rPk4A.js +0 -1
@@ -1,6 +1,7 @@
1
1
  import { deleteBrowserTabBinding, getBrowserTabBinding, setBrowserTabBinding } from "../../../storage/sqlite/browser-tab-binding-repository.js";
2
2
  import { browserTabBindingRequestSchema } from "../../../../packages/gateway-contract/src/browser-tab-binding.js";
3
3
  import { getDevice } from "../../../storage/sqlite/device-access-repository.js";
4
+ import "../../../../packages/browser-control-contract/src/index.js";
4
5
  import { decideBrowserApproval, listBrowserApprovals } from "../../../browser/policy/approval-store.js";
5
6
  import { createBrowserDriver } from "../../../browser/drivers/create-driver.js";
6
7
  import { checkBrowserReadiness } from "../../../browser/readiness.js";
@@ -9,34 +10,19 @@ import { getGatewayPrincipal } from "../../security/gateway-principal.js";
9
10
  import { getConnInfo } from "@hono/node-server/conninfo";
10
11
  //#region src/gateway/hono/routes/browser.ts
11
12
  /** Browser Control status, approvals, and driver diagnostics. */
12
- const EXTENSION_ENDPOINT = "http://127.0.0.1:19820/";
13
- async function extensionStatus() {
14
- const { getExtensionBrowserServerSnapshot } = await import("../../../browser/providers/extension-ws-acquire.js");
15
- const snapshot = getExtensionBrowserServerSnapshot();
16
- let running = snapshot.active;
17
- let socketConnected = false;
18
- let connected = false;
19
- let protocolVersion = null;
20
- let expectedProtocolVersion = null;
21
- let extensionVersion = null;
22
- try {
23
- const payload = await (await fetch(EXTENSION_ENDPOINT, { signal: AbortSignal.timeout(2e3) })).json();
24
- running ||= payload.ok === true;
25
- socketConnected = payload.socketConnected ?? payload.connected === true;
26
- connected = payload.connected === true;
27
- protocolVersion = typeof payload.protocolVersion === "number" ? payload.protocolVersion : null;
28
- expectedProtocolVersion = typeof payload.expectedProtocolVersion === "number" ? payload.expectedProtocolVersion : null;
29
- extensionVersion = typeof payload.extensionVersion === "string" ? payload.extensionVersion : null;
30
- } catch {}
13
+ async function extensionStatus(service) {
14
+ const endpoint = service.endpointTools.registry.list().filter((item) => item.kind === "browser" && item.tools.some((tool) => tool.descriptor.name === "browser.control")).sort((left, right) => right.lastHeartbeatAt - left.lastHeartbeatAt)[0];
15
+ const connected = Boolean(endpoint);
16
+ const extensionVersion = endpoint?.appVersion ?? null;
31
17
  const { browserExtDoctor } = await import("../../../browser/providers/browser-ext-install.js");
32
18
  const doctor = await browserExtDoctor({ runtimeExtensionVersion: extensionVersion ?? void 0 });
33
19
  return {
34
- running,
35
- socketConnected,
20
+ running: true,
21
+ socketConnected: connected,
36
22
  connected,
37
23
  driverKind: "extension",
38
- protocolVersion,
39
- expectedProtocolVersion,
24
+ protocolVersion: connected ? 4 : null,
25
+ expectedProtocolVersion: 4,
40
26
  extensionVersion,
41
27
  artifacts: {
42
28
  installed: doctor.installed,
@@ -45,10 +31,12 @@ async function extensionStatus() {
45
31
  xopcVersion: doctor.xopcVersion,
46
32
  installedVersion: doctor.installedVersion,
47
33
  needsRefresh: doctor.needsRefresh,
48
- needsChromeReload: doctor.needsChromeReload || socketConnected && !connected
34
+ needsChromeReload: doctor.needsChromeReload,
35
+ nativeHost: doctor.nativeHost
49
36
  },
50
- bridgeHeld: snapshot.active,
51
- refCount: snapshot.refCount
37
+ bridgeHeld: false,
38
+ refCount: connected ? 1 : 0,
39
+ transport: "gateway-realtime"
52
40
  };
53
41
  }
54
42
  function isLocalOwnerRequest(c, service) {
@@ -156,7 +144,7 @@ function registerBrowserRoutes(authenticated, deps) {
156
144
  });
157
145
  authenticated.get("/api/browser/extension-status", async (c) => {
158
146
  try {
159
- return c.json(await extensionStatus());
147
+ return c.json(await extensionStatus(service));
160
148
  } catch (error) {
161
149
  return c.json({
162
150
  running: false,
@@ -177,7 +165,7 @@ function registerBrowserRoutes(authenticated, deps) {
177
165
  }
178
166
  });
179
167
  const readiness = await checkBrowserReadiness(service.currentConfig);
180
- const driverStatus = config.driver.kind === "extension" ? await extensionStatus().catch((error) => ({ error: error instanceof Error ? error.message : String(error) })) : config.driver.kind === "playwright" ? await import("../../../browser/providers/playwright-doctor.js").then(({ playwrightChromiumDoctor }) => playwrightChromiumDoctor()) : void 0;
168
+ const driverStatus = config.driver.kind === "extension" ? await extensionStatus(service).catch((error) => ({ error: error instanceof Error ? error.message : String(error) })) : config.driver.kind === "playwright" ? await import("../../../browser/providers/playwright-doctor.js").then(({ playwrightChromiumDoctor }) => playwrightChromiumDoctor()) : void 0;
181
169
  const extensionConnected = config.driver.kind === "extension" ? driverStatus.connected === true : true;
182
170
  const extensionProtocolDetail = config.driver.kind === "extension" && "socketConnected" in driverStatus && driverStatus.socketConnected && !driverStatus.connected ? `Chrome extension protocol ${driverStatus.protocolVersion ?? "unknown"} does not match ${driverStatus.expectedProtocolVersion ?? "the gateway"}. Reload the extension in Chrome.` : void 0;
183
171
  return c.json({
@@ -204,7 +192,7 @@ function registerBrowserRoutes(authenticated, deps) {
204
192
  const startedAt = Date.now();
205
193
  let driver;
206
194
  try {
207
- driver = await createBrowserDriver(service.currentConfig.browser);
195
+ driver = await createBrowserDriver(service.currentConfig.browser, service.endpointTools);
208
196
  await driver.connect();
209
197
  return c.json({
210
198
  ok: true,
@@ -229,12 +217,14 @@ function registerBrowserRoutes(authenticated, deps) {
229
217
  }, 403);
230
218
  const body = await c.req.json().catch(() => ({}));
231
219
  try {
232
- const { browserExtDoctor, ensureBrowserExtensionArtifacts } = await import("../../../browser/providers/browser-ext-install.js");
220
+ const { browserExtDoctor, ensureBrowserExtensionArtifacts, installBrowserNativeMessagingHost } = await import("../../../browser/providers/browser-ext-install.js");
233
221
  const result = await ensureBrowserExtensionArtifacts({ force: body.force === true });
222
+ const nativeHost = await installBrowserNativeMessagingHost({ configPath: service.getHealth().configPath });
234
223
  return c.json({
235
224
  ok: true,
236
225
  payload: {
237
226
  ...result,
227
+ nativeHost,
238
228
  doctor: await browserExtDoctor()
239
229
  }
240
230
  });
@@ -15,6 +15,11 @@ function isLoopbackHost(hostname) {
15
15
  function parseOriginHost(originRaw) {
16
16
  const trimmed = (originRaw ?? "").trim();
17
17
  if (!trimmed || trimmed === "null") return null;
18
+ if (isChromeExtensionOrigin(trimmed)) return {
19
+ origin: trimmed.toLowerCase(),
20
+ host: "",
21
+ hostname: ""
22
+ };
18
23
  try {
19
24
  const url = new URL(trimmed);
20
25
  return {
@@ -41,8 +41,6 @@ export interface GatewayConfigCoordinatorOptions {
41
41
  getChannelManager: () => ChannelManager;
42
42
  getHeartbeatService: () => HeartbeatService | null;
43
43
  getExtensionLoader: () => ExtensionLoader | null;
44
- /** Re-evaluate browser-extension server attachment after agent defaults change. */
45
- reconcileBrowserExtensionServer: () => Promise<void>;
46
44
  /** Sync deterministic user-model maintenance automations after config changes. */
47
45
  reconcileMemoryMaintenanceAutomations: () => Promise<void>;
48
46
  /** Latest channel status snapshot for the `channels.status` event. */
@@ -223,7 +223,6 @@ var GatewayConfigCoordinator = class {
223
223
  log.debug("Reloading agent defaults...");
224
224
  this.opts.setConfig(newConfig);
225
225
  this.opts.getAgentService().applyAgentDefaultsFromConfig(newConfig);
226
- this.opts.reconcileBrowserExtensionServer();
227
226
  this.opts.reconcileMemoryMaintenanceAutomations().catch((err) => {
228
227
  const em = err instanceof Error ? err.message : String(err);
229
228
  log.warn({
@@ -350,7 +349,6 @@ var GatewayConfigCoordinator = class {
350
349
  this.opts.setConfig(reloaded);
351
350
  if (sanitizeTunnelConfig(reloaded)) await saveConfig(reloaded, this.opts.configPath);
352
351
  this.opts.getAgentService().applyAgentDefaultsFromConfig(reloaded);
353
- await this.opts.reconcileBrowserExtensionServer();
354
352
  await this.opts.reconcileMemoryMaintenanceAutomations().catch((err) => {
355
353
  const em = err instanceof Error ? err.message : String(err);
356
354
  log.warn({
@@ -39,10 +39,6 @@ export declare class GatewayService {
39
39
  private notesService;
40
40
  private extensionLoader;
41
41
  private extensionMetadataSnapshot;
42
- private browserExtensionProvider;
43
- private browserExtensionRelease;
44
- /** `${host}:${port}` when the gateway holds the extension bridge listener. */
45
- private browserExtensionBindKey;
46
42
  private heartbeatService;
47
43
  private sessionIndex;
48
44
  private running;
@@ -198,13 +194,6 @@ export declare class GatewayService {
198
194
  */
199
195
  onHttpListening(): Promise<void>;
200
196
  stop(): Promise<void>;
201
- /** Start the browser extension WebSocket server when its driver is active. */
202
- private startBrowserExtensionServerIfNeeded;
203
- /**
204
- * Start/stop/rebind the Chrome extension bridge.
205
- * PATCH saves update config in memory without re-running gateway startup, so this must run on save too.
206
- */
207
- reconcileBrowserExtensionServer(): Promise<void>;
208
197
  /**
209
198
  * Start processing outbound messages and send through channels
210
199
  */
@@ -54,12 +54,6 @@ import { registerClarificationChannelRuntime } from "./clarify-runtime.js";
54
54
  import { ChannelManager } from "../channels/manager.js";
55
55
  import { getWorkspacePath } from "../config/workspace-path-helpers.js";
56
56
  import "../config/index.js";
57
- import { DiscussionService } from "../discussions/service.js";
58
- import { DiscussionOrganizer } from "../discussions/organizer.js";
59
- import { DiscussionLiveWorker } from "../discussions/live-worker.js";
60
- import { DiscussionOrganizerWorker } from "../discussions/organizer-worker.js";
61
- import { DiscussionSealer } from "../discussions/sealer.js";
62
- import "../discussions/index.js";
63
57
  import { ManagedComposioEventPoller } from "../connectors/composio-managed-events.js";
64
58
  import { startConnectedKnowledgeCoordinator } from "../knowledge/coordinator.js";
65
59
  import "../knowledge/index.js";
@@ -67,6 +61,12 @@ import { ConnectionRecoveryService } from "../connectors/connection-recovery-ser
67
61
  import { startConnectorLearningCoordinator } from "../connectors/learning-coordinator.js";
68
62
  import { ConnectedSourceChangePublisher } from "../connectors/source-change-publisher.js";
69
63
  import { startConnectorSupervisor } from "../connectors/supervisor.js";
64
+ import { DiscussionService } from "../discussions/service.js";
65
+ import { DiscussionOrganizer } from "../discussions/organizer.js";
66
+ import { DiscussionLiveWorker } from "../discussions/live-worker.js";
67
+ import { DiscussionOrganizerWorker } from "../discussions/organizer-worker.js";
68
+ import { DiscussionSealer } from "../discussions/sealer.js";
69
+ import "../discussions/index.js";
70
70
  import { EndpointToolRuntime } from "../endpoint-tools/runtime.js";
71
71
  import "../endpoint-tools/index.js";
72
72
  import { assertGatewayAuthConfigured, extractToken, resolveGatewayAuth, validateToken } from "./auth.js";
@@ -152,10 +152,6 @@ var GatewayService = class {
152
152
  notesService;
153
153
  extensionLoader = null;
154
154
  extensionMetadataSnapshot = null;
155
- browserExtensionProvider = null;
156
- browserExtensionRelease = null;
157
- /** `${host}:${port}` when the gateway holds the extension bridge listener. */
158
- browserExtensionBindKey = null;
159
155
  heartbeatService = null;
160
156
  sessionIndex;
161
157
  running = false;
@@ -249,6 +245,7 @@ var GatewayService = class {
249
245
  get browserAutomations() {
250
246
  if (!this.browserAutomationService) this.browserAutomationService = createRuntimeBrowserAutomationService({
251
247
  getConfig: () => this.config,
248
+ endpointTools: this.endpointTools,
252
249
  emit: (type, payload) => this.emit(type, payload)
253
250
  });
254
251
  return this.browserAutomationService;
@@ -520,7 +517,6 @@ var GatewayService = class {
520
517
  getChannelManager: () => this.channelManager,
521
518
  getHeartbeatService: () => this.heartbeatService,
522
519
  getExtensionLoader: () => this.extensionLoader,
523
- reconcileBrowserExtensionServer: () => this.reconcileBrowserExtensionServer(),
524
520
  reconcileMemoryMaintenanceAutomations: () => this.reconcileMemoryMaintenanceAutomations(),
525
521
  getChannelsStatus: () => this.getChannelsStatus(),
526
522
  emit: (type, payload) => this.emit(type, payload)
@@ -1099,7 +1095,6 @@ var GatewayService = class {
1099
1095
  workspaceId: this.currentWorkspacePath
1100
1096
  }) });
1101
1097
  import("../browser/providers/browser-ext-install.js").then(({ ensureBrowserExtensionOnStartup }) => ensureBrowserExtensionOnStartup(this.config)).catch((err) => log.warn({ err }, "Browser extension artifact ensure failed"));
1102
- await trace.measure("browser-extension.start", () => this.startBrowserExtensionServerIfNeeded());
1103
1098
  this.agentService.start().catch((err) => {
1104
1099
  log.error({ err }, "Agent service error");
1105
1100
  });
@@ -1260,12 +1255,6 @@ var GatewayService = class {
1260
1255
  this.connectedKnowledgeCoordinator?.stop();
1261
1256
  this.connectedKnowledgeCoordinator = null;
1262
1257
  await this.browserAutomationService?.shutdown();
1263
- if (this.browserExtensionRelease) {
1264
- await this.browserExtensionRelease();
1265
- this.browserExtensionRelease = null;
1266
- }
1267
- this.browserExtensionProvider = null;
1268
- this.browserExtensionBindKey = null;
1269
1258
  registerClarificationChannelRuntime(null);
1270
1259
  this.connectionRecovery.stop();
1271
1260
  this.agentRunner.disposeClarifications();
@@ -1288,73 +1277,6 @@ var GatewayService = class {
1288
1277
  closeXopcDatabase();
1289
1278
  log.debug("Gateway service stopped");
1290
1279
  }
1291
- /** Start the browser extension WebSocket server when its driver is active. */
1292
- async startBrowserExtensionServerIfNeeded() {
1293
- await this.reconcileBrowserExtensionServer();
1294
- }
1295
- /**
1296
- * Start/stop/rebind the Chrome extension bridge.
1297
- * PATCH saves update config in memory without re-running gateway startup, so this must run on save too.
1298
- */
1299
- async reconcileBrowserExtensionServer() {
1300
- const driver = this.config.browser.driver;
1301
- const bridgeConfig = this.config.browser.enabled && driver.kind === "extension" ? {
1302
- host: "127.0.0.1",
1303
- port: 19820,
1304
- connectionTimeout: this.config.browser.limits.actionTimeoutMs,
1305
- commandTimeout: this.config.browser.limits.actionTimeoutMs
1306
- } : null;
1307
- if (!bridgeConfig) {
1308
- if (this.browserExtensionRelease) {
1309
- await this.browserExtensionRelease();
1310
- this.browserExtensionRelease = null;
1311
- this.browserExtensionProvider = null;
1312
- this.browserExtensionBindKey = null;
1313
- log.debug("Browser extension WebSocket server stopped because its driver is inactive");
1314
- }
1315
- return;
1316
- }
1317
- const { port, host, connectionTimeout, commandTimeout } = bridgeConfig;
1318
- const bindKey = `${host}:${port}`;
1319
- if (this.browserExtensionRelease && this.browserExtensionBindKey === bindKey) return;
1320
- if (this.browserExtensionRelease) {
1321
- await this.browserExtensionRelease();
1322
- this.browserExtensionRelease = null;
1323
- this.browserExtensionProvider = null;
1324
- this.browserExtensionBindKey = null;
1325
- }
1326
- try {
1327
- const { acquireExtensionBrowserServer } = await import("../browser/providers/extension-ws-acquire.js");
1328
- const { provider, release } = await acquireExtensionBrowserServer({
1329
- port,
1330
- host,
1331
- connectionTimeout,
1332
- commandTimeout
1333
- });
1334
- this.browserExtensionProvider = provider;
1335
- this.browserExtensionRelease = release;
1336
- this.browserExtensionBindKey = bindKey;
1337
- log.info({
1338
- port,
1339
- host
1340
- }, "Browser extension WS server started");
1341
- } catch (err) {
1342
- if ((err && typeof err === "object" && "code" in err ? err.code : void 0) === "EADDRINUSE") {
1343
- log.warn({
1344
- err,
1345
- phase: "browser_extension_ws",
1346
- bindPort: port,
1347
- bindHost: host,
1348
- hint: "Another process holds the browser extension bridge port 19820. Stop it before starting xopc."
1349
- }, `Browser extension WS server port is already in use: ${host}:${port}`);
1350
- return;
1351
- }
1352
- log.error({
1353
- err,
1354
- phase: "browser_extension_ws"
1355
- }, `Failed to start browser extension WS server: ${err instanceof Error ? err.message : String(err)}`);
1356
- }
1357
- }
1358
1280
  /**
1359
1281
  * Start processing outbound messages and send through channels
1360
1282
  */
@@ -3,6 +3,8 @@ import { buildDevicePairingProof, devicePairingCompleteSchema, devicePairingDevi
3
3
  import { DEFAULT_BROWSER_EXTENSION_SCOPES, DEFAULT_MOBILE_SCOPES } from "../../gateway/security/gateway-scopes.js";
4
4
  import { createDevice, getDevice, registerInitialDeviceRefreshToken, revokeDevice } from "./device-access-repository.js";
5
5
  import { getOrCreateGatewayIdentity } from "./gateway-identity-repository.js";
6
+ import { browserEnrollmentPublicKeyThumbprint } from "../../browser/enrollment.js";
7
+ import "../../browser/extension-identity.js";
6
8
  import crypto from "node:crypto";
7
9
  //#region src/storage/sqlite/device-pairing-approval.ts
8
10
  const PROOF_WINDOW_MS = 5 * 6e4;
@@ -20,6 +22,23 @@ const fail = (code, status) => {
20
22
  throw new DevicePairingError(code, status);
21
23
  };
22
24
  const scopesForPlatform = (platform) => platform === "chrome" ? DEFAULT_BROWSER_EXTENSION_SCOPES : DEFAULT_MOBILE_SCOPES;
25
+ function isTrustedLocalBrowserSetup(setup, deviceJson) {
26
+ const device = devicePairingDeviceSchema.parse(JSON.parse(deviceJson));
27
+ if (device.platform !== "chrome" || device.extensionId !== "gopbfhaojnnhiheiikblejnpgmfmkmgd" || setup.enrollment_issuer !== "browser-native-host" || setup.enrollment_extension_id !== device.extensionId || !setup.enrollment_public_key_thumbprint || browserEnrollmentPublicKeyThumbprint(device.publicKeyJwk) !== setup.enrollment_public_key_thumbprint || !setup.enrollment_nonce) return false;
28
+ const routes = JSON.parse(setup.routes_json);
29
+ if (routes.length !== 1 || routes[0]?.kind !== "local-browser") return false;
30
+ try {
31
+ const url = new URL(routes[0].url);
32
+ return url.protocol === "http:" && url.hostname === "127.0.0.1";
33
+ } catch {
34
+ return false;
35
+ }
36
+ }
37
+ function approveTrustedLocalBrowserRequest(setup, row, now) {
38
+ if (row.status !== "pending" || !isTrustedLocalBrowserSetup(setup, row.device_json)) return row;
39
+ getSqliteDatabase().prepare("UPDATE device_pairing_requests SET status = 'approved', revision = revision + 1 WHERE request_id = ? AND status = 'pending'").run(row.request_id);
40
+ return requestRow(row.request_id, now);
41
+ }
23
42
  function requestRow(id, now) {
24
43
  const db = getSqliteDatabase();
25
44
  db.prepare(`UPDATE device_pairing_requests SET status = 'expired', revision = revision + 1
@@ -75,18 +94,19 @@ function submitDevicePairingRequest(body, now = Date.now()) {
75
94
  const setup = verifiedSetup(String(body.pairingToken), now);
76
95
  const device = devicePairingDeviceSchema.parse(body.device);
77
96
  const deviceJson = JSON.stringify(device);
97
+ const autoApprove = isTrustedLocalBrowserSetup(setup, deviceJson);
78
98
  verifyProof("request", body, deviceJson, now);
79
99
  return runSqliteWriteTransaction((db) => {
80
100
  const existing = db.prepare("SELECT request_id, device_json FROM device_pairing_requests WHERE pairing_id = ?").get(setup.pairing_id);
81
101
  if (existing) {
82
102
  if (existing.request_id !== body.requestId || existing.device_json !== deviceJson) return fail("PAIRING_BUSY");
83
- return statusFromRow(requestRow(existing.request_id, now), now);
103
+ return statusFromRow(approveTrustedLocalBrowserRequest(setup, requestRow(existing.request_id, now), now), now);
84
104
  }
85
105
  if (setup.consumed_at !== null) return fail("PAIRING_CANCELLED");
86
106
  if (setup.expires_at <= now) return fail("PAIRING_EXPIRED");
87
107
  const expiresAt = Math.min(setup.expires_at, now + APPROVAL_WINDOW_MS);
88
108
  db.prepare(`INSERT INTO device_pairing_requests (request_id, pairing_id, device_json, status,
89
- confirmation_code, expires_at, recovery_until, created_at) VALUES (?, ?, ?, 'pending', ?, ?, ?, ?)`).run(String(body.requestId), setup.pairing_id, deviceJson, crypto.randomInt(0, 1e6).toString().padStart(6, "0"), expiresAt, setup.expires_at + RECOVERY_WINDOW_MS, now);
109
+ confirmation_code, expires_at, recovery_until, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`).run(String(body.requestId), setup.pairing_id, deviceJson, autoApprove ? "approved" : "pending", crypto.randomInt(0, 1e6).toString().padStart(6, "0"), expiresAt, setup.expires_at + RECOVERY_WINDOW_MS, now);
90
110
  return statusFromRow(requestRow(String(body.requestId), now), now);
91
111
  });
92
112
  }
@@ -115,9 +135,10 @@ function cancelDevicePairingSetup(id, now = Date.now()) {
115
135
  }
116
136
  function operateDevicePairingRequest(action, body, now = Date.now()) {
117
137
  const setup = verifiedSetup(String(body.pairingToken), now);
118
- const row = requestRow(String(body.requestId), now);
138
+ let row = requestRow(String(body.requestId), now);
119
139
  if (row.pairing_id !== setup.pairing_id) return fail("PAIRING_DENIED", 401);
120
140
  verifyProof(action, body, row.device_json, now);
141
+ if (action === "status") row = approveTrustedLocalBrowserRequest(setup, row, now);
121
142
  if (action !== "cancel" && row.device_id && getDevice(row.device_id)?.revokedAt !== void 0) return fail("DEVICE_REVOKED", 401);
122
143
  if (now >= row.recovery_until) return fail("PAIRING_EXPIRED");
123
144
  if (action === "cancel") runSqliteWriteTransaction((db) => {
@@ -9,6 +9,12 @@ export type DevicePairingSetup = {
9
9
  routes: DeviceRoute[];
10
10
  expiresAt: number;
11
11
  };
12
+ export type DevicePairingEnrollment = {
13
+ issuer: 'browser-native-host';
14
+ extensionId: string;
15
+ publicKeyThumbprint: string;
16
+ nonce: string;
17
+ };
12
18
  export type PairingConsumeResult = {
13
19
  ok: true;
14
20
  pairingId: string;
@@ -17,6 +23,9 @@ export type PairingConsumeResult = {
17
23
  ok: false;
18
24
  reason: 'invalid' | 'expired' | 'consumed';
19
25
  };
20
- export declare function createDevicePairingSetup(routes: readonly DeviceRoute[], now?: number, protocolVersion?: 2 | 3): DevicePairingSetup;
26
+ export declare function createDevicePairingSetup(routes: readonly DeviceRoute[], now?: number, protocolVersion?: 2 | 3, options?: {
27
+ ttlMs?: number;
28
+ enrollment?: DevicePairingEnrollment;
29
+ }): DevicePairingSetup;
21
30
  export declare function consumeDevicePairingToken(token: string, now?: number): PairingConsumeResult;
22
31
  export declare function isDevicePairingSetupActive(pairingId: string, now?: number): boolean;
@@ -16,11 +16,11 @@ function tokenId(token) {
16
16
  if (separator <= 10 || separator === token.length - 1) return void 0;
17
17
  return token.slice(10, separator);
18
18
  }
19
- function createDevicePairingSetup(routes, now = Date.now(), protocolVersion = 2) {
19
+ function createDevicePairingSetup(routes, now = Date.now(), protocolVersion = 2, options) {
20
20
  if (routes.length === 0) throw new Error("No secure mobile route is available");
21
21
  const id = crypto.randomUUID();
22
22
  const token = `${PAIRING_TOKEN_PREFIX}${id}_${crypto.randomBytes(32).toString("base64url")}`;
23
- const expiresAt = now + PAIRING_TTL_MS;
23
+ const expiresAt = now + (options?.ttlMs ?? PAIRING_TTL_MS);
24
24
  runSqliteWriteTransaction((db) => {
25
25
  db.prepare(`DELETE FROM device_pairing_sessions WHERE
26
26
  (protocol_version = 2 AND (expires_at <= ? OR consumed_at IS NOT NULL)) OR
@@ -28,9 +28,10 @@ function createDevicePairingSetup(routes, now = Date.now(), protocolVersion = 2)
28
28
  db.prepare(`
29
29
  INSERT INTO device_pairing_sessions (
30
30
  pairing_id, secret_hash, routes_json, expires_at, attempts_remaining, created_at
31
- , protocol_version
32
- ) VALUES (?, ?, ?, ?, ?, ?, ?)
33
- `).run(id, hashToken(token), JSON.stringify(routes), expiresAt, PAIRING_ATTEMPTS, now, protocolVersion);
31
+ , protocol_version, enrollment_issuer, enrollment_extension_id,
32
+ enrollment_public_key_thumbprint, enrollment_nonce
33
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
34
+ `).run(id, hashToken(token), JSON.stringify(routes), expiresAt, PAIRING_ATTEMPTS, now, protocolVersion, options?.enrollment?.issuer ?? null, options?.enrollment?.extensionId ?? null, options?.enrollment?.publicKeyThumbprint ?? null, options?.enrollment?.nonce ?? null);
34
35
  });
35
36
  return {
36
37
  id,
@@ -0,0 +1,50 @@
1
+ PRAGMA defer_foreign_keys = ON;
2
+
3
+ -- Rebuild the related tables because SQLite rewrites the child foreign-key target
4
+ -- when the parent table is renamed.
5
+ ALTER TABLE endpoint_session_bindings RENAME TO endpoint_session_bindings_v156;
6
+ ALTER TABLE endpoint_instance_bindings RENAME TO endpoint_instance_bindings_v156;
7
+ ALTER TABLE endpoint_principals RENAME TO endpoint_principals_v156;
8
+
9
+ DROP INDEX idx_endpoint_session_bindings_endpoint;
10
+ DROP INDEX idx_endpoint_instance_bindings_principal;
11
+ DROP INDEX idx_endpoint_principals_active;
12
+
13
+ CREATE TABLE endpoint_principals (
14
+ id TEXT PRIMARY KEY,
15
+ kind TEXT NOT NULL CHECK(kind IN ('web', 'desktop', 'mobile', 'browser')),
16
+ display_name TEXT NOT NULL,
17
+ platform TEXT NOT NULL,
18
+ public_key TEXT NOT NULL,
19
+ created_at INTEGER NOT NULL,
20
+ last_seen_at INTEGER,
21
+ revoked_at INTEGER
22
+ );
23
+
24
+ CREATE TABLE endpoint_instance_bindings (
25
+ endpoint_id TEXT PRIMARY KEY,
26
+ principal_id TEXT NOT NULL REFERENCES endpoint_principals(id),
27
+ bound_at INTEGER NOT NULL
28
+ );
29
+
30
+ CREATE TABLE endpoint_session_bindings (
31
+ session_key TEXT PRIMARY KEY,
32
+ endpoint_id TEXT NOT NULL REFERENCES endpoint_instance_bindings(endpoint_id),
33
+ bound_at INTEGER NOT NULL
34
+ );
35
+
36
+ INSERT INTO endpoint_principals SELECT * FROM endpoint_principals_v156;
37
+ INSERT INTO endpoint_instance_bindings SELECT * FROM endpoint_instance_bindings_v156;
38
+ INSERT INTO endpoint_session_bindings SELECT * FROM endpoint_session_bindings_v156;
39
+
40
+ DROP TABLE endpoint_session_bindings_v156;
41
+ DROP TABLE endpoint_instance_bindings_v156;
42
+ DROP TABLE endpoint_principals_v156;
43
+
44
+ CREATE INDEX idx_endpoint_principals_active
45
+ ON endpoint_principals(kind, last_seen_at DESC)
46
+ WHERE revoked_at IS NULL;
47
+ CREATE INDEX idx_endpoint_instance_bindings_principal
48
+ ON endpoint_instance_bindings(principal_id);
49
+ CREATE INDEX idx_endpoint_session_bindings_endpoint
50
+ ON endpoint_session_bindings(endpoint_id);
@@ -0,0 +1,4 @@
1
+ ALTER TABLE device_pairing_sessions ADD COLUMN enrollment_issuer TEXT;
2
+ ALTER TABLE device_pairing_sessions ADD COLUMN enrollment_extension_id TEXT;
3
+ ALTER TABLE device_pairing_sessions ADD COLUMN enrollment_public_key_thumbprint TEXT;
4
+ ALTER TABLE device_pairing_sessions ADD COLUMN enrollment_nonce TEXT;
@@ -3,7 +3,7 @@ import type { ApplyMigrationsOptions } from './types.js';
3
3
  /** Baseline schema version applied from schema.sql on first open. */
4
4
  export declare const XOPC_DB_BASELINE_SCHEMA_VERSION = 11;
5
5
  /** Latest schema version this release supports (increment when adding migrations). */
6
- export declare const XOPC_DB_SCHEMA_VERSION = 156;
6
+ export declare const XOPC_DB_SCHEMA_VERSION = 158;
7
7
  export declare function resolveMigrationsDir(override?: string): string;
8
8
  /**
9
9
  * Apply sequential SQL migrations from (currentVersion + 1) through targetVersion.
@@ -12,7 +12,7 @@ const log = createLogger("Sqlite:Migrations");
12
12
  /** Baseline schema version applied from schema.sql on first open. */
13
13
  const XOPC_DB_BASELINE_SCHEMA_VERSION = 11;
14
14
  /** Latest schema version this release supports (increment when adding migrations). */
15
- const XOPC_DB_SCHEMA_VERSION = 156;
15
+ const XOPC_DB_SCHEMA_VERSION = 158;
16
16
  const MODULE_DIR = dirname(fileURLToPath(import.meta.url));
17
17
  function backupBeforeTaskCutover(db, databasePath) {
18
18
  db.exec("PRAGMA wal_checkpoint(FULL)");
@@ -57,7 +57,7 @@ function applySingleMigration(db, migration) {
57
57
  * Each step runs in its own transaction and updates schema_meta on success.
58
58
  */
59
59
  function applyPendingMigrations(db, options = {}) {
60
- const targetVersion = options.targetVersion ?? 156;
60
+ const targetVersion = options.targetVersion ?? 158;
61
61
  let currentVersion = readSchemaVersion(db);
62
62
  if (currentVersion > targetVersion) throw new DatabaseSchemaTooNewError(currentVersion, targetVersion);
63
63
  if (currentVersion >= targetVersion) return currentVersion;
@@ -94,7 +94,7 @@ function applyPendingMigrations(db, options = {}) {
94
94
  }
95
95
  /** Inspect schema version without mutating the database. */
96
96
  function inspectSchemaMigrationStatus(db, options = {}) {
97
- const appVersion = options.targetVersion ?? 156;
97
+ const appVersion = options.targetVersion ?? 158;
98
98
  const dbVersion = readSchemaVersion(db);
99
99
  if (dbVersion > appVersion) return {
100
100
  dbVersion,
@@ -19,7 +19,7 @@ const log = createLogger('Sqlite:Migrations');
19
19
  export const XOPC_DB_BASELINE_SCHEMA_VERSION = 11;
20
20
 
21
21
  /** Latest schema version this release supports (increment when adding migrations). */
22
- export const XOPC_DB_SCHEMA_VERSION = 156;
22
+ export const XOPC_DB_SCHEMA_VERSION = 158;
23
23
 
24
24
  const MODULE_DIR = dirname(fileURLToPath(import.meta.url));
25
25
 
@@ -32,7 +32,7 @@ function ensureXopcDatabaseSchema(db, options = {}) {
32
32
  currentVersion = 11;
33
33
  }
34
34
  applyPendingMigrations(db, {
35
- targetVersion: 156,
35
+ targetVersion: 158,
36
36
  databasePath: options.databasePath
37
37
  });
38
38
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xopcai/xopc",
3
- "version": "0.0.266",
3
+ "version": "0.0.267",
4
4
  "description": "Local-first personal AI assistant with reviewable user understanding, durable tasks, workflows, automations, and local or cloud models.",
5
5
  "type": "module",
6
6
  "main": "dist/src/index.js",
@@ -165,9 +165,9 @@
165
165
  "vue": "^3.5.35",
166
166
  "@xopcai/browser-control-contract": "0.1.0",
167
167
  "@xopcai/endpoint-tools-protocol": "0.0.0",
168
+ "@xopcai/gateway-contract": "0.0.0",
168
169
  "@xopcai/realtime-client": "0.0.0",
169
- "@xopcai/realtime-protocol": "0.0.0",
170
- "@xopcai/gateway-contract": "0.0.0"
170
+ "@xopcai/realtime-protocol": "0.0.0"
171
171
  },
172
172
  "scripts": {
173
173
  "assets:brand": "node scripts/generate-brand-assets.mjs",