@xopcai/xopc 0.0.272 → 0.0.274

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (151) hide show
  1. package/README.md +1 -5
  2. package/README.zh-CN.md +1 -5
  3. package/dist/browser-ext/dist/assets/{auth-C1QI-uMG.js → auth-O8RiOF3l.js} +265 -100
  4. package/dist/browser-ext/dist/background.js +1 -22
  5. package/dist/browser-ext/dist/sidepanel.html +1 -1
  6. package/dist/browser-ext/dist/sidepanel.js +68 -36
  7. package/dist/browser-ext/manifest.json +1 -12
  8. package/dist/extensions/telegram/xopc.extension.json +1 -1
  9. package/dist/gateway/static/root/assets/{activity-store-DffqjeqQ.js → activity-store-SJZ5fS17.js} +1 -1
  10. package/dist/gateway/static/root/assets/{agent-avatar-dicebear-Cwj4HCYY.js → agent-avatar-dicebear-BckaANxj.js} +1 -1
  11. package/dist/gateway/static/root/assets/{agent-defaults-settings-panel-6CLfLxVc.js → agent-defaults-settings-panel-B8eyC6K1.js} +1 -1
  12. package/dist/gateway/static/root/assets/agents-CSWj_yo1.js +1 -0
  13. package/dist/gateway/static/root/assets/{api-CGLdye4i.js → api-CnJAVHBu.js} +1 -1
  14. package/dist/gateway/static/root/assets/app-link-BiBXVEvp.js +1 -0
  15. package/dist/gateway/static/root/assets/{app-management-settings-panel-BLujSbaw.js → app-management-settings-panel-DY-Zbgmt.js} +1 -1
  16. package/dist/gateway/static/root/assets/{appearance-settings-BXMcueb8.js → appearance-settings-CoJR7ZRl.js} +1 -1
  17. package/dist/gateway/static/root/assets/{apps-page-BEgy4hY3.js → apps-page-CSMvkuot.js} +1 -1
  18. package/dist/gateway/static/root/assets/{archive-plugin-D4bUampf.js → archive-plugin-BwAlRT53.js} +1 -1
  19. package/dist/gateway/static/root/assets/attachment-load-R835uxgL.js +1 -0
  20. package/dist/gateway/static/root/assets/{attachment-preview-renderer-BDTD8YUe.js → attachment-preview-renderer-CfEDFYPE.js} +1 -1
  21. package/dist/gateway/static/root/assets/attachment-process-heavy-C5CqLCsX.js +5 -0
  22. package/dist/gateway/static/root/assets/{automations-page-dQyolNmy.js → automations-page-BEZy0P_u.js} +1 -1
  23. package/dist/gateway/static/root/assets/automations-workspace-CpaI13J7.js +6 -0
  24. package/dist/gateway/static/root/assets/{binary-plugins-bbvp81h2.js → binary-plugins-CESFx3yb.js} +2 -2
  25. package/dist/gateway/static/root/assets/{block-editor-C8e6Wri4.js → block-editor-DDJcf2wR.js} +3 -3
  26. package/dist/gateway/static/root/assets/{browser-automation-inputs-oJWJtQa6.js → browser-automation-inputs-DGCqIPXT.js} +1 -1
  27. package/dist/gateway/static/root/assets/{browser-automations-page-CAcJQSjN.js → browser-automations-page-BH4lSfr5.js} +2 -2
  28. package/dist/gateway/static/root/assets/browser-settings-page-CbTooPd2.js +8 -0
  29. package/dist/gateway/static/root/assets/capabilities-settings-panel-C4LZWHhv.js +8 -0
  30. package/dist/gateway/static/root/assets/channels-settings-BAljmSa7.js +1 -0
  31. package/dist/gateway/static/root/assets/{channels-status-swr-CfOPNnVr.js → channels-status-swr-BTT2sIGg.js} +1 -1
  32. package/dist/gateway/static/root/assets/{chat-terminal-dock-9ZOl92kh.js → chat-terminal-dock-D8U97O95.js} +2 -2
  33. package/dist/gateway/static/root/assets/connectors-page-CVRMjNNr.js +2 -0
  34. package/dist/gateway/static/root/assets/{date-picker-Dj6FRLF9.js → date-picker-7sDSMswL.js} +1 -1
  35. package/dist/gateway/static/root/assets/{dependency-picker-D1q89tVc.js → dependency-picker-DMIC86ZW.js} +1 -1
  36. package/dist/gateway/static/root/assets/{desktop-pet-DfqZBvhp.js → desktop-pet-BtdiY5jK.js} +1 -1
  37. package/dist/gateway/static/root/assets/{desktop-pet-settings-HCEh2MOm.js → desktop-pet-settings-D_B8azDH.js} +1 -1
  38. package/dist/gateway/static/root/assets/device-pairing-wizard-Dmh3Nh4d.js +1 -0
  39. package/dist/gateway/static/root/assets/excel-worksheet-utils-DpdB-DjU.js +1 -0
  40. package/dist/gateway/static/root/assets/{extension-debug-page-C8cAWVoK.js → extension-debug-page-CoFKb01d.js} +1 -1
  41. package/dist/gateway/static/root/assets/{extension-page-5N87Vaym.js → extension-page-CEkNsCAY.js} +1 -1
  42. package/dist/gateway/static/root/assets/{extension-settings-page-D85SAUFP.js → extension-settings-page-Bi2R8P5j.js} +1 -1
  43. package/dist/gateway/static/root/assets/{gateway-config-swr-BjsDnB_n.js → gateway-config-swr-cd8owslz.js} +1 -1
  44. package/dist/gateway/static/root/assets/gateway-settings-q9p3sduo.js +1 -0
  45. package/dist/gateway/static/root/assets/{gateway-startup-retry-BIf0eLWX.js → gateway-startup-retry-CVTa5HSS.js} +1 -1
  46. package/dist/gateway/static/root/assets/{heartbeat-settings-BpDWGE1a.js → heartbeat-settings-DQtelFiU.js} +1 -1
  47. package/dist/gateway/static/root/assets/home-page-COpGnTFZ.js +1 -0
  48. package/dist/gateway/static/root/assets/{image-generation-api-1m8xTVS_.js → image-generation-api-DtMRG1oF.js} +1 -1
  49. package/dist/gateway/static/root/assets/{index-C6PmD0cK.js → index-SxqsTD4i.js} +35 -35
  50. package/dist/gateway/static/root/assets/{keyboard-shortcuts-settings-Byy8mCSq.js → keyboard-shortcuts-settings-BuYmZS1K.js} +1 -1
  51. package/dist/gateway/static/root/assets/local-app-workbench-page-DV_17Zxc.js +2 -0
  52. package/dist/gateway/static/root/assets/{local-apps-page-BPo0wBgM.js → local-apps-page-CExnZyGZ.js} +1 -1
  53. package/dist/gateway/static/root/assets/{locale-store-CRNble2Z.js → locale-store-BkJ7g35d.js} +1 -1
  54. package/dist/gateway/static/root/assets/{logs-page-X5FdLlXK.js → logs-page-CA5ArMoN.js} +2 -2
  55. package/dist/gateway/static/root/assets/management-settings-1UiJPrkX.js +1 -0
  56. package/dist/gateway/static/root/assets/markdown-view-B2IR4E2r.js +201 -0
  57. package/dist/gateway/static/root/assets/{media-plugins-Bzka9qXu.js → media-plugins-nNFOk9HL.js} +1 -1
  58. package/dist/gateway/static/root/assets/{messages-DH_HCgmw.js → messages-BGc-OJWg.js} +2 -2
  59. package/dist/gateway/static/root/assets/note-detail-panel-B5OQ0fBY.js +5 -0
  60. package/dist/gateway/static/root/assets/notes-page-CVPPMVDl.js +8 -0
  61. package/dist/gateway/static/root/assets/{page-tabs-Ys0p65Lt.js → page-tabs-CRombigq.js} +1 -1
  62. package/dist/gateway/static/root/assets/{preview-open-alternatives-iknB5xEp.js → preview-open-alternatives-Bcuj43lk.js} +1 -1
  63. package/dist/gateway/static/root/assets/{proactive-card-BPZDgNNR.js → proactive-card-Cb3Xeoyx.js} +1 -1
  64. package/dist/gateway/static/root/assets/{proactive-page-CxdZR-p2.js → proactive-page-TbF9KVpm.js} +1 -1
  65. package/dist/gateway/static/root/assets/{product-open-page-W1o3f-kz.js → product-open-page-DsQcgZkI.js} +1 -1
  66. package/dist/gateway/static/root/assets/project-detail-page-B9OQ2l9x.js +1 -0
  67. package/dist/gateway/static/root/assets/projects-page-DCqSTNkY.js +1 -0
  68. package/dist/gateway/static/root/assets/{registry-api-DmYFpjsH.js → registry-api-CTq8EECj.js} +1 -1
  69. package/dist/gateway/static/root/assets/remote-access-hub-d9Fg70gX.js +1 -0
  70. package/dist/gateway/static/root/assets/{runtime-tools-settings-panel-DP1XyYO8.js → runtime-tools-settings-panel-BudlvBu-.js} +2 -2
  71. package/dist/gateway/static/root/assets/{schema-form-BMYKdzsH.js → schema-form-Ch3MEX4h.js} +1 -1
  72. package/dist/gateway/static/root/assets/sessions-page-D8RiQ-ag.js +1 -0
  73. package/dist/gateway/static/root/assets/settings-advanced-gate-DVH_l6Hr.js +1 -0
  74. package/dist/gateway/static/root/assets/{settings-form-section-2LGjz7bu.js → settings-form-section-CIA1AA-A.js} +1 -1
  75. package/dist/gateway/static/root/assets/{settings-page-7DjpC9_C.js → settings-page-B0Khoduh.js} +1 -1
  76. package/dist/gateway/static/root/assets/{setup-status-panel-Dj9jFWJt.js → setup-status-panel-COJFl4N_.js} +1 -1
  77. package/dist/gateway/static/root/assets/share-preview-page-Cj9XWg-X.js +2 -0
  78. package/dist/gateway/static/root/assets/shares-settings-_OuE6A0G.js +1 -0
  79. package/dist/gateway/static/root/assets/skill-reload-api-BM4Ezjbx.js +1 -0
  80. package/dist/gateway/static/root/assets/{skills-page-DNEScMZH.js → skills-page-Dyh1kf9t.js} +1 -1
  81. package/dist/gateway/static/root/assets/{skills-page.utils-q88l65YU.js → skills-page.utils-DsQZkMti.js} +1 -1
  82. package/dist/gateway/static/root/assets/{system-settings-panel-8NIssGKj.js → system-settings-panel-BSho_Tpk.js} +1 -1
  83. package/dist/gateway/static/root/assets/task-detail-page-B5HWM12k.js +1 -0
  84. package/dist/gateway/static/root/assets/{theme-store-BXZG3S25.js → theme-store-mV6Cm2vj.js} +1 -1
  85. package/dist/gateway/static/root/assets/{tunnel-api-CWsRPM84.js → tunnel-api-4DUHxSQG.js} +1 -1
  86. package/dist/gateway/static/root/assets/{use-autosave-BwQuoTQU.js → use-autosave-BG8Yx7hj.js} +1 -1
  87. package/dist/gateway/static/root/assets/user-model-page-IjVyv5jj.js +2 -0
  88. package/dist/gateway/static/root/assets/{voice-api-key-field-Bd03Ic0s.js → voice-api-key-field-DYXwDhHq.js} +1 -1
  89. package/dist/gateway/static/root/assets/{voice-config-api-gggUp3u9.js → voice-config-api-D3DdcH4H.js} +1 -1
  90. package/dist/gateway/static/root/assets/{work-discovery-overlay-DF8PR_pK.js → work-discovery-overlay-CQqjzaRo.js} +1 -1
  91. package/dist/gateway/static/root/assets/{workflow-run-setup-panel-BiAAWlir.js → workflow-run-setup-panel-C5H-Fd-U.js} +2 -2
  92. package/dist/gateway/static/root/assets/workflows-page-D1hMyPsY.js +6 -0
  93. package/dist/gateway/static/root/index.html +10 -9
  94. package/dist/package.js +1 -1
  95. package/dist/packages/gateway-contract/src/device-pairing.js +41 -2
  96. package/dist/src/agent/service.js +20 -16
  97. package/dist/src/browser/native-messaging-host.d.ts +1 -1
  98. package/dist/src/browser/native-messaging-host.js +3 -2
  99. package/dist/src/browser/providers/browser-ext-install.d.ts +6 -0
  100. package/dist/src/browser/providers/browser-ext-install.js +16 -1
  101. package/dist/src/cli/commands/gateway/run-foreground.js +8 -5
  102. package/dist/src/cli/commands/gateway/service.js +4 -3
  103. package/dist/src/cli/commands/gateway/token.js +28 -1
  104. package/dist/src/gateway/device-routes.js +4 -3
  105. package/dist/src/gateway/hono/app.js +5 -2
  106. package/dist/src/gateway/hono/routes/agent-stream.js +1 -1
  107. package/dist/src/gateway/hono/routes/browser.js +26 -0
  108. package/dist/src/gateway/hono/routes/device-pairing-approval.d.ts +9 -2
  109. package/dist/src/gateway/hono/routes/device-pairing-approval.js +50 -10
  110. package/dist/src/gateway/hono/routes/devices.d.ts +2 -1
  111. package/dist/src/gateway/hono/routes/devices.js +33 -16
  112. package/dist/src/gateway/hono/routes/session-input-handler.d.ts +1 -1
  113. package/dist/src/gateway/hono/routes/session-input-handler.js +1 -1
  114. package/dist/src/gateway/hono/routes/side-chats.js +1 -1
  115. package/dist/src/gateway/hono/routes/tunnel.js +34 -2
  116. package/dist/src/gateway/rate-limit/buckets.js +2 -1
  117. package/dist/src/gateway/service/run-gateway-agent.js +1 -1
  118. package/dist/src/storage/sqlite/device-pairing-approval.js +4 -4
  119. package/dist/src/tui/tui.js +1 -1
  120. package/dist/src/tunnel/xopc-cloud-registration.d.ts +5 -0
  121. package/dist/src/tunnel/xopc-cloud-registration.js +17 -11
  122. package/package.json +3 -3
  123. package/dist/browser-ext/dist/pairing-handoff.js +0 -51
  124. package/dist/gateway/static/root/assets/agents-B6mu64fY.js +0 -1
  125. package/dist/gateway/static/root/assets/attachment-load-CBGNA2Ys.js +0 -2
  126. package/dist/gateway/static/root/assets/attachment-process-heavy-B2_kr5EA.js +0 -11
  127. package/dist/gateway/static/root/assets/automations-workspace-DxRFrmrV.js +0 -6
  128. package/dist/gateway/static/root/assets/browser-settings-page-TLAlqcPo.js +0 -8
  129. package/dist/gateway/static/root/assets/capabilities-settings-panel-z-K_jrZz.js +0 -8
  130. package/dist/gateway/static/root/assets/channels-settings-Dzrk33aB.js +0 -1
  131. package/dist/gateway/static/root/assets/connectors-page-B1dIoRNi.js +0 -2
  132. package/dist/gateway/static/root/assets/device-pairing-wizard-CPth92s6.js +0 -1
  133. package/dist/gateway/static/root/assets/excel-worksheet-utils-EyvwNT4X.js +0 -1
  134. package/dist/gateway/static/root/assets/gateway-settings-Dqx8PmcT.js +0 -1
  135. package/dist/gateway/static/root/assets/home-page-DJaqpC8-.js +0 -1
  136. package/dist/gateway/static/root/assets/local-app-workbench-page-l2AEiG2C.js +0 -2
  137. package/dist/gateway/static/root/assets/management-settings-DT6NoIjm.js +0 -1
  138. package/dist/gateway/static/root/assets/markdown-view-AglreSOj.js +0 -201
  139. package/dist/gateway/static/root/assets/note-detail-panel-BUJPoaGZ.js +0 -5
  140. package/dist/gateway/static/root/assets/notes-page-BwFpWRuo.js +0 -8
  141. package/dist/gateway/static/root/assets/project-detail-page-BU1RKAcy.js +0 -1
  142. package/dist/gateway/static/root/assets/projects-page-z0X0tL8G.js +0 -1
  143. package/dist/gateway/static/root/assets/remote-access-hub-NNTut2I8.js +0 -1
  144. package/dist/gateway/static/root/assets/sessions-page-B6SsHlz5.js +0 -1
  145. package/dist/gateway/static/root/assets/settings-advanced-gate-BQu8bAcN.js +0 -1
  146. package/dist/gateway/static/root/assets/share-preview-page-BxYdSi8c.js +0 -2
  147. package/dist/gateway/static/root/assets/shares-settings-CMqE0KuK.js +0 -1
  148. package/dist/gateway/static/root/assets/skill-reload-api-BXqSJymN.js +0 -1
  149. package/dist/gateway/static/root/assets/task-detail-page-L6dp9v2y.js +0 -1
  150. package/dist/gateway/static/root/assets/user-model-page-D4BucMrZ.js +0 -2
  151. package/dist/gateway/static/root/assets/workflows-page-DryKUMfA.js +0 -6
