@xopcai/xopc 0.0.270 → 0.0.272

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 (245) hide show
  1. package/dist/browser-ext/dist/assets/{auth-cwEj8rFb.js → auth-C1QI-uMG.js} +65 -14
  2. package/dist/browser-ext/dist/background.js +23 -2
  3. package/dist/browser-ext/dist/pairing-handoff.js +51 -0
  4. package/dist/browser-ext/dist/sidepanel.html +1 -1
  5. package/dist/browser-ext/dist/sidepanel.js +39 -27
  6. package/dist/browser-ext/manifest.json +12 -1
  7. package/dist/extensions/telegram/xopc.extension.json +1 -1
  8. package/dist/gateway/static/root/assets/{activity-store-D_0FmdRj.js → activity-store-DffqjeqQ.js} +1 -1
  9. package/dist/gateway/static/root/assets/{agent-avatar-dicebear-IZvEXak_.js → agent-avatar-dicebear-Cwj4HCYY.js} +1 -1
  10. package/dist/gateway/static/root/assets/agent-defaults-settings-panel-6CLfLxVc.js +1 -0
  11. package/dist/gateway/static/root/assets/agents-B6mu64fY.js +1 -0
  12. package/dist/gateway/static/root/assets/{api-O3TTQ4qN.js → api-CGLdye4i.js} +1 -1
  13. package/dist/gateway/static/root/assets/{app-management-settings-panel-Cqqsbzri.js → app-management-settings-panel-BLujSbaw.js} +1 -1
  14. package/dist/gateway/static/root/assets/{appearance-settings-DaJyf32s.js → appearance-settings-BXMcueb8.js} +1 -1
  15. package/dist/gateway/static/root/assets/apps-page-BEgy4hY3.js +1 -0
  16. package/dist/gateway/static/root/assets/{archive-plugin-kDPnDTJI.js → archive-plugin-D4bUampf.js} +1 -1
  17. package/dist/gateway/static/root/assets/{attachment-load-CJ3VceKS.js → attachment-load-CBGNA2Ys.js} +1 -1
  18. package/dist/gateway/static/root/assets/{automations-page-JwMtU8XB.js → automations-page-dQyolNmy.js} +1 -1
  19. package/dist/gateway/static/root/assets/automations-workspace-DxRFrmrV.js +6 -0
  20. package/dist/gateway/static/root/assets/{binary-plugins-DzyZXLBX.js → binary-plugins-bbvp81h2.js} +1 -1
  21. package/dist/gateway/static/root/assets/{block-editor-kTt3gYIJ.js → block-editor-C8e6Wri4.js} +3 -3
  22. package/dist/gateway/static/root/assets/{browser-automation-inputs-BKohPYR4.js → browser-automation-inputs-oJWJtQa6.js} +1 -1
  23. package/dist/gateway/static/root/assets/{browser-automations-page-ktQfFPk-.js → browser-automations-page-CAcJQSjN.js} +2 -2
  24. package/dist/gateway/static/root/assets/browser-settings-page-TLAlqcPo.js +8 -0
  25. package/dist/gateway/static/root/assets/capabilities-settings-panel-z-K_jrZz.js +8 -0
  26. package/dist/gateway/static/root/assets/channels-settings-Dzrk33aB.js +1 -0
  27. package/dist/gateway/static/root/assets/{channels-status-swr-DrqzYmDr.js → channels-status-swr-CfOPNnVr.js} +1 -1
  28. package/dist/gateway/static/root/assets/{chat-terminal-dock-CIFGGLGa.js → chat-terminal-dock-9ZOl92kh.js} +2 -2
  29. package/dist/gateway/static/root/assets/clock-3-DnrAnuyZ.js +1 -0
  30. package/dist/gateway/static/root/assets/connectors-page-B1dIoRNi.js +2 -0
  31. package/dist/gateway/static/root/assets/cron-expression-C26e13YU.js +2 -0
  32. package/dist/gateway/static/root/assets/date-picker-Dj6FRLF9.js +1 -0
  33. package/dist/gateway/static/root/assets/{dependency-picker-D3vNIjRI.js → dependency-picker-D1q89tVc.js} +1 -1
  34. package/dist/gateway/static/root/assets/{desktop-pet-fQwRb_3p.js → desktop-pet-DfqZBvhp.js} +1 -1
  35. package/dist/gateway/static/root/assets/{desktop-pet-settings-DfOJ-GvT.js → desktop-pet-settings-HCEh2MOm.js} +1 -1
  36. package/dist/gateway/static/root/assets/device-pairing-wizard-CPth92s6.js +1 -0
  37. package/dist/gateway/static/root/assets/{extension-debug-page-Cn53jB5-.js → extension-debug-page-C8cAWVoK.js} +1 -1
  38. package/dist/gateway/static/root/assets/{extension-page-IDpLCxpC.js → extension-page-5N87Vaym.js} +1 -1
  39. package/dist/gateway/static/root/assets/extension-settings-page-D85SAUFP.js +1 -0
  40. package/dist/gateway/static/root/assets/{gateway-config-swr-B7dcmP7Q.js → gateway-config-swr-BjsDnB_n.js} +1 -1
  41. package/dist/gateway/static/root/assets/gateway-settings-Dqx8PmcT.js +1 -0
  42. package/dist/gateway/static/root/assets/{gateway-startup-retry-JGhGMhVZ.js → gateway-startup-retry-BIf0eLWX.js} +1 -1
  43. package/dist/gateway/static/root/assets/git-branch-Dh9uxTot.js +1 -0
  44. package/dist/gateway/static/root/assets/heartbeat-settings-BpDWGE1a.js +1 -0
  45. package/dist/gateway/static/root/assets/home-page-DJaqpC8-.js +1 -0
  46. package/dist/gateway/static/root/assets/{image-generation-api-BJLX7Qrh.js → image-generation-api-1m8xTVS_.js} +1 -1
  47. package/dist/gateway/static/root/assets/index-C6PmD0cK.js +74 -0
  48. package/dist/gateway/static/root/assets/index-DKy1Qn1_.css +2 -0
  49. package/dist/gateway/static/root/assets/{keyboard-shortcuts-settings-CBvMTFFx.js → keyboard-shortcuts-settings-Byy8mCSq.js} +1 -1
  50. package/dist/gateway/static/root/assets/local-app-workbench-page-l2AEiG2C.js +2 -0
  51. package/dist/gateway/static/root/assets/{local-apps-page-APCMNDvU.js → local-apps-page-BPo0wBgM.js} +1 -1
  52. package/dist/gateway/static/root/assets/locale-store-CRNble2Z.js +1 -0
  53. package/dist/gateway/static/root/assets/logs-page-X5FdLlXK.js +2 -0
  54. package/dist/gateway/static/root/assets/management-settings-DT6NoIjm.js +1 -0
  55. package/dist/gateway/static/root/assets/{markdown-view-BwjpJVPs.js → markdown-view-AglreSOj.js} +1 -1
  56. package/dist/gateway/static/root/assets/{media-plugins-C10byeaa.js → media-plugins-Bzka9qXu.js} +1 -1
  57. package/dist/gateway/static/root/assets/message-circle-Badhj-4D.js +1 -0
  58. package/dist/gateway/static/root/assets/message-square-fVE7Ghj1.js +1 -0
  59. package/dist/gateway/static/root/assets/{messages-D0V_bgRP.js → messages-DH_HCgmw.js} +3 -3
  60. package/dist/gateway/static/root/assets/note-detail-panel-BUJPoaGZ.js +5 -0
  61. package/dist/gateway/static/root/assets/notes-page-BwFpWRuo.js +8 -0
  62. package/dist/gateway/static/root/assets/{page-tabs-qQ36zncF.js → page-tabs-Ys0p65Lt.js} +1 -1
  63. package/dist/gateway/static/root/assets/popover-select-Dz_Qis2R.js +1 -0
  64. package/dist/gateway/static/root/assets/{preview-open-alternatives-BiAJvvCo.js → preview-open-alternatives-iknB5xEp.js} +1 -1
  65. package/dist/gateway/static/root/assets/{proactive-card-CcNBqHoH.js → proactive-card-BPZDgNNR.js} +1 -1
  66. package/dist/gateway/static/root/assets/{proactive-page-C3K61jve.js → proactive-page-CxdZR-p2.js} +1 -1
  67. package/dist/gateway/static/root/assets/{product-open-page-CunnDP4w.js → product-open-page-W1o3f-kz.js} +1 -1
  68. package/dist/gateway/static/root/assets/project-detail-page-BU1RKAcy.js +1 -0
  69. package/dist/gateway/static/root/assets/projects-page-z0X0tL8G.js +1 -0
  70. package/dist/gateway/static/root/assets/{registry-api-0JSegbNl.js → registry-api-DmYFpjsH.js} +1 -1
  71. package/dist/gateway/static/root/assets/remote-access-hub-NNTut2I8.js +1 -0
  72. package/dist/gateway/static/root/assets/{runtime-tools-settings-panel-bu_mwBTr.js → runtime-tools-settings-panel-DP1XyYO8.js} +2 -2
  73. package/dist/gateway/static/root/assets/{schema-form-BE0sgZyz.js → schema-form-BMYKdzsH.js} +1 -1
  74. package/dist/gateway/static/root/assets/sessions-page-B6SsHlz5.js +1 -0
  75. package/dist/gateway/static/root/assets/settings-advanced-gate-BQu8bAcN.js +1 -0
  76. package/dist/gateway/static/root/assets/{settings-form-section-CBo517lV.js → settings-form-section-2LGjz7bu.js} +1 -1
  77. package/dist/gateway/static/root/assets/{settings-page-DBPimx93.js → settings-page-7DjpC9_C.js} +1 -1
  78. package/dist/gateway/static/root/assets/setup-status-panel-Dj9jFWJt.js +1 -0
  79. package/dist/gateway/static/root/assets/{share-preview-page-DdV75VTe.js → share-preview-page-BxYdSi8c.js} +1 -1
  80. package/dist/gateway/static/root/assets/shares-settings-CMqE0KuK.js +1 -0
  81. package/dist/gateway/static/root/assets/skill-reload-api-BXqSJymN.js +1 -0
  82. package/dist/gateway/static/root/assets/{skills-page-Cn_-nvRo.js → skills-page-DNEScMZH.js} +1 -1
  83. package/dist/gateway/static/root/assets/skills-page.utils-q88l65YU.js +1 -0
  84. package/dist/gateway/static/root/assets/system-settings-panel-8NIssGKj.js +1 -0
  85. package/dist/gateway/static/root/assets/task-detail-page-L6dp9v2y.js +1 -0
  86. package/dist/gateway/static/root/assets/{theme-store-1wryLgBQ.js → theme-store-BXZG3S25.js} +1 -1
  87. package/dist/gateway/static/root/assets/tunnel-api-CWsRPM84.js +1 -0
  88. package/dist/gateway/static/root/assets/{use-autosave-N_lm4FdD.js → use-autosave-BwQuoTQU.js} +1 -1
  89. package/dist/gateway/static/root/assets/user-model-page-D4BucMrZ.js +2 -0
  90. package/dist/gateway/static/root/assets/{voice-api-key-field-DMsyVfri.js → voice-api-key-field-Bd03Ic0s.js} +1 -1
  91. package/dist/gateway/static/root/assets/{voice-config-api-QWVmiAp3.js → voice-config-api-gggUp3u9.js} +1 -1
  92. package/dist/gateway/static/root/assets/{work-discovery-overlay-BC0vAxp1.js → work-discovery-overlay-DF8PR_pK.js} +1 -1
  93. package/dist/gateway/static/root/assets/{workflow-run-setup-panel-Cz9jQIlP.js → workflow-run-setup-panel-BiAAWlir.js} +2 -2
  94. package/dist/gateway/static/root/assets/workflows-page-DryKUMfA.js +6 -0
  95. package/dist/gateway/static/root/index.html +15 -11
  96. package/dist/package.js +1 -1
  97. package/dist/packages/gateway-contract/src/device-pairing.js +2 -1
  98. package/dist/packages/gateway-contract/src/session-identity.js +37 -0
  99. package/dist/src/agent/memory/compaction-chunks.d.ts +18 -0
  100. package/dist/src/agent/memory/compaction-chunks.js +53 -0
  101. package/dist/src/agent/memory/compaction-request.d.ts +12 -0
  102. package/dist/src/agent/memory/compaction-request.js +72 -0
  103. package/dist/src/agent/memory/compaction.d.ts +3 -0
  104. package/dist/src/agent/memory/compaction.js +166 -118
  105. package/dist/src/agent/service.js +1 -0
  106. package/dist/src/browser/native-messaging-host.js +3 -1
  107. package/dist/src/cli/bin.js +2 -2
  108. package/dist/src/cli/commands/agent.js +2 -2
  109. package/dist/src/cli/commands/auth.js +1 -1
  110. package/dist/src/cli/commands/doctor/checks/database-schema.js +1 -1
  111. package/dist/src/cli/commands/extension-dev.js +1 -1
  112. package/dist/src/cli/commands/extension-marketplace.js +1 -1
  113. package/dist/src/cli/commands/extension.js +1 -1
  114. package/dist/src/cli/commands/gateway/lifecycle.js +1 -1
  115. package/dist/src/cli/commands/gateway-ssh-tunnel.js +1 -1
  116. package/dist/src/cli/commands/models.js +2 -2
  117. package/dist/src/cli/commands/onboard.js +1 -1
  118. package/dist/src/cli/commands/platform.js +1 -1
  119. package/dist/src/cli/commands/profile.js +1 -1
  120. package/dist/src/cli/commands/tunnel.js +6 -1
  121. package/dist/src/cli/commands/update.js +1 -1
  122. package/dist/src/cli/extension-cli-register.js +1 -1
  123. package/dist/src/config/index.js +1 -1
  124. package/dist/src/gateway/heartbeat/service.js +1 -1
  125. package/dist/src/gateway/hono/app.js +1 -0
  126. package/dist/src/gateway/hono/middleware/auth.d.ts +1 -2
  127. package/dist/src/gateway/hono/middleware/auth.js +49 -31
  128. package/dist/src/gateway/hono/middleware/strict-rate-limit.js +11 -15
  129. package/dist/src/gateway/hono/routes/browser-session.d.ts +3 -0
  130. package/dist/src/gateway/hono/routes/browser-session.js +43 -0
  131. package/dist/src/gateway/hono/routes/devices.js +61 -96
  132. package/dist/src/gateway/hono/routes/exposure.js +3 -7
  133. package/dist/src/gateway/hono/routes/lazy-bundles.js +8 -0
  134. package/dist/src/gateway/hono/routes/models.js +60 -1
  135. package/dist/src/gateway/hono/routes/notes.js +3 -7
  136. package/dist/src/gateway/hono/routes/sessions.js +11 -0
  137. package/dist/src/gateway/hono/routes/shares.js +8 -20
  138. package/dist/src/gateway/hono/routes/site-shares.js +4 -8
  139. package/dist/src/gateway/hono/routes/tunnel.js +13 -13
  140. package/dist/src/gateway/image-generation-setup.d.ts +17 -0
  141. package/dist/src/gateway/image-generation-setup.js +30 -21
  142. package/dist/src/gateway/rate-limit/buckets.d.ts +3 -0
  143. package/dist/src/gateway/rate-limit/buckets.js +10 -0
  144. package/dist/src/gateway/run-loop.js +1 -1
  145. package/dist/src/gateway/runtime-config.js +2 -0
  146. package/dist/src/gateway/security/browser-session.d.ts +5 -0
  147. package/dist/src/gateway/security/browser-session.js +45 -0
  148. package/dist/src/gateway/security/gateway-principal.d.ts +1 -0
  149. package/dist/src/gateway/server.js +1 -1
  150. package/dist/src/gateway/service.d.ts +1 -0
  151. package/dist/src/gateway/service.js +11 -1
  152. package/dist/src/heartbeat/index.js +1 -1
  153. package/dist/src/infra/rate-limit/keyed-store.d.ts +3 -0
  154. package/dist/src/infra/rate-limit/keyed-store.js +9 -2
  155. package/dist/src/proactive/policy/service.d.ts +1 -1
  156. package/dist/src/realtime/runtime.d.ts +2 -1
  157. package/dist/src/realtime/runtime.js +9 -3
  158. package/dist/src/realtime/tickets.d.ts +1 -0
  159. package/dist/src/realtime/tickets.js +3 -0
  160. package/dist/src/session/store.d.ts +2 -0
  161. package/dist/src/session/store.js +17 -2
  162. package/dist/src/session/types.d.ts +2 -1
  163. package/dist/src/storage/sqlite/browser-session-repository.d.ts +17 -0
  164. package/dist/src/storage/sqlite/browser-session-repository.js +54 -0
  165. package/dist/src/storage/sqlite/device-pairing-approval.js +2 -1
  166. package/dist/src/storage/sqlite/device-pairing-repository.d.ts +4 -10
  167. package/dist/src/storage/sqlite/device-pairing-repository.js +10 -62
  168. package/dist/src/storage/sqlite/index.d.ts +1 -1
  169. package/dist/src/storage/sqlite/index.js +2 -2
  170. package/dist/src/storage/sqlite/migrations/164_browser_sessions.sql +8 -0
  171. package/dist/src/storage/sqlite/migrations/165_device_pairing_target.sql +40 -0
  172. package/dist/src/storage/sqlite/migrations/runner.d.ts +1 -1
  173. package/dist/src/storage/sqlite/migrations/runner.js +3 -3
  174. package/dist/src/storage/sqlite/migrations/runner.ts +1 -1
  175. package/dist/src/storage/sqlite/schema.js +1 -1
  176. package/dist/src/storage/sqlite/session-identity-sql.d.ts +2 -0
  177. package/dist/src/storage/sqlite/session-identity-sql.js +18 -0
  178. package/dist/src/storage/sqlite/session-repository.js +26 -22
  179. package/dist/src/tunnel/auth-policy.d.ts +3 -0
  180. package/dist/src/tunnel/auth-policy.js +8 -0
  181. package/dist/src/tunnel/broker-client.d.ts +5 -15
  182. package/dist/src/tunnel/broker-client.js +37 -49
  183. package/dist/src/tunnel/frpc-binary.d.ts +8 -6
  184. package/dist/src/tunnel/frpc-binary.js +76 -88
  185. package/dist/src/tunnel/frpc-config.d.ts +2 -1
  186. package/dist/src/tunnel/frpc-config.js +29 -12
  187. package/dist/src/tunnel/frpc-release.d.ts +27 -0
  188. package/dist/src/tunnel/frpc-release.js +30 -0
  189. package/dist/src/tunnel/gateway-lifecycle.js +8 -2
  190. package/dist/src/tunnel/index.js +2 -1
  191. package/dist/src/tunnel/registration-proof.d.ts +19 -0
  192. package/dist/src/tunnel/registration-proof.js +37 -0
  193. package/dist/src/tunnel/tunnel-persist.js +3 -1
  194. package/dist/src/tunnel/tunnel-service.d.ts +1 -0
  195. package/dist/src/tunnel/tunnel-service.js +35 -8
  196. package/dist/src/tunnel/tunnel-state.js +11 -5
  197. package/dist/src/tunnel/tunnel-types.d.ts +2 -0
  198. package/dist/src/tunnel/well-known.js +5 -1
  199. package/dist/src/user-context/config.js +2 -2
  200. package/dist/src/utils/logger/redact.js +4 -0
  201. package/dist/src/voice/realtime/runtime.d.ts +1 -0
  202. package/dist/src/voice/realtime/runtime.js +17 -1
  203. package/dist/src/voice/realtime/runtime.types.d.ts +1 -0
  204. package/dist/src/workflows/service/workflow-run-service.js +2 -0
  205. package/dist/src/workflows/service/workflow-session-bridge.d.ts +1 -0
  206. package/dist/src/workflows/service/workflow-session-bridge.js +1 -0
  207. package/package.json +4 -4
  208. package/dist/gateway/static/root/assets/agent-defaults-settings-panel-CE3ygr3l.js +0 -1
  209. package/dist/gateway/static/root/assets/agents-DzG2qw-c.js +0 -1
  210. package/dist/gateway/static/root/assets/apps-page-B4lX6yoT.js +0 -1
  211. package/dist/gateway/static/root/assets/automations-workspace-BiNBewg0.js +0 -6
  212. package/dist/gateway/static/root/assets/browser-settings-page-Dgdt5uVL.js +0 -8
  213. package/dist/gateway/static/root/assets/capabilities-settings-panel-BpDW-R9f.js +0 -7
  214. package/dist/gateway/static/root/assets/channels-settings-DU4EQnR0.js +0 -1
  215. package/dist/gateway/static/root/assets/connectors-page-CfJqy284.js +0 -2
  216. package/dist/gateway/static/root/assets/cron-expression-BNwbnrEb.js +0 -2
  217. package/dist/gateway/static/root/assets/date-picker-DKyirU4K.js +0 -1
  218. package/dist/gateway/static/root/assets/extension-settings-page-B1dds2bP.js +0 -1
  219. package/dist/gateway/static/root/assets/gateway-settings-pp9jN0wl.js +0 -1
  220. package/dist/gateway/static/root/assets/heartbeat-settings-B8_zTyev.js +0 -1
  221. package/dist/gateway/static/root/assets/home-page-g1KdAdLb.js +0 -1
  222. package/dist/gateway/static/root/assets/index-1zRt56Zt.css +0 -2
  223. package/dist/gateway/static/root/assets/index-OMnem2nr.js +0 -74
  224. package/dist/gateway/static/root/assets/local-app-workbench-page-bj75v2ZE.js +0 -2
  225. package/dist/gateway/static/root/assets/locale-store-BThkG0W5.js +0 -1
  226. package/dist/gateway/static/root/assets/logs-page-BX7n39zV.js +0 -2
  227. package/dist/gateway/static/root/assets/management-settings-CM62j8ZX.js +0 -1
  228. package/dist/gateway/static/root/assets/mobile-pairing-wizard-BCWfwVx3.js +0 -1
  229. package/dist/gateway/static/root/assets/note-detail-panel-koOqb0gX.js +0 -5
  230. package/dist/gateway/static/root/assets/notes-page-BH54rY3B.js +0 -8
  231. package/dist/gateway/static/root/assets/popover-select-CKBYnT3c.js +0 -1
  232. package/dist/gateway/static/root/assets/project-detail-page-SL2g8MZY.js +0 -1
  233. package/dist/gateway/static/root/assets/projects-page-BtxazT5J.js +0 -1
  234. package/dist/gateway/static/root/assets/remote-access-hub-BSb7b67A.js +0 -1
  235. package/dist/gateway/static/root/assets/sessions-page-ljcS2KVz.js +0 -1
  236. package/dist/gateway/static/root/assets/settings-advanced-gate-CR9Y9LQm.js +0 -1
  237. package/dist/gateway/static/root/assets/setup-status-panel-J3AnooPW.js +0 -1
  238. package/dist/gateway/static/root/assets/shares-settings-AAm4Iwcp.js +0 -1
  239. package/dist/gateway/static/root/assets/skill-reload-api-pRCjP5sm.js +0 -1
  240. package/dist/gateway/static/root/assets/skills-page.utils-CfVAc_lu.js +0 -1
  241. package/dist/gateway/static/root/assets/system-settings-panel-B-pzalp-.js +0 -1
  242. package/dist/gateway/static/root/assets/task-detail-page-CmrLy3PO.js +0 -1
  243. package/dist/gateway/static/root/assets/tunnel-api-Cgpnm2fL.js +0 -1
  244. package/dist/gateway/static/root/assets/user-model-page-u3QLDX6L.js +0 -2
  245. package/dist/gateway/static/root/assets/workflows-page-DA6OOK6X.js +0 -6