@@ -1,4 +1,5 @@
1
1
  import { closeXopcDatabase, openXopcDatabase } from "../storage/sqlite/connection.js";
2
+ import { devicePairingInvitationPayloadSchema, formatBrowserPairingInvitation } from "../../packages/gateway-contract/src/device-pairing.js";
2
3
  import { getGatewayIdentityPublicKeyRaw, getOrCreateGatewayIdentity } from "../storage/sqlite/gateway-identity-repository.js";
3
4
  import { createDevicePairingSetup } from "../storage/sqlite/device-pairing-repository.js";
4
5
  import { init_loader, loadConfig } from "../config/loader.js";
@@ -49,7 +50,7 @@ function createBrowserNativeBootstrap(configPath, request) {
49
50
  return {
50
51
  ok: true,
51
52
  gatewayUrl,
52
- pairingLink: `https://link.xopc.ai/connect#p=${Buffer.from(JSON.stringify({
53
+ invitation: formatBrowserPairingInvitation(Buffer.from(JSON.stringify(devicePairingInvitationPayloadSchema.parse({
53
54
  version: 3,
54
55
  pairingToken: setup.token,
55
56
  gatewayId: identity.id,
@@ -58,7 +59,7 @@ function createBrowserNativeBootstrap(configPath, request) {
58
59
  routes: [route],
59
60
  targetKind: setup.targetKind,
60
61
  expiresAt: setup.expiresAt
61
- })).toString("base64url")}`,
62
+ }))).toString("base64url")),
62
63
  expiresAt: setup.expiresAt
63
64
  };
64
65
  } finally {
@@ -33,6 +33,10 @@ export interface EnsureBrowserExtResult {
33
33
  xopcVersion: string;
34
34
  copied: boolean;
35
35
  }
36
+ export interface BrowserExtensionArchive {
37
+ filename: string;
38
+ data: Buffer;
39
+ }
36
40
  export interface BrowserNativeHostInstallResult {
37
41
  installed: boolean;
38
42
  manifestPaths: string[];
@@ -50,6 +54,8 @@ export declare function resolveBundledBrowserExtDir(): Promise<{
50
54
  dir: string;
51
55
  bundledFrom: BrowserExtBundledFrom;
52
56
  } | null>;
57
+ /** Build a portable archive for installing the bundled extension on another computer. */
58
+ export declare function createBrowserExtensionArchive(): Promise<BrowserExtensionArchive>;
53
59
  export declare function computeNeedsRefresh(params: {
54
60
  force?: boolean;
55
61
  bundledManifestVersion: string;
@@ -14,6 +14,7 @@ import { fileURLToPath } from "node:url";
14
14
  import { spawn } from "node:child_process";
15
15
  import { createHash } from "node:crypto";
16
16
  import { readFile, readdir, rm } from "node:fs/promises";
17
+ import AdmZip from "adm-zip";
17
18
  //#region src/browser/providers/browser-ext-install.ts
18
19
  /**
19
20
  * Install bundled Chrome extension artifacts into {resolveBinDir()}/browser-ext/.
@@ -134,6 +135,20 @@ async function resolveBundledBrowserExtDir() {
134
135
  }
135
136
  return null;
136
137
  }
138
+ /** Build a portable archive for installing the bundled extension on another computer. */
139
+ async function createBrowserExtensionArchive() {
140
+ const bundled = await resolveBundledBrowserExtDir();
141
+ if (!bundled) throw new Error("Bundled browser extension not found. Reinstall or rebuild xopc.");
142
+ const root = "xopc-browser-extension";
143
+ const zip = new AdmZip();
144
+ zip.addLocalFile(join(bundled.dir, "manifest.json"), root);
145
+ zip.addLocalFolder(join(bundled.dir, "dist"), `${root}/dist`);
146
+ zip.addLocalFolder(join(bundled.dir, "icons"), `${root}/icons`);
147
+ return {
148
+ filename: `xopc-browser-extension-${PACKAGE_VERSION}.zip`,
149
+ data: zip.toBuffer()
150
+ };
151
+ }
137
152
  function computeNeedsRefresh(params) {
138
153
  if (params.force) return true;
139
154
  if (!params.installedPath || !validateBrowserExtLayout(params.installedPath)) return true;
@@ -471,4 +486,4 @@ async function openBrowserExtensionInstallUi(opts) {
471
486
  };
472
487
  }
473
488
  //#endregion
474
- export { BROWSER_EXT_REQUIRED_FILES, browserExtContentHash, browserExtDoctor, browserNativeHostDoctor, browserNativeManifestDirectories, computeNeedsRefresh, ensureBrowserExtensionArtifacts, ensureBrowserExtensionOnStartup, installBrowserNativeMessagingHost, openBrowserExtensionInstallUi, readInstalledExtensionDir, resolveBundledBrowserExtDir, resolveInstalledExtensionPath, resolveWindowsExtensionManager, validateBrowserExtLayout };
489
+ export { BROWSER_EXT_REQUIRED_FILES, browserExtContentHash, browserExtDoctor, browserNativeHostDoctor, browserNativeManifestDirectories, computeNeedsRefresh, createBrowserExtensionArchive, ensureBrowserExtensionArtifacts, ensureBrowserExtensionOnStartup, installBrowserNativeMessagingHost, openBrowserExtensionInstallUi, readInstalledExtensionDir, resolveBundledBrowserExtDir, resolveInstalledExtensionPath, resolveWindowsExtensionManager, validateBrowserExtLayout };
@@ -69,10 +69,13 @@ async function runGatewayFromCliOptions(options, ctx) {
69
69
  const { resolveGatewayAuth, assertGatewayAuthConfigured } = await import("../../../gateway/auth.js");
70
70
  const { assertGatewayRuntimeConfig } = await import("../../../gateway/runtime-config.js");
71
71
  let auth = resolveGatewayAuth({ authConfig: effectiveConfig.gateway?.auth });
72
- if (typeof options.token === "string" && options.token.trim().length > 0) auth = {
73
- mode: "token",
74
- token: options.token.trim()
75
- };
72
+ if (typeof options.token === "string" && options.token.trim().length > 0) {
73
+ auth = {
74
+ mode: "token",
75
+ token: options.token.trim()
76
+ };
77
+ process.env.XOPC_GATEWAY_TOKEN = auth.token;
78
+ }
76
79
  try {
77
80
  assertGatewayAuthConfigured(auth);
78
81
  assertGatewayRuntimeConfig({
@@ -126,7 +129,7 @@ async function runGatewayFromCliOptions(options, ctx) {
126
129
  });
127
130
  await server.start();
128
131
  const displayHost = bindHost === "0.0.0.0" ? "localhost" : bindHost;
129
- const token = options.token || config?.gateway?.auth?.token;
132
+ const token = auth.mode === "token" ? auth.token : void 0;
130
133
  console.log("✅ Gateway started");
131
134
  console.log(` URL: http://${displayHost}:${port}`);
132
135
  if (token) console.log(` Token: ${token.slice(0, 8)}...${token.slice(-8)}`);
@@ -9,8 +9,9 @@ init_paths();
9
9
  * Create service install subcommand - actually installs the OS service
10
10
  */
11
11
  function createInstallCommand() {
12
- return new Command("install").description("Install gateway as OS service (LaunchAgent / systemd / Task)").option("--port <port>", "Gateway port").option("--token <token>", "Gateway auth token").option("--force", "Force reinstall if already installed").option("--json", "Output JSON").action(async (options) => {
12
+ return new Command("install").description("Install gateway as OS service (LaunchAgent / systemd / Task)").option("--port <port>", "Gateway port").option("--token <token>", "Gateway auth token").option("--force", "Force reinstall if already installed").option("--json", "Output JSON").action(async (options, command) => {
13
13
  const configPath = getContextWithOpts().configPath || resolveConfigPath();
14
+ const force = options.force === true || command.optsWithGlobals().force === true;
14
15
  const [{ loadConfig }, { resolveGatewayService, isDaemonAvailableAsync, getPlatformName }, { buildGatewayInstallArgs }] = await Promise.all([
15
16
  import("../../../config/index.js"),
16
17
  import("../../../daemon/service.js"),
@@ -30,7 +31,7 @@ function createInstallCommand() {
30
31
  }
31
32
  const service = await resolveGatewayService();
32
33
  const loaded = await service.isLoaded({ env: process.env });
33
- if (loaded && !options.force) {
34
+ if (loaded && !force) {
34
35
  if (options.json) console.log(JSON.stringify({
35
36
  ok: true,
36
37
  result: "already-installed"
@@ -38,7 +39,7 @@ function createInstallCommand() {
38
39
  else console.log("ℹ️ Service already installed. Use --force to reinstall.");
39
40
  return;
40
41
  }
41
- if (loaded && options.force) try {
42
+ if (loaded && force) try {
42
43
  await service.uninstall({ env: process.env });
43
44
  } catch {}
44
45
  const installArgs = buildGatewayInstallArgs({
@@ -4,6 +4,17 @@ import crypto from "crypto";
4
4
  import { Command } from "commander";
5
5
  //#region src/cli/commands/gateway/token.ts
6
6
  init_paths();
7
+ async function readInstalledServiceToken() {
8
+ try {
9
+ const { isDaemonAvailableAsync, resolveGatewayService } = await import("../../../daemon/service.js");
10
+ if (!await isDaemonAvailableAsync()) return void 0;
11
+ const service = await resolveGatewayService();
12
+ if (!await service.isLoaded({ env: process.env })) return void 0;
13
+ return (await service.readCommand(process.env))?.environment?.XOPC_GATEWAY_TOKEN?.trim() || void 0;
14
+ } catch {
15
+ return;
16
+ }
17
+ }
7
18
  /**
8
19
  * Create the token subcommand for managing gateway authentication token.
9
20
  */
@@ -14,6 +25,7 @@ function createTokenCommand() {
14
25
  const log = createLogger("GatewayTokenCommand");
15
26
  try {
16
27
  const config = loadConfig(configPath);
28
+ const installedServiceToken = await readInstalledServiceToken();
17
29
  if (options.generate) {
18
30
  const newToken = crypto.randomBytes(24).toString("hex");
19
31
  config.gateway = config.gateway || {};
@@ -31,9 +43,16 @@ function createTokenCommand() {
31
43
  console.log("");
32
44
  console.log("Or set environment variable:");
33
45
  console.log(` export XOPC_GATEWAY_TOKEN=${newToken}`);
46
+ if (installedServiceToken && installedServiceToken !== newToken) {
47
+ console.log("");
48
+ console.log("⚠️ The installed gateway service still uses its previous token.");
49
+ console.log(" Run `xopc gateway service install --force`, then restart the service.");
50
+ }
34
51
  process.exit(0);
35
52
  } else {
36
- const currentToken = config.gateway?.auth?.token;
53
+ const configToken = config.gateway?.auth?.token?.trim();
54
+ const environmentToken = process.env.XOPC_GATEWAY_TOKEN?.trim();
55
+ const currentToken = installedServiceToken || environmentToken || configToken;
37
56
  if ((config.gateway?.auth?.mode || "token") === "none") {
38
57
  console.log("⚠️ Gateway authentication is disabled (mode: none)");
39
58
  console.log("");
@@ -46,6 +65,14 @@ function createTokenCommand() {
46
65
  console.log(` ${currentToken}`);
47
66
  console.log("");
48
67
  console.log(`Preview: ${tokenPreview}`);
68
+ if (installedServiceToken) console.log("Source: installed gateway service");
69
+ else if (environmentToken) console.log("Source: XOPC_GATEWAY_TOKEN");
70
+ else console.log(`Source: ${configPath}`);
71
+ if (installedServiceToken && configToken && installedServiceToken !== configToken) {
72
+ console.log("");
73
+ console.log("⚠️ Token drift detected: the installed service token overrides the config token.");
74
+ console.log(" Run `xopc gateway service install --force`, then restart the service to sync.");
75
+ }
49
76
  console.log("");
50
77
  console.log("Usage:");
51
78
  console.log(` xopc gateway --token ${currentToken}`);
@@ -19,9 +19,6 @@ function routeId(kind, url) {
19
19
  function resolveSecureDeviceRoutes(config) {
20
20
  const tunnelStatus = getTunnelService().getStatus();
21
21
  const candidates = [{
22
- kind: "custom-https",
23
- url: secureUrl(resolveReverseProxyPublicUrl(config))
24
- }, {
25
22
  kind: "xopc-secure-link",
26
23
  url: tunnelStatus.state === "connected" ? secureUrl(tunnelStatus.publicUrl) : null
27
24
  }];
@@ -30,6 +27,10 @@ function resolveSecureDeviceRoutes(config) {
30
27
  kind: "tailscale",
31
28
  url: secureUrl(`https://${tailscale.hostname}`)
32
29
  });
30
+ candidates.push({
31
+ kind: "custom-https",
32
+ url: secureUrl(resolveReverseProxyPublicUrl(config))
33
+ });
33
34
  const seen = /* @__PURE__ */ new Set();
34
35
  const routes = [];
35
36
  for (const candidate of candidates) {
@@ -5,8 +5,8 @@ import { resolveAllowedBrowserOrigins, resolveGatewayServiceListenPort } from ".
5
5
  import { resolveGatewayEffectiveHost } from "../../config/gateway-bind.js";
6
6
  import { loadTunnelState } from "../../tunnel/tunnel-state.js";
7
7
  import { resolveReverseProxyPublicUrl } from "../public-url.js";
8
- import { BROWSER_EXTENSION_ID } from "../../browser/extension-identity.js";
9
8
  import { maxSessionInputRequestBodyBytes } from "../chat-limits.js";
9
+ import { BROWSER_EXTENSION_ID } from "../../browser/extension-identity.js";
10
10
  import { isLoopbackIpAddress, isTrustedProxyAddress } from "../client-ip.js";
11
11
  import { buildGatewayConsoleCspHeader } from "../security/csp.js";
12
12
  import { checkBrowserOrigin, isChromeExtensionOrigin } from "../security/origin-check.js";
@@ -217,7 +217,10 @@ function createHonoApp(config) {
217
217
  });
218
218
  registerPublicGatewayRoutes(app, service);
219
219
  registerPublicHtmlPreviewRoute(app);
220
- registerDeviceAuthPublicRoutes(app);
220
+ registerDeviceAuthPublicRoutes(app, { getTrustedProxyContext: () => ({
221
+ trustedProxies: service.currentConfig.gateway?.trustedProxies,
222
+ allowRealIpFallback: service.currentConfig.gateway?.allowRealIpFallback === true
223
+ }) });
221
224
  registerPublicExtensionAssetRoutes(app, service);
222
225
  registerPublicLocalAppPreviewRoutes(app, service);
223
226
  const authenticated = new Hono();
@@ -1,5 +1,5 @@
1
- import { parseTurnContextRefs } from "../../../agent/source-context/types.js";
2
1
  import { validateWebchatAttachments, validateWebchatContent } from "../../chat-limits.js";
2
+ import { parseTurnContextRefs } from "../../../agent/source-context/types.js";
3
3
  import { replaceLatestSessionTurn, submitSessionInput } from "./session-input-handler.js";
4
4
  //#region src/gateway/hono/routes/agent-stream.ts
5
5
  function registerAgentStreamRoutes(authenticated, deps) {
@@ -49,6 +49,10 @@ function isLocalOwnerRequest(c, service) {
49
49
  return false;
50
50
  }
51
51
  }
52
+ function isOwnerRequest(c) {
53
+ const principal = getGatewayPrincipal(c);
54
+ return principal.kind === "owner" || principal.kind === "trusted-proxy";
55
+ }
52
56
  function chromeDevicePrincipal(c) {
53
57
  const principal = getGatewayPrincipal(c);
54
58
  if (principal.kind !== "device" || !principal.deviceId) return void 0;
@@ -235,6 +239,28 @@ function registerBrowserRoutes(authenticated, deps) {
235
239
  }, 500);
236
240
  }
237
241
  });
242
+ authenticated.get("/api/browser/extension/archive", strictRateLimitMiddleware, async (c) => {
243
+ if (!isOwnerRequest(c)) return c.json({
244
+ ok: false,
245
+ error: "Owner access required."
246
+ }, 403);
247
+ try {
248
+ const { createBrowserExtensionArchive } = await import("../../../browser/providers/browser-ext-install.js");
249
+ const archive = await createBrowserExtensionArchive();
250
+ const body = new Uint8Array(archive.data.byteLength);
251
+ body.set(archive.data);
252
+ return c.body(body, 200, {
253
+ "Content-Type": "application/zip",
254
+ "Content-Disposition": `attachment; filename="${archive.filename}"`,
255
+ "Cache-Control": "private, no-store"
256
+ });
257
+ } catch (error) {
258
+ return c.json({
259
+ ok: false,
260
+ error: error instanceof Error ? error.message : String(error)
261
+ }, 500);
262
+ }
263
+ });
238
264
  authenticated.post("/api/browser/extension/open", strictRateLimitMiddleware, async (c) => {
239
265
  if (!isLocalOwnerRequest(c, service)) return c.json({
240
266
  ok: false,
@@ -1,3 +1,10 @@
1
- import type { Hono } from 'hono';
2
- export declare function registerPairingApprovalPublicRoutes(app: Hono): void;
1
+ import type { Context, Hono } from 'hono';
2
+ export type PairingPublicRouteOptions = {
3
+ getTrustedProxyContext?: () => {
4
+ trustedProxies?: string[];
5
+ allowRealIpFallback?: boolean;
6
+ };
7
+ };
8
+ export declare function pairingClientKey(c: Context, options?: PairingPublicRouteOptions): string;
9
+ export declare function registerPairingApprovalPublicRoutes(app: Hono, options?: PairingPublicRouteOptions): void;
3
10
  export declare function registerPairingApprovalAdminRoutes(app: Hono): void;
@@ -1,9 +1,41 @@
1
1
  import { devicePairingCompleteSchema, devicePairingProofSchema, devicePairingRequestSchema } from "../../../../packages/gateway-contract/src/device-pairing.js";
2
2
  import { getOrCreateGatewayIdentity, signGatewayPayload } from "../../../storage/sqlite/gateway-identity-repository.js";
3
+ import { resolveClientIpFromRequest } from "../../client-ip.js";
4
+ import { buckets } from "../../rate-limit/buckets.js";
5
+ import "../../rate-limit/index.js";
3
6
  import { DevicePairingError, cancelDevicePairingSetup, decideDevicePairingRequest, operateDevicePairingRequest, readDevicePairingSetup, submitDevicePairingRequest } from "../../../storage/sqlite/device-pairing-approval.js";
4
7
  import os from "node:os";
5
8
  import { z } from "zod";
9
+ import { bodyLimit } from "hono/body-limit";
10
+ import { getConnInfo } from "@hono/node-server/conninfo";
6
11
  //#region src/gateway/hono/routes/device-pairing-approval.ts
12
+ function pairingClientKey(c, options) {
13
+ let remoteAddress;
14
+ try {
15
+ remoteAddress = getConnInfo(c).remote.address;
16
+ } catch {
17
+ remoteAddress = void 0;
18
+ }
19
+ const proxy = options?.getTrustedProxyContext?.();
20
+ return resolveClientIpFromRequest({
21
+ remoteAddress,
22
+ getHeader: (name) => c.req.header(name),
23
+ trustedProxies: proxy?.trustedProxies,
24
+ allowRealIpFallback: proxy?.allowRealIpFallback
25
+ });
26
+ }
27
+ function pairingBlocked(c, key) {
28
+ const blocked = buckets.pairingExchange().check(key);
29
+ if (!blocked.blocked) return void 0;
30
+ c.header("Retry-After", String(blocked.retryAfterSec));
31
+ return c.json({
32
+ ok: false,
33
+ error: {
34
+ code: "PAIRING_RATE_LIMITED",
35
+ message: "Too many failed pairing attempts"
36
+ }
37
+ }, 429);
38
+ }
7
39
  function pairingError(c, error) {
8
40
  if (error instanceof DevicePairingError) return c.json({
9
41
  ok: false,
@@ -20,7 +52,7 @@ function pairingError(c, error) {
20
52
  }
21
53
  }, 500);
22
54
  }
23
- function registerPairingApprovalPublicRoutes(app) {
55
+ function registerPairingApprovalPublicRoutes(app, options) {
24
56
  for (const action of [
25
57
  "request",
26
58
  "status",
@@ -28,15 +60,21 @@ function registerPairingApprovalPublicRoutes(app) {
28
60
  "cancel"
29
61
  ]) {
30
62
  const path = action === "request" ? "/api/device-pairing/requests" : `/api/device-pairing/requests/:id/${action}`;
31
- app.post(path, async (c) => {
63
+ app.post(path, bodyLimit({ maxSize: 4 * 1024 }), async (c) => {
64
+ const clientKey = pairingClientKey(c, options);
65
+ const blocked = pairingBlocked(c, clientKey);
66
+ if (blocked) return blocked;
32
67
  const parsed = (action === "request" ? devicePairingRequestSchema : action === "complete" ? devicePairingCompleteSchema : devicePairingProofSchema).safeParse(await c.req.json().catch(() => null));
33
- if (!parsed.success || action !== "request" && parsed.data.requestId !== c.req.param("id")) return c.json({
34
- ok: false,
35
- error: {
36
- code: "BAD_REQUEST",
37
- message: "Invalid pairing request"
38
- }
39
- }, 400);
68
+ if (!parsed.success || action !== "request" && parsed.data.requestId !== c.req.param("id")) {
69
+ buckets.pairingExchange().fail(clientKey);
70
+ return c.json({
71
+ ok: false,
72
+ error: {
73
+ code: "BAD_REQUEST",
74
+ message: "Invalid pairing request"
75
+ }
76
+ }, 400);
77
+ }
40
78
  try {
41
79
  const result = action === "request" ? { request: submitDevicePairingRequest(parsed.data) } : operateDevicePairingRequest(action, parsed.data);
42
80
  const signedPayload = Buffer.from(JSON.stringify({
@@ -48,12 +86,14 @@ function registerPairingApprovalPublicRoutes(app) {
48
86
  nonce: parsed.data.nonce,
49
87
  issuedAt: Date.now()
50
88
  })).toString("base64url");
89
+ buckets.pairingExchange().succeed(clientKey);
51
90
  return c.json({
52
91
  ok: true,
53
92
  signedPayload,
54
93
  signature: signGatewayPayload(signedPayload)
55
94
  });
56
95
  } catch (error) {
96
+ buckets.pairingExchange().fail(clientKey);
57
97
  return pairingError(c, error);
58
98
  }
59
99
  });
@@ -97,4 +137,4 @@ function registerPairingApprovalAdminRoutes(app) {
97
137
  });
98
138
  }
99
139
  //#endregion
100
- export { registerPairingApprovalAdminRoutes, registerPairingApprovalPublicRoutes };
140
+ export { pairingClientKey, registerPairingApprovalAdminRoutes, registerPairingApprovalPublicRoutes };
@@ -1,4 +1,5 @@
1
1
  import type { Hono } from 'hono';
2
2
  import type { AuthenticatedRouteDeps } from './deps.js';
3
- export declare function registerDeviceAuthPublicRoutes(app: Hono): void;
3
+ import { type PairingPublicRouteOptions } from './device-pairing-approval.js';
4
+ export declare function registerDeviceAuthPublicRoutes(app: Hono, options?: PairingPublicRouteOptions): void;
4
5
  export declare function registerDeviceRoutes(authenticated: Hono, deps: AuthenticatedRouteDeps): void;
@@ -1,4 +1,4 @@
1
- import { devicePairingTargetKindSchema } from "../../../../packages/gateway-contract/src/device-pairing.js";
1
+ import { devicePairingInvitationPayloadSchema, devicePairingTargetKindSchema, formatBrowserPairingInvitation } from "../../../../packages/gateway-contract/src/device-pairing.js";
2
2
  import { listDevices, revokeDevice, rotateDeviceRefreshToken } from "../../../storage/sqlite/device-access-repository.js";
3
3
  import { getGatewayIdentityPublicKeyRaw, getOrCreateGatewayIdentity, signGatewayPayload } from "../../../storage/sqlite/gateway-identity-repository.js";
4
4
  import { createDevicePairingSetup, isDevicePairingSetupActive } from "../../../storage/sqlite/device-pairing-repository.js";
@@ -6,7 +6,7 @@ import { resolveSecureDeviceRoutes } from "../../device-routes.js";
6
6
  import { buckets } from "../../rate-limit/buckets.js";
7
7
  import "../../rate-limit/index.js";
8
8
  import { getGatewayPrincipal } from "../../security/gateway-principal.js";
9
- import { registerPairingApprovalAdminRoutes, registerPairingApprovalPublicRoutes } from "./device-pairing-approval.js";
9
+ import { pairingClientKey, registerPairingApprovalAdminRoutes, registerPairingApprovalPublicRoutes } from "./device-pairing-approval.js";
10
10
  import os from "node:os";
11
11
  import { z } from "zod";
12
12
  import { bodyLimit } from "hono/body-limit";
@@ -21,10 +21,10 @@ const refreshRequestSchema = z.strictObject({
21
21
  });
22
22
  const pairingProbeSchema = z.strictObject({ pairingId: z.string().uuid() });
23
23
  const pairingSetupSchema = z.strictObject({ targetKind: devicePairingTargetKindSchema });
24
- function pairingLinkPayload(input) {
25
- return Buffer.from(JSON.stringify(input)).toString("base64url");
24
+ function pairingPayload(input) {
25
+ return Buffer.from(JSON.stringify(devicePairingInvitationPayloadSchema.parse(input))).toString("base64url");
26
26
  }
27
- function registerDeviceAuthPublicRoutes(app) {
27
+ function registerDeviceAuthPublicRoutes(app, options) {
28
28
  app.post("/api/gateway-identity/challenge", bodyLimit({ maxSize: 512 }), async (c) => {
29
29
  const budget = buckets.identityChallenge().consume("gateway");
30
30
  if (!budget.allowed) {
@@ -46,7 +46,7 @@ function registerDeviceAuthPublicRoutes(app) {
46
46
  signature: signGatewayPayload(signedPayload)
47
47
  });
48
48
  });
49
- registerPairingApprovalPublicRoutes(app);
49
+ registerPairingApprovalPublicRoutes(app, options);
50
50
  app.post("/api/device-auth/refresh", async (c) => {
51
51
  const parsed = refreshRequestSchema.safeParse(await c.req.json().catch(() => null));
52
52
  if (!parsed.success) return c.json({
@@ -81,21 +81,37 @@ function registerDeviceAuthPublicRoutes(app) {
81
81
  }, 401);
82
82
  }
83
83
  });
84
- app.post("/api/device-pairing/probe", async (c) => {
84
+ app.post("/api/device-pairing/probe", bodyLimit({ maxSize: 512 }), async (c) => {
85
+ const clientKey = pairingClientKey(c, options);
86
+ const blocked = buckets.pairingExchange().check(clientKey);
87
+ if (blocked.blocked) {
88
+ c.header("Retry-After", String(blocked.retryAfterSec));
89
+ return c.json({
90
+ ok: false,
91
+ error: {
92
+ code: "PAIRING_RATE_LIMITED",
93
+ message: "Too many failed pairing attempts"
94
+ }
95
+ }, 429);
96
+ }
85
97
  const parsed = pairingProbeSchema.safeParse(await c.req.json().catch(() => null));
86
- if (!parsed.success || !isDevicePairingSetupActive(parsed.data.pairingId)) return c.json({
87
- ok: false,
88
- error: {
89
- code: "PAIRING_NOT_FOUND",
90
- message: "Pairing setup not found"
91
- }
92
- }, 404);
98
+ if (!parsed.success || !isDevicePairingSetupActive(parsed.data.pairingId)) {
99
+ buckets.pairingExchange().fail(clientKey);
100
+ return c.json({
101
+ ok: false,
102
+ error: {
103
+ code: "PAIRING_NOT_FOUND",
104
+ message: "Pairing setup not found"
105
+ }
106
+ }, 404);
107
+ }
93
108
  const identity = getOrCreateGatewayIdentity();
94
109
  const payload = Buffer.from(JSON.stringify({
95
110
  gatewayId: identity.id,
96
111
  pairingId: parsed.data.pairingId,
97
112
  issuedAt: Date.now()
98
113
  })).toString("base64url");
114
+ buckets.pairingExchange().succeed(clientKey);
99
115
  return c.json({
100
116
  ok: true,
101
117
  signedPayload: payload,
@@ -136,7 +152,7 @@ function registerDeviceRoutes(authenticated, deps) {
136
152
  }, 409);
137
153
  const setup = createDevicePairingSetup(routes, Date.now(), { targetKind: parsed.data.targetKind });
138
154
  const identity = getOrCreateGatewayIdentity();
139
- const encoded = pairingLinkPayload({
155
+ const encoded = pairingPayload({
140
156
  version: 3,
141
157
  pairingToken: setup.token,
142
158
  gatewayId: identity.id,
@@ -146,11 +162,12 @@ function registerDeviceRoutes(authenticated, deps) {
146
162
  targetKind: setup.targetKind,
147
163
  expiresAt: setup.expiresAt
148
164
  });
165
+ const invitation = setup.targetKind === "mobile" ? { universalLink: `https://link.xopc.ai/connect#p=${encoded}` } : { browserInvitation: formatBrowserPairingInvitation(encoded) };
149
166
  return c.json({
150
167
  ok: true,
151
168
  setup: {
152
169
  id: setup.id,
153
- universalLink: `https://link.xopc.ai/connect#p=${encoded}`,
170
+ ...invitation,
154
171
  expiresAt: setup.expiresAt,
155
172
  routes,
156
173
  protocolVersion: 3,
@@ -136,7 +136,7 @@ export declare function replaceLatestSessionTurn(c: Context, deps: Authenticated
136
136
  code: "TARGET_NOT_FOUND" | "NOT_LATEST" | "SESSION_BUSY" | "BAD_REQUEST" | "CONTEXT_UNAVAILABLE";
137
137
  message: string;
138
138
  };
139
- }, 400 | 404 | 409, "json">) | (Response & import("hono").TypedResponse<{
139
+ }, 404 | 400 | 409, "json">) | (Response & import("hono").TypedResponse<{
140
140
  ok: true;
141
141
  payload: {
142
142
  sessionKey: string;
@@ -1,9 +1,9 @@
1
1
  import { browserPageContextsInputSchema } from "../../../../packages/gateway-contract/src/browser-page-context.js";
2
2
  import { endpointTurnClaimSchema } from "../../../../packages/endpoint-tools-protocol/src/index.js";
3
3
  import { init_providers, resolveModel } from "../../../providers/index.js";
4
+ import { validateWebchatAttachments, validateWebchatContent } from "../../chat-limits.js";
4
5
  import { parseTurnContextRefs } from "../../../agent/source-context/types.js";
5
6
  import { getModelThinking } from "../../../providers/model-thinking.js";
6
- import { validateWebchatAttachments, validateWebchatContent } from "../../chat-limits.js";
7
7
  import { withModelConfigLock } from "../../../session/model-config-lock.js";
8
8
  import { browserPageContextToAgentContext } from "../../../agent/source-context/browser-page.js";
9
9
  //#region src/gateway/hono/routes/session-input-handler.ts
@@ -1,7 +1,7 @@
1
1
  import { chooseModelThinking } from "../../../../packages/gateway-contract/src/model-selection.js";
2
2
  import { init_providers, resolveModel } from "../../../providers/index.js";
3
- import { getModelThinking } from "../../../providers/model-thinking.js";
4
3
  import { validateWebchatAttachments, validateWebchatContent } from "../../chat-limits.js";
4
+ import { getModelThinking } from "../../../providers/model-thinking.js";
5
5
  import { validateSideChatSelections } from "../../side-chat/context-snapshot.js";
6
6
  import { SideChatError, parseThinkingLevel } from "../../side-chat/manager.js";
7
7
  import "../../side-chat/index.js";
@@ -1,3 +1,5 @@
1
+ import { createLogger } from "../../../utils/logger/index.js";
2
+ import { init_logger } from "../../../utils/logger.js";
1
3
  import { init_public_url, validatePublicUrl } from "../../../config/public-url.js";
2
4
  import { TUNNEL_REGISTRATION_SECRET_REQUIRED_CODE, TunnelRegistrationSecretError, getTunnelRegistrationSecretMeta, readTunnelRegistrationSecretFromConfigOnly, resolveTunnelBrokerUrl } from "../../../tunnel/env.js";
3
5
  import { logTunnelAudit } from "../../../tunnel/tunnel-audit.js";
@@ -9,9 +11,11 @@ import { applyTunnelConsentToConfig, mergeTunnelConfigPatch, setTunnelEnabledInC
9
11
  import { getGatewayPrincipal } from "../../security/gateway-principal.js";
10
12
  import { consumeTunnelMutationLimit } from "../../../tunnel/tunnel-rate-limit.js";
11
13
  import "../../../tunnel/index.js";
12
- import { provisionTunnelRegistrationKey } from "../../../tunnel/xopc-cloud-registration.js";
14
+ import { TunnelRegistrationProvisionError, provisionTunnelRegistrationKey } from "../../../tunnel/xopc-cloud-registration.js";
13
15
  //#region src/gateway/hono/routes/tunnel.ts
16
+ init_logger();
14
17
  init_public_url();
18
+ const log = createLogger("Gateway:Tunnel");
15
19
  async function configureTunnelFromService(deps, opts) {
16
20
  await configureTunnelFromGatewayConfig(deps.service.currentConfig, opts);
17
21
  }
@@ -161,6 +165,10 @@ function registerTunnelRoutes(authenticated, deps) {
161
165
  });
162
166
  authenticated.post("/api/tunnel/registration-key", tunnelMutationLimit, async (c) => {
163
167
  const config = deps.service.currentConfig;
168
+ if (readTunnelRegistrationSecretFromConfigOnly(config)) return c.json({
169
+ ok: true,
170
+ reused: true
171
+ });
164
172
  try {
165
173
  const merged = mergeTunnelConfigPatch(config, { registrationSecret: await provisionTunnelRegistrationKey() });
166
174
  if (merged.ok === false) return c.json({
@@ -176,9 +184,33 @@ function registerTunnelRoutes(authenticated, deps) {
176
184
  return c.json({ ok: true });
177
185
  } catch (error) {
178
186
  const message = error instanceof Error ? error.message : String(error);
187
+ if (error instanceof TunnelRegistrationProvisionError) {
188
+ const status = error.code === "tunnel_key_limit_reached" ? 409 : error.code === "tunnel_oauth_required" ? 403 : 502;
189
+ log.warn({
190
+ err: error,
191
+ code: error.code,
192
+ upstreamStatus: error.status,
193
+ phase: "registration_key"
194
+ }, `Tunnel registration key failed: ${message}`);
195
+ return c.json({
196
+ ok: false,
197
+ error: {
198
+ code: error.code,
199
+ message
200
+ }
201
+ }, status);
202
+ }
203
+ log.warn({
204
+ err: error,
205
+ errorMessage: message,
206
+ phase: "registration_key"
207
+ }, `Tunnel registration key failed: ${message}`);
179
208
  return c.json({
180
209
  ok: false,
181
- error: message
210
+ error: {
211
+ code: "tunnel_registration_failed",
212
+ message
213
+ }
182
214
  }, 502);
183
215
  }
184
216
  });
@@ -144,7 +144,8 @@ var BucketRegistry = class {
144
144
  if (!this.pairingExchangeLimiter) this.pairingExchangeLimiter = new FailureLimiter({
145
145
  maxFailures: 30,
146
146
  windowMs: 5 * 6e4,
147
- blockDurationMs: 5 * 6e4
147
+ blockDurationMs: 5 * 6e4,
148
+ burstCoalesceMs: 0
148
149
  });
149
150
  return this.pairingExchangeLimiter;
150
151
  }
@@ -9,8 +9,8 @@ import { init_session_key, parseSessionKey } from "../../routing/session-key.js"
9
9
  import { resolveExecutionContext } from "../../tasks/execution-context.js";
10
10
  import { TaskRunCoordinator } from "../../tasks/task-run-coordinator.js";
11
11
  import { formatAgentRunErrorForClient } from "../../agent/client-error-format.js";
12
- import { prependEnvelopeTimestamp } from "../../channels/envelope-timestamp.js";
13
12
  import "../chat-limits.js";
13
+ import { prependEnvelopeTimestamp } from "../../channels/envelope-timestamp.js";
14
14
  import { ChatStreamMapper } from "../chat-stream/mapper.js";
15
15
  import { resolveWebchatSessionKey } from "../resolve-webchat-session-key.js";
16
16
  import { recordExplicitRelationshipFollowUp } from "../../user-context/relationship-continuity.js";