@@ -59,12 +59,10 @@ function getAgentImageGenerationConfig(config, agentIdRaw) {
59
59
  model: resolveEffectiveAgentConfigForAgent(config, agentId).config.models.imageGeneration ?? null
60
60
  };
61
61
  }
62
- function prepareImageGenerationSetup(config, agentIdRaw, input) {
63
- const agentId = normalizeAgentId(agentIdRaw);
64
- if (!config.agents.list.some((entry) => entry.enabled !== false && normalizeAgentId(entry.id) === agentId)) return {
65
- ok: false,
66
- error: `Agent not found: ${agentId}`
67
- };
62
+ function getDefaultImageGenerationConfig(config) {
63
+ return { model: config.agents.defaults.models.imageGeneration ?? null };
64
+ }
65
+ function prepareImageGenerationConfig(config, input, applyModel) {
68
66
  const providerId = input.providerId.trim();
69
67
  const provider = getImageGenerationProvider(providerId);
70
68
  if (!provider) return {
@@ -82,11 +80,6 @@ function prepareImageGenerationSetup(config, agentIdRaw, input) {
82
80
  error: parsedProviderConfig.error
83
81
  };
84
82
  const next = structuredClone(config);
85
- const index = next.agents.list.findIndex((entry) => entry.enabled !== false && normalizeAgentId(entry.id) === agentId);
86
- if (index < 0) return {
87
- ok: false,
88
- error: `Agent not found: ${agentId}`
89
- };
90
83
  next.providers = {
91
84
  ...next.providers ?? {},
92
85
  [providerId]: {
@@ -94,15 +87,11 @@ function prepareImageGenerationSetup(config, agentIdRaw, input) {
94
87
  ...parsedProviderConfig.value
95
88
  }
96
89
  };
97
- const entry = next.agents.list[index];
98
- entry.models = {
99
- ...entry.models ?? {},
100
- imageGeneration: {
101
- primary: `${providerId}/${modelId}`,
102
- fallbacks: [],
103
- autoProviderFallback: false
104
- }
105
- };
90
+ applyModel(next, {
91
+ primary: `${providerId}/${modelId}`,
92
+ fallbacks: [],
93
+ autoProviderFallback: false
94
+ });
106
95
  const parsed = ConfigSchema.safeParse(next);
107
96
  if (!parsed.success) return {
108
97
  ok: false,
@@ -115,6 +104,26 @@ function prepareImageGenerationSetup(config, agentIdRaw, input) {
115
104
  modelId
116
105
  };
117
106
  }
107
+ function prepareDefaultImageGenerationSetup(config, input) {
108
+ return prepareImageGenerationConfig(config, input, (next, model) => {
109
+ next.agents.defaults.models.imageGeneration = model;
110
+ });
111
+ }
112
+ function prepareImageGenerationSetup(config, agentIdRaw, input) {
113
+ const agentId = normalizeAgentId(agentIdRaw);
114
+ if (!config.agents.list.some((entry) => entry.enabled !== false && normalizeAgentId(entry.id) === agentId)) return {
115
+ ok: false,
116
+ error: `Agent not found: ${agentId}`
117
+ };
118
+ return prepareImageGenerationConfig(config, input, (next, model) => {
119
+ const index = next.agents.list.findIndex((entry) => entry.enabled !== false && normalizeAgentId(entry.id) === agentId);
120
+ const entry = next.agents.list[index];
121
+ entry.models = {
122
+ ...entry.models ?? {},
123
+ imageGeneration: model
124
+ };
125
+ });
126
+ }
118
127
  async function verifyImageGenerationCredential(input) {
119
128
  const apiKey = input.apiKey.trim();
120
129
  if (!apiKey) return {
@@ -165,4 +174,4 @@ async function verifyImageGenerationCredential(input) {
165
174
  }
166
175
  }
167
176
  //#endregion
168
- export { getAgentImageGenerationConfig, getImageGenerationCatalog, prepareImageGenerationSetup, verifyImageGenerationCredential };
177
+ export { getAgentImageGenerationConfig, getDefaultImageGenerationConfig, getImageGenerationCatalog, prepareDefaultImageGenerationSetup, prepareImageGenerationSetup, verifyImageGenerationCredential };
@@ -34,6 +34,7 @@ export declare function authPolicyConfig(cfg: ResolvedAuthRateLimitConfig): Auth
34
34
  declare class BucketRegistry {
35
35
  private authFailureLimiter?;
36
36
  private authFailureSignature?;
37
+ private identityChallengeLimiter?;
37
38
  private strictApiLimiter?;
38
39
  private chatApiLimiter?;
39
40
  private mediaApiLimiter?;
@@ -50,6 +51,8 @@ declare class BucketRegistry {
50
51
  * config read.
51
52
  */
52
53
  authFailure(cfg: ResolvedAuthRateLimitConfig): FailureLimiter;
54
+ /** Global pre-authentication signing budget; independent of authenticated traffic. */
55
+ identityChallenge(): RateLimiter;
53
56
  /** Authenticated admin / mutation endpoints — 150 req / 60 s per client IP. */
54
57
  strictApi(): RateLimiter;
55
58
  /** Interactive chat actions — 180 req / 60 s per client IP. */
@@ -46,6 +46,7 @@ function authPolicyConfig(cfg) {
46
46
  var BucketRegistry = class {
47
47
  authFailureLimiter;
48
48
  authFailureSignature;
49
+ identityChallengeLimiter;
49
50
  strictApiLimiter;
50
51
  chatApiLimiter;
51
52
  mediaApiLimiter;
@@ -75,6 +76,13 @@ var BucketRegistry = class {
75
76
  }
76
77
  return this.authFailureLimiter;
77
78
  }
79
+ /** Global pre-authentication signing budget; independent of authenticated traffic. */
80
+ identityChallenge() {
81
+ return this.identityChallengeLimiter ??= new RateLimiter({
82
+ maxRequests: 300,
83
+ windowMs: 6e4
84
+ });
85
+ }
78
86
  /** Authenticated admin / mutation endpoints — 150 req / 60 s per client IP. */
79
87
  strictApi() {
80
88
  if (!this.strictApiLimiter) this.strictApiLimiter = new RateLimiter({
@@ -158,6 +166,7 @@ var BucketRegistry = class {
158
166
  }
159
167
  destroyAll() {
160
168
  this.authFailureLimiter?.destroy();
169
+ this.identityChallengeLimiter?.destroy();
161
170
  this.strictApiLimiter?.destroy();
162
171
  this.chatApiLimiter?.destroy();
163
172
  this.mediaApiLimiter?.destroy();
@@ -169,6 +178,7 @@ var BucketRegistry = class {
169
178
  this.sharePublicShortLimiter?.destroy();
170
179
  this.sharePublicLongLimiter?.destroy();
171
180
  this.authFailureLimiter = void 0;
181
+ this.identityChallengeLimiter = void 0;
172
182
  this.strictApiLimiter = void 0;
173
183
  this.chatApiLimiter = void 0;
174
184
  this.mediaApiLimiter = void 0;
@@ -1,6 +1,6 @@
1
1
  import { loadConfig } from "../config/loader.js";
2
- import { isRestartEnabled } from "../config/commands.flags.js";
3
2
  import "../config/index.js";
3
+ import { isRestartEnabled } from "../config/commands.flags.js";
4
4
  import { runBootstrapMigrationsSync } from "../migrations/runner.js";
5
5
  import { restartGatewayProcessWithFreshPid } from "./respawn.js";
6
6
  import { acquireGatewayLock } from "./lock.js";
@@ -1,6 +1,7 @@
1
1
  import { isAllInterfacesHost, isLoopbackHost, resolveGatewayCorsOrigins } from "./host.js";
2
2
  import { resolveGatewayListenPlan } from "./listen.js";
3
3
  import { assertTailscaleExposureCompatible } from "./tailscale-lifecycle.js";
4
+ import { requireTunnelGatewayToken } from "../tunnel/auth-policy.js";
4
5
  import { isAuthRateLimitGloballyDisabled, isGatewayStrictSecurityEnabled } from "./rate-limit/env-flags.js";
5
6
  import "./rate-limit/index.js";
6
7
  //#region src/gateway/runtime-config.ts
@@ -15,6 +16,7 @@ function hasSharedSecret(auth) {
15
16
  * Throws when bind/auth/origin combinations are unsafe for network exposure.
16
17
  */
17
18
  function assertGatewayRuntimeConfig(params) {
19
+ if (params.cfg.tunnel?.enabled) requireTunnelGatewayToken(params.auth);
18
20
  const { bindMode, bindHost, customBindHost } = resolveGatewayListenPlan({
19
21
  cfg: params.cfg,
20
22
  bindOverride: params.bindOverride
@@ -0,0 +1,5 @@
1
+ import type { Context } from 'hono';
2
+ export declare function readBrowserSessionCookie(c: Context): string | undefined;
3
+ export declare function browserCookieRequestAllowed(c: Context): boolean;
4
+ export declare function writeBrowserSessionCookie(c: Context, token: string, expiresAt: number): void;
5
+ export declare function clearBrowserSessionCookie(c: Context): void;
@@ -0,0 +1,45 @@
1
+ import { isLoopbackHost } from "../host.js";
2
+ import { deleteCookie, getCookie, setCookie } from "hono/cookie";
3
+ //#region src/gateway/security/browser-session.ts
4
+ function origin(c) {
5
+ return new URL(`http://${c.req.header("host") ?? new URL(c.req.url).host}`);
6
+ }
7
+ function secure(c) {
8
+ return !isLoopbackHost(origin(c).hostname.replace(/^\[|\]$/g, ""));
9
+ }
10
+ function name(c) {
11
+ return secure(c) ? "__Host-xopc-session" : "xopc-local-session";
12
+ }
13
+ function readBrowserSessionCookie(c) {
14
+ return getCookie(c, name(c));
15
+ }
16
+ function browserCookieRequestAllowed(c) {
17
+ const requestOrigin = c.req.header("origin");
18
+ if (requestOrigin) try {
19
+ const parsed = new URL(requestOrigin);
20
+ const localOrigin = isLoopbackHost(parsed.hostname.replace(/^\[|\]$/g, ""));
21
+ return (parsed.host === origin(c).host || !secure(c) && localOrigin) && (parsed.protocol === "https:" || !secure(c) && parsed.protocol === "http:");
22
+ } catch {
23
+ return false;
24
+ }
25
+ return ["GET", "HEAD"].includes(c.req.method) && c.req.header("sec-fetch-site") !== "cross-site";
26
+ }
27
+ function writeBrowserSessionCookie(c, token, expiresAt) {
28
+ setCookie(c, name(c), token, {
29
+ httpOnly: true,
30
+ secure: secure(c),
31
+ sameSite: "Strict",
32
+ path: "/",
33
+ expires: new Date(expiresAt)
34
+ });
35
+ }
36
+ function clearBrowserSessionCookie(c) {
37
+ deleteCookie(c, name(c), {
38
+ httpOnly: true,
39
+ secure: secure(c),
40
+ sameSite: "Strict",
41
+ path: "/"
42
+ });
43
+ }
44
+ //#endregion
45
+ export { browserCookieRequestAllowed, clearBrowserSessionCookie, readBrowserSessionCookie, writeBrowserSessionCookie };
@@ -5,6 +5,7 @@ export type GatewayPrincipal = {
5
5
  principalId: string;
6
6
  deviceId?: string;
7
7
  accessSessionId?: string;
8
+ browserSessionId?: string;
8
9
  scopes: readonly GatewayScope[];
9
10
  };
10
11
  export declare function setGatewayPrincipal(c: Context, principal: GatewayPrincipal): void;
@@ -1,6 +1,6 @@
1
1
  import { resolveGatewayBindHost, resolveGatewayListenHosts } from "../config/gateway-bind.js";
2
- import { resolveGatewayListenPlan } from "./listen.js";
3
2
  import { ensureXopcCliOnPath } from "../infra/path-env.js";
3
+ import { resolveGatewayListenPlan } from "./listen.js";
4
4
  import { GatewayService } from "./service.js";
5
5
  import { handleSiteShareUpgrade } from "../share/site-share-router.js";
6
6
  import { createHonoApp } from "./hono/app.js";
@@ -48,6 +48,7 @@ export declare class GatewayService {
48
48
  private readonly modelCatalogSync;
49
49
  private auth;
50
50
  readonly endpointTools: EndpointToolRuntime;
51
+ private isRealtimePrincipalActive;
51
52
  readonly realtime: RealtimeRuntime;
52
53
  readonly voiceRealtime: VoiceRealtimeRuntime;
53
54
  getConfig(): Config;
@@ -122,6 +122,7 @@ import { GatewaySessionsApi } from "./service/sessions-api.js";
122
122
  import { GatewayMarketplaceService } from "./service/marketplace-service.js";
123
123
  import { GatewayConfigCoordinator } from "./service/config-coordinator.js";
124
124
  import { GatewayAgentRunner } from "./service/agent-runner.js";
125
+ import { isBrowserSessionActive } from "../storage/sqlite/browser-session-repository.js";
125
126
  import { RealtimeRuntime } from "../realtime/runtime.js";
126
127
  import { VoiceRealtimeRuntime } from "../voice/realtime/runtime.js";
127
128
  import { DurableVoiceAgentBroker } from "../voice/realtime/agentBroker.js";
@@ -165,8 +166,17 @@ var GatewayService = class {
165
166
  });
166
167
  auth;
167
168
  endpointTools = new EndpointToolRuntime();
168
- realtime = new RealtimeRuntime(this.endpointTools);
169
+ isRealtimePrincipalActive = (principalId) => {
170
+ if (!principalId.startsWith("browser:")) return true;
171
+ try {
172
+ return isBrowserSessionActive(principalId.slice(8), this.auth);
173
+ } catch {
174
+ return false;
175
+ }
176
+ };
177
+ realtime = new RealtimeRuntime(this.endpointTools, this.isRealtimePrincipalActive);
169
178
  voiceRealtime = new VoiceRealtimeRuntime({
179
+ isPrincipalActive: this.isRealtimePrincipalActive,
170
180
  getConversationContext: async (sessionKey, expectedSessionId) => {
171
181
  if ((await this.sessionIndex.getSessionMetadata(sessionKey))?.sessionId !== expectedSessionId) throw new Error("Conversation changed before voice connection");
172
182
  const messages = await this.sessionIndex.loadMessages(sessionKey);
@@ -1,6 +1,6 @@
1
1
  import { DEFAULT_ACK_MAX_CHARS, HEARTBEAT_OK, NO_REPLY, shouldSilence, stripHeartbeatToken } from "./tokens.js";
2
+ import { appendCronEventLines } from "./event-prompt.js";
2
3
  import { isWithinActiveHours } from "./active-hours.js";
3
4
  import { isHeartbeatContentEmpty } from "./content-check.js";
4
- import { appendCronEventLines } from "./event-prompt.js";
5
5
  import { createHeartbeatWake } from "./wake.js";
6
6
  export { DEFAULT_ACK_MAX_CHARS, HEARTBEAT_OK, NO_REPLY, appendCronEventLines, createHeartbeatWake, isHeartbeatContentEmpty, isWithinActiveHours, shouldSilence, stripHeartbeatToken };
@@ -11,6 +11,7 @@ export type Clock = () => number;
11
11
  export type KeyedStoreOptions = {
12
12
  /** Drop entries whose `lastTouchedMs` is older than this. */
13
13
  staleAfterMs: number;
14
+ maxEntries?: number;
14
15
  /** How often the sweep runs. Defaults to `staleAfterMs / 4` (capped at 10min). */
15
16
  cleanupIntervalMs?: number;
16
17
  /** Injected clock for deterministic tests. */
@@ -20,6 +21,8 @@ export declare class KeyedStore<V extends {
20
21
  lastTouchedMs: number;
21
22
  }> {
22
23
  private readonly map;
24
+ private readonly maxEntries;
25
+ private overflow?;
23
26
  private readonly clock;
24
27
  private readonly staleAfterMs;
25
28
  private cleanupTimer?;
@@ -1,10 +1,13 @@
1
1
  //#region src/infra/rate-limit/keyed-store.ts
2
2
  var KeyedStore = class {
3
3
  map = /* @__PURE__ */ new Map();
4
+ maxEntries;
5
+ overflow;
4
6
  clock;
5
7
  staleAfterMs;
6
8
  cleanupTimer;
7
9
  constructor(opts) {
10
+ this.maxEntries = Math.max(1, opts.maxEntries ?? 1e4);
8
11
  this.clock = opts.clock ?? Date.now;
9
12
  this.staleAfterMs = Math.max(1e3, opts.staleAfterMs);
10
13
  const interval = Math.min(600 * 1e3, Math.max(1e3, opts.cleanupIntervalMs ?? Math.floor(this.staleAfterMs / 4)));
@@ -12,10 +15,11 @@ var KeyedStore = class {
12
15
  this.cleanupTimer.unref?.();
13
16
  }
14
17
  get(key) {
15
- return this.map.get(key);
18
+ return this.map.get(key) ?? (this.map.size >= this.maxEntries ? this.overflow : void 0);
16
19
  }
17
20
  set(key, value) {
18
- this.map.set(key, value);
21
+ if (this.map.has(key) || this.map.size < this.maxEntries) this.map.set(key, value);
22
+ else this.overflow ??= value;
19
23
  }
20
24
  delete(key) {
21
25
  this.map.delete(key);
@@ -25,6 +29,7 @@ var KeyedStore = class {
25
29
  }
26
30
  clear() {
27
31
  this.map.clear();
32
+ this.overflow = void 0;
28
33
  }
29
34
  destroy() {
30
35
  if (this.cleanupTimer) {
@@ -32,9 +37,11 @@ var KeyedStore = class {
32
37
  this.cleanupTimer = void 0;
33
38
  }
34
39
  this.map.clear();
40
+ this.overflow = void 0;
35
41
  }
36
42
  sweep() {
37
43
  const now = this.clock();
44
+ if (this.overflow && now - this.overflow.lastTouchedMs > this.staleAfterMs) this.overflow = void 0;
38
45
  for (const [k, v] of this.map.entries()) if (now - v.lastTouchedMs > this.staleAfterMs) this.map.delete(k);
39
46
  }
40
47
  };
@@ -31,7 +31,7 @@ export declare function effectiveProactivePolicy(subscriptionId: string, now?: D
31
31
  digestEnabled: boolean;
32
32
  digestHour: number;
33
33
  digestMinute: number;
34
- preferredChannel: "mobile" | "all" | "auto" | "browser" | "telegram";
34
+ preferredChannel: "mobile" | "telegram" | "browser" | "all" | "auto";
35
35
  suppressWhileViewing: boolean;
36
36
  telegram: {
37
37
  chatId: string;
@@ -5,6 +5,7 @@ import { RealtimeBroker } from './broker.js';
5
5
  import { RealtimeTicketStore } from './tickets.js';
6
6
  export declare class RealtimeRuntime {
7
7
  private readonly endpointTools?;
8
+ private readonly isPrincipalActive;
8
9
  readonly broker: RealtimeBroker;
9
10
  readonly tickets: RealtimeTicketStore;
10
11
  private readonly preauthBudget;
@@ -12,7 +13,7 @@ export declare class RealtimeRuntime {
12
13
  private readonly topicCleanupTimers;
13
14
  private readonly socketsByPrincipal;
14
15
  private closed;
15
- constructor(endpointTools?: EndpointToolRuntime);
16
+ constructor(endpointTools?: EndpointToolRuntime, isPrincipalActive?: (id: string) => boolean);
16
17
  handleUpgrade(req: IncomingMessage, socket: Socket, head: Buffer): boolean;
17
18
  close(): void;
18
19
  disconnectPrincipal(principalId: string): void;
@@ -51,8 +51,9 @@ var RealtimeRuntime = class {
51
51
  topicCleanupTimers = /* @__PURE__ */ new Map();
52
52
  socketsByPrincipal = /* @__PURE__ */ new Map();
53
53
  closed = false;
54
- constructor(endpointTools) {
54
+ constructor(endpointTools, isPrincipalActive = () => true) {
55
55
  this.endpointTools = endpointTools;
56
+ this.isPrincipalActive = isPrincipalActive;
56
57
  this.wss.on("connection", (socket, request) => this.handleConnection(socket, request));
57
58
  this.wss.on("error", (err) => log.error({ err }, "Realtime WebSocket server failed"));
58
59
  }
@@ -91,7 +92,8 @@ var RealtimeRuntime = class {
91
92
  this.socketsByPrincipal.clear();
92
93
  }
93
94
  disconnectPrincipal(principalId) {
94
- for (const socket of this.socketsByPrincipal.get(principalId) ?? []) socket.close(4403, "Device access revoked");
95
+ this.tickets.revokePrincipal(principalId);
96
+ for (const socket of this.socketsByPrincipal.get(principalId) ?? []) socket.terminate();
95
97
  }
96
98
  completeTopic(topic, ttlMs = 5 * 6e4) {
97
99
  const existing = this.topicCleanupTimers.get(topic);
@@ -119,6 +121,10 @@ var RealtimeRuntime = class {
119
121
  };
120
122
  const helloTimer = setTimeout(() => socket.close(4401, "Realtime hello timeout"), REALTIME_HELLO_TIMEOUT_MS);
121
123
  const heartbeatTimer = setInterval(() => {
124
+ if (principalId && !this.isPrincipalActive(principalId)) {
125
+ socket.terminate();
126
+ return;
127
+ }
122
128
  if (Date.now() - lastSeenAt > 45e3) socket.close(4408, "Realtime heartbeat timeout");
123
129
  }, REALTIME_HEARTBEAT_INTERVAL_MS);
124
130
  const sendError = (code, message) => {
@@ -181,7 +187,7 @@ var RealtimeRuntime = class {
181
187
  return;
182
188
  }
183
189
  const claim = this.tickets.consume(message.payload.ticket, message.payload.clientId, message.payload.clientKind);
184
- if (!claim) {
190
+ if (!claim || !this.isPrincipalActive(claim.principalId)) {
185
191
  socket.close(4401, "Realtime authentication failed");
186
192
  return;
187
193
  }
@@ -16,5 +16,6 @@ export declare class RealtimeTicketStore {
16
16
  private readonly claims;
17
17
  issue(clientId: string, clientKind: RealtimeClientKind, principal: Omit<RealtimeTicketClaim, 'clientId' | 'clientKind' | 'expiresAt'>, now?: number): IssuedRealtimeTicket;
18
18
  consume(ticket: string, clientId: string, clientKind: RealtimeClientKind, now?: number): RealtimeTicketClaim | undefined;
19
+ revokePrincipal(principalId: string): void;
19
20
  private prune;
20
21
  }
@@ -31,6 +31,9 @@ var RealtimeTicketStore = class {
31
31
  if (claim.clientId !== clientId || claim.clientKind !== clientKind) return void 0;
32
32
  return claim;
33
33
  }
34
+ revokePrincipal(principalId) {
35
+ for (const [key, claim] of this.claims) if (claim.principalId === principalId) this.claims.delete(key);
36
+ }
34
37
  prune(now) {
35
38
  for (const [key, claim] of this.claims) if (claim.expiresAt < now) this.claims.delete(key);
36
39
  }
@@ -44,6 +44,8 @@ export declare class SessionStore {
44
44
  private compactionHooks;
45
45
  private readonly compactionTails;
46
46
  constructor(options: SessionStoreOptions, windowConfig?: Partial<WindowConfig>, compactionConfig?: Partial<CompactionConfig>);
47
+ /** Refresh policy for subsequent compactions without changing an in-flight request. */
48
+ updateConfig(config: Config): void;
47
49
  setCompactionHooks(hooks: SessionCompactionHooks): void;
48
50
  private runCompactionHook;
49
51
  private resolveWorkspaceCwd;
@@ -46,7 +46,18 @@ var SessionStore = class {
46
46
  constructor(options, windowConfig, compactionConfig) {
47
47
  this.options = options;
48
48
  this.window = new SlidingWindow(windowConfig);
49
- this.compactor = new SessionCompactor(compactionConfig);
49
+ this.compactor = new SessionCompactor({
50
+ ...resolveCompactionPolicy(options.config),
51
+ ...compactionConfig
52
+ });
53
+ }
54
+ /** Refresh policy for subsequent compactions without changing an in-flight request. */
55
+ updateConfig(config) {
56
+ this.options = {
57
+ ...this.options,
58
+ config
59
+ };
60
+ this.compactor = new SessionCompactor(resolveCompactionPolicy(config));
50
61
  }
51
62
  setCompactionHooks(hooks) {
52
63
  this.compactionHooks = hooks;
@@ -509,7 +520,11 @@ var SessionStore = class {
509
520
  messageCount: messages.length,
510
521
  tokenCount
511
522
  });
512
- const result = await this.compactor.compact(snapshot.entries, model, instructions, force, executionOptions);
523
+ const result = await this.compactor.compact(snapshot.entries, model, instructions, force, {
524
+ ...executionOptions,
525
+ sessionKey: key
526
+ });
527
+ executionOptions?.signal?.throwIfAborted();
513
528
  if (result.compacted) {
514
529
  const compacted = await this.applyCompaction(key, result, snapshot);
515
530
  await this.runCompactionHook("after", {
@@ -1,4 +1,5 @@
1
1
  import type { CompactionAudit } from './compaction-types.js';
2
+ import type { SessionDiscoveryQuery } from '@xopcai/gateway-contract';
2
3
  import type { TranscriptStoredRow } from './session-context-for-llm.js';
3
4
  export interface Message {
4
5
  role: 'system' | 'user' | 'assistant' | 'tool' | 'toolResult';
@@ -139,7 +140,7 @@ export interface SessionDetail extends SessionMetadata {
139
140
  transcriptRows?: TranscriptStoredRow[];
140
141
  }
141
142
  /** Session list query parameters */
142
- export interface SessionListQuery {
143
+ export interface SessionListQuery extends SessionDiscoveryQuery {
143
144
  status?: SessionStatus | SessionStatus[];
144
145
  /** Single `sourceChannel`, or comma-separated list (e.g. `telegram,weixin`). */
145
146
  channel?: string;
@@ -0,0 +1,17 @@
1
+ import type { ResolvedGatewayAuth } from '../../gateway/auth.js';
2
+ export declare const BROWSER_SESSION_TTL_MS: number;
3
+ export declare class BrowserSessionLimitError extends Error {
4
+ constructor();
5
+ }
6
+ export declare function createBrowserSession(auth: ResolvedGatewayAuth, replacedSessionId?: string): {
7
+ sessionId: `${string}-${string}-${string}-${string}-${string}`;
8
+ token: string;
9
+ expiresAt: number;
10
+ };
11
+ export declare function authenticateBrowserSession(token: string, auth: ResolvedGatewayAuth): {
12
+ sessionId: string;
13
+ expiresAt: number;
14
+ renewed: boolean;
15
+ } | null;
16
+ export declare function isBrowserSessionActive(sessionId: string, auth: ResolvedGatewayAuth): boolean;
17
+ export declare function deleteBrowserSession(sessionId: string): void;
@@ -0,0 +1,54 @@
1
+ import { getSqliteDatabase, runSqliteWriteTransaction } from "./transaction.js";
2
+ import { createHash, randomBytes, randomUUID } from "node:crypto";
3
+ //#region src/storage/sqlite/browser-session-repository.ts
4
+ const BROWSER_SESSION_TTL_MS = 720 * 36e5;
5
+ const digest = (value) => createHash("sha256").update(value).digest("hex");
6
+ function fingerprint(auth) {
7
+ const credential = auth.mode === "token" ? auth.token : auth.mode === "password" ? auth.password : void 0;
8
+ return credential ? digest(JSON.stringify([auth.mode, credential])) : null;
9
+ }
10
+ var BrowserSessionLimitError = class extends Error {
11
+ constructor() {
12
+ super("Browser session limit reached");
13
+ }
14
+ };
15
+ function createBrowserSession(auth, replacedSessionId) {
16
+ const authFingerprint = fingerprint(auth);
17
+ if (!authFingerprint) throw new Error("Browser sessions require Gateway credentials");
18
+ const sessionId = randomUUID();
19
+ const token = randomBytes(32).toString("base64url");
20
+ const now = Date.now();
21
+ const expiresAt = now + BROWSER_SESSION_TTL_MS;
22
+ runSqliteWriteTransaction((db) => {
23
+ db.prepare("DELETE FROM browser_sessions WHERE expires_at <= ? OR auth_fingerprint <> ?").run(now, authFingerprint);
24
+ if (replacedSessionId) db.prepare("DELETE FROM browser_sessions WHERE session_id = ?").run(replacedSessionId);
25
+ if (db.prepare("SELECT count(*) AS count FROM browser_sessions").get().count >= 32) throw new BrowserSessionLimitError();
26
+ db.prepare("INSERT INTO browser_sessions (session_id, token_hash, auth_fingerprint, created_at, expires_at) VALUES (?, ?, ?, ?, ?)").run(sessionId, digest(token), authFingerprint, now, expiresAt);
27
+ });
28
+ return {
29
+ sessionId,
30
+ token,
31
+ expiresAt
32
+ };
33
+ }
34
+ function authenticateBrowserSession(token, auth) {
35
+ if (!/^[A-Za-z0-9_-]{43}$/.test(token)) return null;
36
+ const row = getSqliteDatabase().prepare("SELECT session_id, expires_at FROM browser_sessions WHERE token_hash = ? AND auth_fingerprint = ? AND expires_at > ?").get(digest(token), fingerprint(auth), Date.now());
37
+ if (!row) return null;
38
+ const renewed = row.expires_at < Date.now() + 168 * 36e5;
39
+ const expiresAt = renewed ? Date.now() + BROWSER_SESSION_TTL_MS : row.expires_at;
40
+ if (renewed) getSqliteDatabase().prepare("UPDATE browser_sessions SET expires_at = ? WHERE session_id = ?").run(expiresAt, row.session_id);
41
+ return {
42
+ sessionId: row.session_id,
43
+ expiresAt,
44
+ renewed
45
+ };
46
+ }
47
+ function isBrowserSessionActive(sessionId, auth) {
48
+ return Boolean(getSqliteDatabase().prepare("SELECT 1 FROM browser_sessions WHERE session_id = ? AND auth_fingerprint = ? AND expires_at > ?").get(sessionId, fingerprint(auth), Date.now()));
49
+ }
50
+ function deleteBrowserSession(sessionId) {
51
+ getSqliteDatabase().prepare("DELETE FROM browser_sessions WHERE session_id = ?").run(sessionId);
52
+ }
53
+ //#endregion
54
+ export { BROWSER_SESSION_TTL_MS, BrowserSessionLimitError, authenticateBrowserSession, createBrowserSession, deleteBrowserSession, isBrowserSessionActive };
@@ -66,7 +66,7 @@ function verifiedSetup(token, now) {
66
66
  const id = /^xopc_pair_([0-9a-f-]{36})_/i.exec(token)?.[1];
67
67
  if (!id) return fail("PAIRING_DENIED", 401);
68
68
  const row = getSqliteDatabase().prepare("SELECT * FROM device_pairing_sessions WHERE pairing_id = ?").get(id);
69
- if (!row || row.protocol_version !== 3 || row.attempts_remaining <= 0) return fail("PAIRING_DENIED", 401);
69
+ if (!row || row.attempts_remaining <= 0) return fail("PAIRING_DENIED", 401);
70
70
  if (!crypto.timingSafeEqual(Buffer.from(row.secret_hash, "hex"), Buffer.from(hash(token), "hex"))) {
71
71
  getSqliteDatabase().prepare("UPDATE device_pairing_sessions SET attempts_remaining = attempts_remaining - 1 WHERE pairing_id = ?").run(id);
72
72
  return fail("PAIRING_DENIED", 401);
@@ -93,6 +93,7 @@ function verifyProof(action, body, deviceJson, now) {
93
93
  function submitDevicePairingRequest(body, now = Date.now()) {
94
94
  const setup = verifiedSetup(String(body.pairingToken), now);
95
95
  const device = devicePairingDeviceSchema.parse(body.device);
96
+ if (!(setup.target_kind === "browser" ? device.platform === "chrome" : setup.target_kind === "mobile" && device.platform !== "chrome")) fail("PAIRING_DEVICE_MISMATCH");
96
97
  const deviceJson = JSON.stringify(device);
97
98
  const autoApprove = isTrustedLocalBrowserSetup(setup, deviceJson);
98
99
  verifyProof("request", body, deviceJson, now);
@@ -1,3 +1,4 @@
1
+ import type { DevicePairingTargetKind } from '@xopcai/gateway-contract';
1
2
  export type DeviceRoute = {
2
3
  id: string;
3
4
  kind: 'xopc-secure-link' | 'tailscale' | 'custom-https' | 'local-browser';
@@ -8,6 +9,7 @@ export type DevicePairingSetup = {
8
9
  token: string;
9
10
  routes: DeviceRoute[];
10
11
  expiresAt: number;
12
+ targetKind: DevicePairingTargetKind;
11
13
  };
12
14
  export type DevicePairingEnrollment = {
13
15
  issuer: 'browser-native-host';
@@ -15,17 +17,9 @@ export type DevicePairingEnrollment = {
15
17
  publicKeyThumbprint: string;
16
18
  nonce: string;
17
19
  };
18
- export type PairingConsumeResult = {
19
- ok: true;
20
- pairingId: string;
21
- routes: DeviceRoute[];
22
- } | {
23
- ok: false;
24
- reason: 'invalid' | 'expired' | 'consumed';
25
- };
26
- export declare function createDevicePairingSetup(routes: readonly DeviceRoute[], now?: number, protocolVersion?: 2 | 3, options?: {
20
+ export declare function createDevicePairingSetup(routes: readonly DeviceRoute[], now: number, options: {
21
+ targetKind: DevicePairingTargetKind;
27
22
  ttlMs?: number;
28
23
  enrollment?: DevicePairingEnrollment;
29
24
  }): DevicePairingSetup;
30
- export declare function consumeDevicePairingToken(token: string, now?: number): PairingConsumeResult;
31
25
  export declare function isDevicePairingSetupActive(pairingId: string, now?: number): boolean;