@xopcai/xopc 0.0.231 → 0.0.232

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 (339) hide show
  1. package/README.md +1 -0
  2. package/README.zh-CN.md +1 -0
  3. package/dist/browser-ext/manifest.json +1 -1
  4. package/dist/extensions/feishu/src/schema/config-schema.d.ts +3 -3
  5. package/dist/extensions/telegram/src/config-schema.d.ts +4 -4
  6. package/dist/extensions/telegram/xopc.extension.json +1 -1
  7. package/dist/extensions/weixin/src/config-schema.d.ts +3 -3
  8. package/dist/gateway/static/root/assets/{activity-store-M0nAY5XB.js → activity-store-VXCWz43N.js} +1 -1
  9. package/dist/gateway/static/root/assets/agent-avatar-dicebear-BXRaZYW8.js +1 -0
  10. package/dist/gateway/static/root/assets/agent-defaults-settings-panel-dR7HMaRX.js +1 -0
  11. package/dist/gateway/static/root/assets/agents-Bileycwe.js +1 -0
  12. package/dist/gateway/static/root/assets/api-wGPgL8xY.js +1 -0
  13. package/dist/gateway/static/root/assets/{app-management-settings-panel-DP7YVRPn.js → app-management-settings-panel-BJfClfPx.js} +1 -1
  14. package/dist/gateway/static/root/assets/{appearance-settings-CVQ5n7y6.js → appearance-settings-C_R0vUAu.js} +1 -1
  15. package/dist/gateway/static/root/assets/{apps-page-DSCia0DT.js → apps-page-CBow8pBI.js} +1 -1
  16. package/dist/gateway/static/root/assets/{archive-plugin-7_DXaMTP.js → archive-plugin-DnFHkSHE.js} +1 -1
  17. package/dist/gateway/static/root/assets/{attachment-load-mmr_mHRr.js → attachment-load-DZrEwytM.js} +1 -1
  18. package/dist/gateway/static/root/assets/{automations-page-DfY-T6Ga.js → automations-page-DpkKajui.js} +1 -1
  19. package/dist/gateway/static/root/assets/automations-workspace-BBntVWgv.js +6 -0
  20. package/dist/gateway/static/root/assets/{binary-plugins-CO9QMEh2.js → binary-plugins-Ckc_Csue.js} +1 -1
  21. package/dist/gateway/static/root/assets/{block-editor-CE14CVOy.js → block-editor-DAC6w-iV.js} +4 -4
  22. package/dist/gateway/static/root/assets/browser-settings-page-DrsMd-Po.js +8 -0
  23. package/dist/gateway/static/root/assets/browser-workflow-inputs-BGd7R8GO.js +1 -0
  24. package/dist/gateway/static/root/assets/{browser-workflows-page-1wgXyoSO.js → browser-workflows-page-DUfj7ntM.js} +2 -2
  25. package/dist/gateway/static/root/assets/capabilities-settings-panel-97gkGeSV.js +7 -0
  26. package/dist/gateway/static/root/assets/channels-settings-CuBsz8UI.js +1 -0
  27. package/dist/gateway/static/root/assets/{channel-recipient-api-C5ABr73b.js → channels-status-swr-CfboECLt.js} +1 -1
  28. package/dist/gateway/static/root/assets/{chat-terminal-dock-CdRW6Euc.js → chat-terminal-dock-CA9fndbZ.js} +2 -2
  29. package/dist/gateway/static/root/assets/{code-editor-dFzqyDXS.js → code-editor-D4_k-6QN.js} +1 -1
  30. package/dist/gateway/static/root/assets/connectors-page-SuL7td7Z.js +2 -0
  31. package/dist/gateway/static/root/assets/cron-expression-CqPu7HYJ.js +2 -0
  32. package/dist/gateway/static/root/assets/date-picker-C8dJsCgk.js +1 -0
  33. package/dist/gateway/static/root/assets/dependency-picker-Iz2wYimy.js +1 -0
  34. package/dist/gateway/static/root/assets/{desktop-pet-CMO-J3cG.js → desktop-pet-C3h40FmG.js} +1 -1
  35. package/dist/gateway/static/root/assets/{desktop-pet-settings-MmOR4A3u.js → desktop-pet-settings-YfeVVzLe.js} +1 -1
  36. package/dist/gateway/static/root/assets/dist-DqPZM2MH2.js +1 -0
  37. package/dist/gateway/static/root/assets/{extension-debug-page-DdH0dG2_.js → extension-debug-page-CChgztz_.js} +1 -1
  38. package/dist/gateway/static/root/assets/{extension-page-Clg-KktN.js → extension-page-DF6ct6_y.js} +1 -1
  39. package/dist/gateway/static/root/assets/extension-settings-page-jgBsjn0F.js +1 -0
  40. package/dist/gateway/static/root/assets/{gateway-config-api-DEHnF4Wm.js → gateway-config-api-CVajuIGL.js} +1 -1
  41. package/dist/gateway/static/root/assets/gateway-config-swr-BGQn8duh.js +1 -0
  42. package/dist/gateway/static/root/assets/gateway-settings-BbssNGid.js +1 -0
  43. package/dist/gateway/static/root/assets/{gateway-startup-retry-B_zrtccP.js → gateway-startup-retry-D2Rnh9fX.js} +1 -1
  44. package/dist/gateway/static/root/assets/heartbeat-settings-CJtXu8iD.js +1 -0
  45. package/dist/gateway/static/root/assets/home-page-Be1o1AXQ.js +1 -0
  46. package/dist/gateway/static/root/assets/html-workspace-editor-C3DKAkMc.js +1 -0
  47. package/dist/gateway/static/root/assets/{image-generation-api-YKP2me5Z.js → image-generation-api-D7W2p5_8.js} +1 -1
  48. package/dist/gateway/static/root/assets/index-B2OJSXX9.js +70 -0
  49. package/dist/gateway/static/root/assets/index-rHjAElxG.css +2 -0
  50. package/dist/gateway/static/root/assets/{keyboard-shortcuts-settings-BvpEeHnm.js → keyboard-shortcuts-settings-BvHYyNus.js} +1 -1
  51. package/dist/gateway/static/root/assets/local-app-workbench-page-COfelgk7.js +2 -0
  52. package/dist/gateway/static/root/assets/{local-apps-page-Bzvn99eH.js → local-apps-page-Cb8t2wWM.js} +1 -1
  53. package/dist/gateway/static/root/assets/locale-store-DLwVxS3V.js +1 -0
  54. package/dist/gateway/static/root/assets/logs-page-DA8oDjeB.js +2 -0
  55. package/dist/gateway/static/root/assets/management-settings-B8Jie4-J.js +1 -0
  56. package/dist/gateway/static/root/assets/markdown-editor-nDLmA_LF.js +2 -0
  57. package/dist/gateway/static/root/assets/markdown-split-BD1r4o2i.js +1 -0
  58. package/dist/gateway/static/root/assets/{markdown-view-RxNHwlNo.js → markdown-view-B-BHEcNd.js} +1 -1
  59. package/dist/gateway/static/root/assets/{media-plugins-D8I7OGnT.js → media-plugins-BCev8fkW.js} +1 -1
  60. package/dist/gateway/static/root/assets/messages-Dp1OxSKt.js +3 -0
  61. package/dist/gateway/static/root/assets/mobile-device-api-DACoHhhk.js +1 -0
  62. package/dist/gateway/static/root/assets/{models-hub-cache-0RjEwafI.js → models-hub-cache-C8zjqmIv.js} +1 -1
  63. package/dist/gateway/static/root/assets/{notes-api-EpgG-OQc.js → notes-api-DEdNKw0I.js} +1 -1
  64. package/dist/gateway/static/root/assets/{notes-page-CCvjGMhN.js → notes-page-BBTLjJwa.js} +1 -1
  65. package/dist/gateway/static/root/assets/notes-workbench-DICgUGPv.js +4 -0
  66. package/dist/gateway/static/root/assets/{oauth-api-BwNLwbI8.js → oauth-api-BNCO2YK5.js} +1 -1
  67. package/dist/gateway/static/root/assets/popover-select-3E24E65N.js +1 -0
  68. package/dist/gateway/static/root/assets/{product-open-page-MmMswmOU.js → product-open-page-fEBbRekH.js} +1 -1
  69. package/dist/gateway/static/root/assets/project-detail-page-CqdkznYJ.js +1 -0
  70. package/dist/gateway/static/root/assets/projects-page-dHDXpMCg.js +1 -0
  71. package/dist/gateway/static/root/assets/{registry-api-BFZrVYfy.js → registry-api-Cvr4Qm7N.js} +1 -1
  72. package/dist/gateway/static/root/assets/remote-access-hub-2feZudJw.js +1 -0
  73. package/dist/gateway/static/root/assets/{runtime-tools-settings-panel-Dx6o7sLu.js → runtime-tools-settings-panel-BT5B74IH.js} +2 -2
  74. package/dist/gateway/static/root/assets/{schema-form-Cdd9c4vt.js → schema-form-C4tB34S5.js} +1 -1
  75. package/dist/gateway/static/root/assets/sessions-page-CO2JCFdy.js +1 -0
  76. package/dist/gateway/static/root/assets/settings-advanced-gate-DxMVaqdn.js +1 -0
  77. package/dist/gateway/static/root/assets/{settings-form-section-D0YxMnhm.js → settings-form-section-D1oXhkgI.js} +1 -1
  78. package/dist/gateway/static/root/assets/settings-loading-skeleton-CldfX6FJ.js +1 -0
  79. package/dist/gateway/static/root/assets/settings-page-c3LJnbvE.js +1 -0
  80. package/dist/gateway/static/root/assets/settings-shell-layer-context-DCp6ykjQ.js +1 -0
  81. package/dist/gateway/static/root/assets/setup-status-panel-DZNHb6A5.js +1 -0
  82. package/dist/gateway/static/root/assets/{share-preview-page-DiqBn-oE.js → share-preview-page-DptCK_mk.js} +1 -1
  83. package/dist/gateway/static/root/assets/shares-settings-DakZ2AGN.js +1 -0
  84. package/dist/gateway/static/root/assets/skill-reload-api-BOiNkWJC.js +1 -0
  85. package/dist/gateway/static/root/assets/{skills-page-CZwhWiRl.js → skills-page-OHBeBtSg.js} +1 -1
  86. package/dist/gateway/static/root/assets/{skills-page.utils-kBGr8opZ.js → skills-page.utils-B9WZbOLC.js} +1 -1
  87. package/dist/gateway/static/root/assets/{system-settings-panel-CPBs6lWi.js → system-settings-panel-Dj_DpOGw.js} +1 -1
  88. package/dist/gateway/static/root/assets/task-detail-page-D5Nbe3C8.js +1 -0
  89. package/dist/gateway/static/root/assets/{theme-store-Qgvo0Ga7.js → theme-store-Cpd3Gd5R.js} +1 -1
  90. package/dist/gateway/static/root/assets/{use-autosave-C4YcEiP9.js → use-autosave-DCl5Yq7N.js} +1 -1
  91. package/dist/gateway/static/root/assets/user-context-page-CAPSUK8B.js +1 -0
  92. package/dist/gateway/static/root/assets/{vendor-codemirror-DRr7hFaD.js → vendor-codemirror--gTq3YAI.js} +16 -16
  93. package/dist/gateway/static/root/assets/voice-api-key-field-BB_ZQV8F.js +1 -0
  94. package/dist/gateway/static/root/assets/voice-config-api-BNiYmpl5.js +1 -0
  95. package/dist/gateway/static/root/assets/{work-discovery-overlay-CKIdPEen.js → work-discovery-overlay-BrIdtdum.js} +1 -1
  96. package/dist/gateway/static/root/assets/{workflow-run-setup-panel-BK44Vm5W.js → workflow-run-setup-panel-hKh-uCLo.js} +2 -2
  97. package/dist/gateway/static/root/assets/workflows-page-ahDvypGO.js +5 -0
  98. package/dist/gateway/static/root/index.html +16 -16
  99. package/dist/package.js +1 -1
  100. package/dist/src/agent/agent-manager.d.ts +1 -2
  101. package/dist/src/agent/agent-manager.js +29 -44
  102. package/dist/src/agent/background-review/run-background-review.js +10 -15
  103. package/dist/src/agent/embedded/run-for-session.js +1 -1
  104. package/dist/src/agent/external-tools/mcp-provider.js +3 -13
  105. package/dist/src/agent/image/generation/runtime.js +1 -1
  106. package/dist/src/agent/image/tool-model-config.js +13 -14
  107. package/dist/src/agent/mcp/bundle-mcp-policy.d.ts +0 -9
  108. package/dist/src/agent/mcp/bundle-mcp-policy.js +1 -13
  109. package/dist/src/agent/mcp/bundle-mcp-runtime.js +1 -1
  110. package/dist/src/agent/mcp/index.js +2 -2
  111. package/dist/src/agent/media-generation/runtime-shared.d.ts +1 -1
  112. package/dist/src/agent/media-generation/runtime-shared.js +1 -1
  113. package/dist/src/agent/memory/create-memory-manager.js +1 -1
  114. package/dist/src/agent/memory/manager.d.ts +1 -1
  115. package/dist/src/{agent-runtime/memory-runtime.d.ts → agent/memory/runtime.d.ts} +1 -1
  116. package/dist/src/{agent-runtime/memory-runtime.js → agent/memory/runtime.js} +2 -3
  117. package/dist/src/agent/orchestration/agent-orchestrator.d.ts +0 -3
  118. package/dist/src/agent/orchestration/agent-orchestrator.js +1 -3
  119. package/dist/src/agent/orchestration/run-agent-turn-with-timeout.js +2 -2
  120. package/dist/src/agent/service.js +0 -1
  121. package/dist/src/agent/starter-agents.js +1 -8
  122. package/dist/src/agent/tools/executor.d.ts +1 -1
  123. package/dist/src/agent/tools/workflow-tool.js +1 -5
  124. package/dist/src/agent-config/index.d.ts +2 -0
  125. package/dist/src/agent-config/index.js +11 -0
  126. package/dist/src/agent-config/resolver.d.ts +11 -0
  127. package/dist/src/agent-config/resolver.js +113 -0
  128. package/dist/src/agent-config/schema.d.ts +491 -0
  129. package/dist/src/agent-config/schema.js +172 -0
  130. package/dist/src/chat-commands/builtins/config.js +2 -2
  131. package/dist/src/chat-commands/builtins/review.js +1 -1
  132. package/dist/src/chat-commands/config-paths.js +1 -1
  133. package/dist/src/cli/commands/agents.js +2 -1
  134. package/dist/src/cli/commands/config.js +1 -1
  135. package/dist/src/cli/commands/doctor/checks/database-schema.js +1 -1
  136. package/dist/src/cli/commands/doctor/checks/provider-auth.js +12 -4
  137. package/dist/src/cli/commands/image.js +3 -4
  138. package/dist/src/cli/commands/models.js +9 -3
  139. package/dist/src/cli/commands/onboard/model.js +12 -11
  140. package/dist/src/cli/commands/onboard.js +1 -1
  141. package/dist/src/cli/commands/tunnel.js +3 -18
  142. package/dist/src/cli/utils/init-workspace-core.d.ts +1 -1
  143. package/dist/src/cli/utils/init-workspace-core.js +1 -1
  144. package/dist/src/commands/agents.config.d.ts +1 -3
  145. package/dist/src/commands/agents.config.js +9 -26
  146. package/dist/src/config/agent-model-intents.d.ts +10 -0
  147. package/dist/src/config/agent-model-intents.js +39 -0
  148. package/dist/src/config/agent-profile.d.ts +7 -9
  149. package/dist/src/config/agent-profile.js +30 -28
  150. package/dist/src/config/default-model.d.ts +1 -11
  151. package/dist/src/config/default-model.js +3 -11
  152. package/dist/src/config/index.js +3 -3
  153. package/dist/src/config/integration.d.ts +1 -1
  154. package/dist/src/config/reload.js +1 -1
  155. package/dist/src/config/rules.js +3 -3
  156. package/dist/src/config/schema.d.ts +187 -452
  157. package/dist/src/config/schema.js +53 -131
  158. package/dist/src/extensions/activation-context.js +7 -11
  159. package/dist/src/gateway/agent-builtin-tools.d.ts +250 -6
  160. package/dist/src/gateway/agent-builtin-tools.js +284 -46
  161. package/dist/src/gateway/agents-admin.d.ts +20 -62
  162. package/dist/src/gateway/agents-admin.js +36 -228
  163. package/dist/src/gateway/device-routes.d.ts +3 -0
  164. package/dist/src/gateway/device-routes.js +47 -0
  165. package/dist/src/gateway/global-defaults-admin.d.ts +6 -20
  166. package/dist/src/gateway/global-defaults-admin.js +20 -120
  167. package/dist/src/gateway/hono/app.js +4 -2
  168. package/dist/src/gateway/hono/lib/config-payload.d.ts +54 -137
  169. package/dist/src/gateway/hono/lib/config-payload.js +2 -4
  170. package/dist/src/gateway/hono/middleware/auth.js +33 -1
  171. package/dist/src/gateway/hono/middleware/scopes.d.ts +2 -12
  172. package/dist/src/gateway/hono/middleware/scopes.js +9 -18
  173. package/dist/src/gateway/hono/routes/agents.js +64 -100
  174. package/dist/src/gateway/hono/routes/connectors.js +1 -1
  175. package/dist/src/gateway/hono/routes/device-push.d.ts +2 -0
  176. package/dist/src/gateway/hono/routes/{mobile.js → device-push.js} +40 -18
  177. package/dist/src/gateway/hono/routes/devices.d.ts +4 -0
  178. package/dist/src/gateway/hono/routes/devices.js +214 -0
  179. package/dist/src/gateway/hono/routes/files.js +5 -1
  180. package/dist/src/gateway/hono/routes/global-defaults.js +5 -9
  181. package/dist/src/gateway/hono/routes/index.js +4 -2
  182. package/dist/src/gateway/hono/routes/lazy-bundles.js +4 -20
  183. package/dist/src/gateway/hono/routes/models.js +2 -2
  184. package/dist/src/gateway/hono/routes/realtime.js +8 -1
  185. package/dist/src/gateway/hono/routes/status.js +9 -16
  186. package/dist/src/gateway/hono/routes/tunnel.d.ts +0 -2
  187. package/dist/src/gateway/hono/routes/tunnel.js +10 -199
  188. package/dist/src/gateway/image-generation-setup.d.ts +2 -2
  189. package/dist/src/gateway/image-generation-setup.js +8 -7
  190. package/dist/src/gateway/public-url.d.ts +1 -1
  191. package/dist/src/gateway/security/gateway-principal.d.ts +11 -0
  192. package/dist/src/gateway/security/gateway-principal.js +12 -0
  193. package/dist/src/gateway/security/gateway-scopes.d.ts +7 -0
  194. package/dist/src/gateway/security/gateway-scopes.js +66 -0
  195. package/dist/src/gateway/security/index.d.ts +2 -1
  196. package/dist/src/gateway/security/index.js +3 -2
  197. package/dist/src/gateway/service.js +7 -7
  198. package/dist/src/gateway/xopc-cloud-capability-setup.js +19 -21
  199. package/dist/src/migrations/index.d.ts +0 -1
  200. package/dist/src/migrations/index.js +1 -2
  201. package/dist/src/migrations/runner.js +1 -2
  202. package/dist/src/notifications/device-store.d.ts +1 -1
  203. package/dist/src/notifications/device-store.js +11 -11
  204. package/dist/src/notifications/store.js +3 -3
  205. package/dist/src/providers/index.js +1 -1
  206. package/dist/src/providers/model-reference-auditor.js +14 -9
  207. package/dist/src/realtime/runtime.d.ts +2 -0
  208. package/dist/src/realtime/runtime.js +27 -3
  209. package/dist/src/realtime/tickets.d.ts +6 -1
  210. package/dist/src/realtime/tickets.js +2 -1
  211. package/dist/src/remote-access/exposure-manager.js +3 -3
  212. package/dist/src/share/share-url.js +2 -2
  213. package/dist/src/storage/sqlite/device-access-repository.d.ts +54 -0
  214. package/dist/src/storage/sqlite/device-access-repository.js +194 -0
  215. package/dist/src/storage/sqlite/device-pairing-repository.d.ts +22 -0
  216. package/dist/src/storage/sqlite/device-pairing-repository.js +86 -0
  217. package/dist/src/storage/sqlite/gateway-identity-repository.d.ts +10 -0
  218. package/dist/src/storage/sqlite/gateway-identity-repository.js +50 -0
  219. package/dist/src/storage/sqlite/index.d.ts +3 -0
  220. package/dist/src/storage/sqlite/index.js +4 -1
  221. package/dist/src/storage/sqlite/migrations/138_device_access.sql +96 -0
  222. package/dist/src/storage/sqlite/migrations/runner.d.ts +1 -1
  223. package/dist/src/storage/sqlite/migrations/runner.js +3 -3
  224. package/dist/src/storage/sqlite/migrations/runner.ts +1 -1
  225. package/dist/src/storage/sqlite/schema.js +1 -1
  226. package/dist/src/tunnel/consent.d.ts +1 -1
  227. package/dist/src/tunnel/consent.js +1 -1
  228. package/dist/src/tunnel/gateway-lifecycle.js +1 -1
  229. package/dist/src/tunnel/index.d.ts +1 -4
  230. package/dist/src/tunnel/index.js +3 -5
  231. package/dist/src/tunnel/tunnel-audit.d.ts +1 -1
  232. package/dist/src/tunnel/tunnel-config.js +1 -1
  233. package/dist/src/tunnel/tunnel-service.d.ts +2 -3
  234. package/dist/src/tunnel/tunnel-service.js +1 -20
  235. package/dist/src/tunnel/tunnel-types.d.ts +0 -6
  236. package/dist/src/workflows/draft/workflow-draft-service.js +2 -2
  237. package/dist/src/workflows/service/workflow-run-service.js +3 -3
  238. package/package.json +3 -3
  239. package/dist/gateway/static/root/assets/agent-avatar-dicebear-CBqUnlvL.js +0 -1
  240. package/dist/gateway/static/root/assets/agents-7YO6znTT.js +0 -68
  241. package/dist/gateway/static/root/assets/api-BywYxpaS.js +0 -1
  242. package/dist/gateway/static/root/assets/automations-workspace-BpvD54D6.js +0 -6
  243. package/dist/gateway/static/root/assets/browser-settings-page-CeESA4Oj.js +0 -8
  244. package/dist/gateway/static/root/assets/browser-workflow-inputs-8NC2qDRF.js +0 -1
  245. package/dist/gateway/static/root/assets/capabilities-settings-panel-B_OXTojB.js +0 -7
  246. package/dist/gateway/static/root/assets/capability-presets-api-o0y9WgAI.js +0 -1
  247. package/dist/gateway/static/root/assets/capability-presets-settings-panel-D5nZecxV.js +0 -3
  248. package/dist/gateway/static/root/assets/channels-settings-BzmnWx6X.js +0 -1
  249. package/dist/gateway/static/root/assets/channels-status-swr-38FsOnR0.js +0 -1
  250. package/dist/gateway/static/root/assets/connectors-page-DX6OkwFQ.js +0 -2
  251. package/dist/gateway/static/root/assets/cron-expression-DYSMZsms.js +0 -2
  252. package/dist/gateway/static/root/assets/date-picker-DGCfX4tK.js +0 -1
  253. package/dist/gateway/static/root/assets/dependency-picker-DHDa4ht1.js +0 -1
  254. package/dist/gateway/static/root/assets/directory-picker-path-field-B9qoxDcn.js +0 -1
  255. package/dist/gateway/static/root/assets/dist-J_ySZvz0.js +0 -1
  256. package/dist/gateway/static/root/assets/extension-settings-page-eP3fJ2M6.js +0 -1
  257. package/dist/gateway/static/root/assets/file-code-corner-byQA1VfD.js +0 -1
  258. package/dist/gateway/static/root/assets/gateway-config-swr-BWGjnmDl.js +0 -1
  259. package/dist/gateway/static/root/assets/gateway-settings-DKkEXXj9.js +0 -1
  260. package/dist/gateway/static/root/assets/heartbeat-settings-DlBPNubY.js +0 -1
  261. package/dist/gateway/static/root/assets/home-page-BnOrLRfs.js +0 -1
  262. package/dist/gateway/static/root/assets/html-workspace-editor-bXK5tq64.js +0 -1
  263. package/dist/gateway/static/root/assets/index-0xQO5B_Q.js +0 -70
  264. package/dist/gateway/static/root/assets/index-CCyIdGuQ.css +0 -2
  265. package/dist/gateway/static/root/assets/local-app-workbench-page-Ch1rdUo8.js +0 -2
  266. package/dist/gateway/static/root/assets/locale-store-CXhjfe4q.js +0 -1
  267. package/dist/gateway/static/root/assets/logs-page-zxx2ExlY.js +0 -2
  268. package/dist/gateway/static/root/assets/management-settings-BQaeyOYm.js +0 -1
  269. package/dist/gateway/static/root/assets/markdown-editor-TWGaF7Xa.js +0 -2
  270. package/dist/gateway/static/root/assets/markdown-split-D9tmIiEm.js +0 -1
  271. package/dist/gateway/static/root/assets/messages-BMdD0LWk.js +0 -3
  272. package/dist/gateway/static/root/assets/notes-workbench-9cgh77VH.js +0 -4
  273. package/dist/gateway/static/root/assets/popover-select-CccGri5Y.js +0 -1
  274. package/dist/gateway/static/root/assets/project-detail-page-hf6pmH4p.js +0 -1
  275. package/dist/gateway/static/root/assets/projects-page-By2U-Wdh.js +0 -1
  276. package/dist/gateway/static/root/assets/remote-access-hub-CUcpp7CQ.js +0 -1
  277. package/dist/gateway/static/root/assets/sessions-page-ByI82xS1.js +0 -1
  278. package/dist/gateway/static/root/assets/settings-advanced-gate-BCoI5IJ8.js +0 -1
  279. package/dist/gateway/static/root/assets/settings-loading-skeleton-y53gEKsF.js +0 -1
  280. package/dist/gateway/static/root/assets/settings-page-BoE_tToG.js +0 -1
  281. package/dist/gateway/static/root/assets/setup-status-panel-BQmyb5KC.js +0 -1
  282. package/dist/gateway/static/root/assets/shares-settings-Bf3VpZ-R.js +0 -1
  283. package/dist/gateway/static/root/assets/skill-reload-api-CXHlbuGS.js +0 -1
  284. package/dist/gateway/static/root/assets/task-detail-page-Bo0KSZ2v.js +0 -1
  285. package/dist/gateway/static/root/assets/typed-models-lib-q5-Vfqd8.js +0 -1
  286. package/dist/gateway/static/root/assets/user-context-page-Cnj7gMX2.js +0 -1
  287. package/dist/gateway/static/root/assets/voice-api-key-field-BML8zCwZ.js +0 -1
  288. package/dist/gateway/static/root/assets/workflows-page-CB98MTwG.js +0 -5
  289. package/dist/src/agent-manifest/index.d.ts +0 -5
  290. package/dist/src/agent-manifest/index.js +0 -10
  291. package/dist/src/agent-manifest/preset-chain.d.ts +0 -10
  292. package/dist/src/agent-manifest/preset-chain.js +0 -30
  293. package/dist/src/agent-manifest/prompt.d.ts +0 -2
  294. package/dist/src/agent-manifest/prompt.js +0 -49
  295. package/dist/src/agent-manifest/resolver.d.ts +0 -32
  296. package/dist/src/agent-manifest/resolver.js +0 -114
  297. package/dist/src/agent-manifest/schema.d.ts +0 -695
  298. package/dist/src/agent-manifest/schema.js +0 -226
  299. package/dist/src/agent-manifest/validator.d.ts +0 -25
  300. package/dist/src/agent-manifest/validator.js +0 -74
  301. package/dist/src/agent-runtime/boundary-guard.d.ts +0 -12
  302. package/dist/src/agent-runtime/boundary-guard.js +0 -21
  303. package/dist/src/agent-runtime/index.d.ts +0 -7
  304. package/dist/src/agent-runtime/index.js +0 -8
  305. package/dist/src/agent-runtime/model-router.d.ts +0 -11
  306. package/dist/src/agent-runtime/model-router.js +0 -14
  307. package/dist/src/agent-runtime/run-trace.d.ts +0 -27
  308. package/dist/src/agent-runtime/run-trace.js +0 -51
  309. package/dist/src/agent-runtime/runtime-profile.d.ts +0 -26
  310. package/dist/src/agent-runtime/runtime-profile.js +0 -42
  311. package/dist/src/agent-runtime/tool-registry.d.ts +0 -28
  312. package/dist/src/agent-runtime/tool-registry.js +0 -50
  313. package/dist/src/agent-runtime/workflow-runtime.d.ts +0 -31
  314. package/dist/src/agent-runtime/workflow-runtime.js +0 -32
  315. package/dist/src/config/agent-typed-models.d.ts +0 -13
  316. package/dist/src/config/agent-typed-models.js +0 -48
  317. package/dist/src/gateway/capability-presets-admin.d.ts +0 -88
  318. package/dist/src/gateway/capability-presets-admin.js +0 -303
  319. package/dist/src/gateway/hono/lib/agent-model.d.ts +0 -35
  320. package/dist/src/gateway/hono/lib/agent-model.js +0 -97
  321. package/dist/src/gateway/hono/routes/capability-presets.d.ts +0 -3
  322. package/dist/src/gateway/hono/routes/capability-presets.js +0 -187
  323. package/dist/src/gateway/hono/routes/mobile.d.ts +0 -3
  324. package/dist/src/gateway/security/operator-scopes.d.ts +0 -37
  325. package/dist/src/gateway/security/operator-scopes.js +0 -147
  326. package/dist/src/migrations/config/default-capability-preset.d.ts +0 -10
  327. package/dist/src/migrations/config/default-capability-preset.js +0 -133
  328. package/dist/src/tunnel/enable-lan-pairing.d.ts +0 -8
  329. package/dist/src/tunnel/enable-lan-pairing.js +0 -41
  330. package/dist/src/tunnel/pair-context.d.ts +0 -36
  331. package/dist/src/tunnel/pair-context.js +0 -77
  332. package/dist/src/tunnel/pair-url.d.ts +0 -43
  333. package/dist/src/tunnel/pair-url.js +0 -117
  334. package/dist/src/tunnel/pairing-rate-limit.d.ts +0 -12
  335. package/dist/src/tunnel/pairing-rate-limit.js +0 -28
  336. package/dist/src/tunnel/pairing.d.ts +0 -21
  337. package/dist/src/tunnel/pairing.js +0 -103
  338. package/dist/src/tunnel/tunnel-qr.d.ts +0 -9
  339. package/dist/src/tunnel/tunnel-qr.js +0 -29
@@ -41,11 +41,11 @@ function deviceFromRow(row) {
41
41
  }
42
42
  function registerNotificationDevice(input) {
43
43
  const now = Date.now();
44
- const preferences = input.preferences ? normalizePreferences(input.preferences) : getNotificationDevice(input.id)?.preferences ?? normalizePreferences();
44
+ const preferences = input.preferences ? normalizePreferences(input.preferences) : getNotificationDevice(input.deviceId)?.preferences ?? normalizePreferences();
45
45
  runSqliteWriteTransaction((db) => {
46
- db.prepare(`DELETE FROM notification_devices
47
- WHERE push_provider = 'expo' AND push_token = ? AND device_id <> ?`).run(input.pushToken, input.id);
48
- db.prepare(`INSERT INTO notification_devices (
46
+ db.prepare(`DELETE FROM device_push_endpoints
47
+ WHERE push_token = ? AND device_id <> ?`).run(input.pushToken, input.deviceId);
48
+ db.prepare(`INSERT INTO device_push_endpoints (
49
49
  device_id, platform, push_token, enabled, permissions, preferences_json,
50
50
  locale, app_version, lease_expires_at, last_seen_at, created_at, updated_at
51
51
  ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
@@ -59,16 +59,16 @@ function registerNotificationDevice(input) {
59
59
  app_version = excluded.app_version,
60
60
  lease_expires_at = excluded.lease_expires_at,
61
61
  last_seen_at = excluded.last_seen_at,
62
- updated_at = excluded.updated_at`).run(input.id, input.platform, input.pushToken, input.permissions === "granted" ? 1 : 0, input.permissions, JSON.stringify(preferences), input.locale, input.appVersion?.trim() || null, now + DEVICE_LEASE_MS, now, now, now);
62
+ updated_at = excluded.updated_at`).run(input.deviceId, input.platform, input.pushToken, input.permissions === "granted" ? 1 : 0, input.permissions, JSON.stringify(preferences), input.locale, input.appVersion?.trim() || null, now + DEVICE_LEASE_MS, now, now, now);
63
63
  });
64
- return getNotificationDevice(input.id);
64
+ return getNotificationDevice(input.deviceId);
65
65
  }
66
66
  function getNotificationDevice(deviceId) {
67
- const row = getSqliteDatabase().prepare("SELECT * FROM notification_devices WHERE device_id = ?").get(deviceId);
67
+ const row = getSqliteDatabase().prepare("SELECT * FROM device_push_endpoints WHERE device_id = ?").get(deviceId);
68
68
  return row ? deviceFromRow(row) : null;
69
69
  }
70
70
  function listDeliverableNotificationDevices(now = Date.now()) {
71
- return getSqliteDatabase().prepare(`SELECT * FROM notification_devices
71
+ return getSqliteDatabase().prepare(`SELECT * FROM device_push_endpoints
72
72
  WHERE enabled = 1 AND permissions = 'granted' AND lease_expires_at > ?
73
73
  ORDER BY updated_at DESC`).all(now).map(deviceFromRow);
74
74
  }
@@ -80,16 +80,16 @@ function updateNotificationDevicePreferences(deviceId, patch) {
80
80
  ...patch
81
81
  });
82
82
  runSqliteWriteTransaction((db) => {
83
- db.prepare("UPDATE notification_devices SET preferences_json = ?, updated_at = ? WHERE device_id = ?").run(JSON.stringify(preferences), Date.now(), deviceId);
83
+ db.prepare("UPDATE device_push_endpoints SET preferences_json = ?, updated_at = ? WHERE device_id = ?").run(JSON.stringify(preferences), Date.now(), deviceId);
84
84
  });
85
85
  return getNotificationDevice(deviceId);
86
86
  }
87
87
  function removeNotificationDevice(deviceId) {
88
- return runSqliteWriteTransaction((db) => db.prepare("DELETE FROM notification_devices WHERE device_id = ?").run(deviceId).changes > 0);
88
+ return runSqliteWriteTransaction((db) => db.prepare("DELETE FROM device_push_endpoints WHERE device_id = ?").run(deviceId).changes > 0);
89
89
  }
90
90
  function disableNotificationDeviceForPushToken(pushToken) {
91
91
  runSqliteWriteTransaction((db) => {
92
- db.prepare("UPDATE notification_devices SET enabled = 0, updated_at = ? WHERE push_token = ?").run(Date.now(), pushToken);
92
+ db.prepare("UPDATE device_push_endpoints SET enabled = 0, updated_at = ? WHERE push_token = ?").run(Date.now(), pushToken);
93
93
  });
94
94
  }
95
95
  //#endregion
@@ -88,7 +88,7 @@ const DELIVERY_JOIN = `
88
88
  n.push_token, n.locale
89
89
  FROM notification_deliveries d
90
90
  JOIN notification_events e ON e.event_id = d.event_id
91
- JOIN notification_devices n ON n.device_id = d.device_id
91
+ JOIN device_push_endpoints n ON n.device_id = d.device_id
92
92
  `;
93
93
  function listDueNotificationDeliveries(status, now = Date.now(), limit = 100) {
94
94
  return getSqliteDatabase().prepare(`${DELIVERY_JOIN}
@@ -135,7 +135,7 @@ function notificationDeliveryMetrics() {
135
135
  SUM(CASE WHEN enabled = 1 AND permissions = 'granted' AND lease_expires_at > ? THEN 1 ELSE 0 END) AS deliverable,
136
136
  SUM(CASE WHEN enabled = 1 AND lease_expires_at <= ? THEN 1 ELSE 0 END) AS expired,
137
137
  SUM(CASE WHEN enabled = 0 OR permissions <> 'granted' THEN 1 ELSE 0 END) AS disabled
138
- FROM notification_devices`).get(now, now);
138
+ FROM device_push_endpoints`).get(now, now);
139
139
  const oldest = getSqliteDatabase().prepare(`SELECT MIN(next_attempt_at) AS value FROM notification_deliveries
140
140
  WHERE status IN ('pending', 'accepted')`).get();
141
141
  const latestError = getSqliteDatabase().prepare(`SELECT event_id, device_id, last_error, updated_at
@@ -166,7 +166,7 @@ function expireUndeliverableNotificationDeliveries(now = Date.now()) {
166
166
  SET status = 'dead', last_error = 'Device registration is disabled or expired', updated_at = ?
167
167
  WHERE status IN ('pending', 'accepted')
168
168
  AND EXISTS (
169
- SELECT 1 FROM notification_devices n
169
+ SELECT 1 FROM device_push_endpoints n
170
170
  WHERE n.device_id = notification_deliveries.device_id
171
171
  AND (n.enabled = 0 OR n.permissions <> 'granted' OR n.lease_expires_at <= ?)
172
172
  )`).run(now, now).changes));
@@ -90,7 +90,7 @@ function getApiKeySync(provider) {
90
90
  */
91
91
  function resolveModel(ref) {
92
92
  const trimmedRef = typeof ref === "string" ? ref.trim() : "";
93
- if (!trimmedRef) throw new Error("No default model configured. Choose a model in onboarding or set agents.capabilityPresets.default.models.roles.<role>.model.");
93
+ if (!trimmedRef) throw new Error("No default model configured. Choose a model in onboarding or set agents.defaults.models.chat.primary.");
94
94
  const customModel = getModelRegistry().resolve(trimmedRef);
95
95
  if (customModel) return normalizeProviderModel(customModel);
96
96
  const qualifiedRef = splitProviderModelRef(trimmedRef);
@@ -24,19 +24,24 @@ function collectToolModel(out, value, location, kind = "text") {
24
24
  function collectModelPolicy(out, value, location) {
25
25
  const policy = asRecord(value);
26
26
  if (!policy) return;
27
- const roles = asRecord(policy.roles);
28
- for (const [roleId, roleValue] of Object.entries(roles ?? {})) {
29
- const role = asRecord(roleValue);
30
- if (!role) continue;
31
- addRef(out, role.model, `${location}.roles.${roleId}.model`);
32
- if (Array.isArray(role.fallbacks)) role.fallbacks.forEach((ref, index) => addRef(out, ref, `${location}.roles.${roleId}.fallbacks[${index}]`));
27
+ const chat = asRecord(policy.chat);
28
+ if (chat) {
29
+ addRef(out, chat.primary, `${location}.chat.primary`);
30
+ if (Array.isArray(chat.fallbacks)) chat.fallbacks.forEach((ref, index) => addRef(out, ref, `${location}.chat.fallbacks[${index}]`));
33
31
  }
34
- collectToolModel(out, policy.imageModel, `${location}.imageModel`);
35
- collectToolModel(out, policy.imageGenerationModel, `${location}.imageGenerationModel`, "image-generation");
32
+ const intents = asRecord(policy.intents);
33
+ for (const [intent, routeValue] of Object.entries(intents ?? {})) {
34
+ const route = asRecord(routeValue);
35
+ if (!route) continue;
36
+ addRef(out, route.primary, `${location}.intents.${intent}.primary`);
37
+ if (Array.isArray(route.fallbacks)) route.fallbacks.forEach((ref, index) => addRef(out, ref, `${location}.intents.${intent}.fallbacks[${index}]`));
38
+ }
39
+ collectToolModel(out, policy.imageUnderstanding, `${location}.imageUnderstanding`);
40
+ collectToolModel(out, policy.imageGeneration, `${location}.imageGeneration`, "image-generation");
36
41
  }
37
42
  function collectReferences(config, sessionConfigs) {
38
43
  const out = [];
39
- for (const [presetId, preset] of Object.entries(config.agents.capabilityPresets)) collectModelPolicy(out, preset.models, `agents.capabilityPresets.${presetId}.models`);
44
+ collectModelPolicy(out, config.agents.defaults.models, "agents.defaults.models");
40
45
  for (const agent of config.agents.list) collectModelPolicy(out, agent.models, `agents.list.${agent.id}.models`);
41
46
  for (const [sessionKey, sessionConfig] of sessionConfigs) addRef(out, sessionConfig.modelOverride, `sessions.${sessionKey}.modelOverride`);
42
47
  return out;
@@ -10,10 +10,12 @@ export declare class RealtimeRuntime {
10
10
  private readonly preauthBudget;
11
11
  private readonly wss;
12
12
  private readonly topicCleanupTimers;
13
+ private readonly socketsByPrincipal;
13
14
  private closed;
14
15
  constructor(endpointTools?: EndpointToolRuntime);
15
16
  handleUpgrade(req: IncomingMessage, socket: Socket, head: Buffer): boolean;
16
17
  close(): void;
18
+ disconnectPrincipal(principalId: string): void;
17
19
  completeTopic(topic: string, ttlMs?: number): void;
18
20
  private handleConnection;
19
21
  private rawDataToText;
@@ -1,5 +1,6 @@
1
1
  import { createLogger } from "../utils/logger/index.js";
2
2
  import { init_logger } from "../utils/logger.js";
3
+ import { hasGatewayScope } from "../gateway/security/gateway-scopes.js";
3
4
  import { REALTIME_HEARTBEAT_INTERVAL_MS, REALTIME_HEARTBEAT_TIMEOUT_MS, REALTIME_HELLO_TIMEOUT_MS, REALTIME_MAX_CLIENT_FRAME_BYTES, parseClientRealtimeJsonFrame, parseClientRealtimeMessage } from "../../packages/realtime-protocol/src/index.js";
4
5
  import { createPreauthConnectionBudget } from "../gateway/security/preauth-connection-budget.js";
5
6
  import { RealtimeBroker } from "./broker.js";
@@ -24,8 +25,13 @@ function serverMessage(kind, payload) {
24
25
  payload
25
26
  };
26
27
  }
27
- function isAuthorizedTopic(topic) {
28
- return topic === "gateway" || topic === "sessions" || topic === "logs" || topic.startsWith("session:") && topic.length > 8 || topic.startsWith("run:") && topic.length > 4 || topic.startsWith("workflow:") && topic.length > 9;
28
+ function isAuthorizedTopic(topic, scopes) {
29
+ if (topic === "gateway") return hasGatewayScope(scopes, "gateway.status");
30
+ if (topic === "logs") return hasGatewayScope(scopes, "gateway.admin");
31
+ if (topic === "sessions" || topic.startsWith("session:")) return hasGatewayScope(scopes, "sessions.read");
32
+ if (topic.startsWith("run:")) return hasGatewayScope(scopes, "agents.run");
33
+ if (topic.startsWith("workflow:")) return hasGatewayScope(scopes, "automations.read");
34
+ return false;
29
35
  }
30
36
  var RealtimeRuntime = class {
31
37
  broker = new RealtimeBroker(void 0, (error, topic) => {
@@ -42,6 +48,7 @@ var RealtimeRuntime = class {
42
48
  perMessageDeflate: false
43
49
  });
44
50
  topicCleanupTimers = /* @__PURE__ */ new Map();
51
+ socketsByPrincipal = /* @__PURE__ */ new Map();
45
52
  closed = false;
46
53
  constructor(endpointTools) {
47
54
  this.endpointTools = endpointTools;
@@ -80,6 +87,10 @@ var RealtimeRuntime = class {
80
87
  for (const timer of this.topicCleanupTimers.values()) clearTimeout(timer);
81
88
  this.topicCleanupTimers.clear();
82
89
  this.wss.close();
90
+ this.socketsByPrincipal.clear();
91
+ }
92
+ disconnectPrincipal(principalId) {
93
+ for (const socket of this.socketsByPrincipal.get(principalId) ?? []) socket.close(4403, "Device access revoked");
83
94
  }
84
95
  completeTopic(topic, ttlMs = 5 * 6e4) {
85
96
  const existing = this.topicCleanupTimers.get(topic);
@@ -97,6 +108,8 @@ var RealtimeRuntime = class {
97
108
  let lastSeenAt = Date.now();
98
109
  let connectionId;
99
110
  let endpointId;
111
+ let principalId;
112
+ let grantedScopes = [];
100
113
  let budgetHeld = true;
101
114
  const releaseBudget = () => {
102
115
  if (!budgetHeld) return;
@@ -115,7 +128,7 @@ var RealtimeRuntime = class {
115
128
  };
116
129
  const subscribe = (requested) => {
117
130
  const authorized = requested.filter((item) => {
118
- if (isAuthorizedTopic(item.topic)) return true;
131
+ if (isAuthorizedTopic(item.topic, grantedScopes)) return true;
119
132
  sendError("FORBIDDEN_TOPIC", `Topic is not available: ${item.topic}`);
120
133
  return false;
121
134
  });
@@ -172,6 +185,11 @@ var RealtimeRuntime = class {
172
185
  return;
173
186
  }
174
187
  authenticated = true;
188
+ principalId = claim.principalId;
189
+ grantedScopes = claim.scopes;
190
+ const principalSockets = this.socketsByPrincipal.get(principalId) ?? /* @__PURE__ */ new Set();
191
+ principalSockets.add(socket);
192
+ this.socketsByPrincipal.set(principalId, principalSockets);
175
193
  connectionId = crypto.randomUUID();
176
194
  let endpointReady;
177
195
  if (message.payload.endpoint) {
@@ -215,6 +233,7 @@ var RealtimeRuntime = class {
215
233
  subscribe(message.payload.subscriptions);
216
234
  log.info({
217
235
  connectionId,
236
+ principalId,
218
237
  clientId: claim.clientId,
219
238
  clientKind: claim.clientKind
220
239
  }, "Realtime client connected");
@@ -249,6 +268,11 @@ var RealtimeRuntime = class {
249
268
  for (const handle of subscriptions.values()) handle.unsubscribe();
250
269
  subscriptions.clear();
251
270
  if (endpointId && connectionId) this.endpointTools?.remove(endpointId, connectionId);
271
+ if (principalId) {
272
+ const principalSockets = this.socketsByPrincipal.get(principalId);
273
+ principalSockets?.delete(socket);
274
+ if (principalSockets?.size === 0) this.socketsByPrincipal.delete(principalId);
275
+ }
252
276
  if (connectionId) {
253
277
  const reason = reasonBuffer.toString();
254
278
  log.info({
@@ -1,7 +1,12 @@
1
1
  import type { RealtimeClientKind } from '@xopcai/realtime-protocol';
2
+ import type { GatewayScope } from '../gateway/security/gateway-scopes.js';
2
3
  export interface RealtimeTicketClaim {
3
4
  clientId: string;
4
5
  clientKind: RealtimeClientKind;
6
+ principalId: string;
7
+ deviceId?: string;
8
+ accessSessionId?: string;
9
+ scopes: readonly GatewayScope[];
5
10
  expiresAt: number;
6
11
  }
7
12
  export interface IssuedRealtimeTicket extends RealtimeTicketClaim {
@@ -9,7 +14,7 @@ export interface IssuedRealtimeTicket extends RealtimeTicketClaim {
9
14
  }
10
15
  export declare class RealtimeTicketStore {
11
16
  private readonly claims;
12
- issue(clientId: string, clientKind: RealtimeClientKind, now?: number): IssuedRealtimeTicket;
17
+ issue(clientId: string, clientKind: RealtimeClientKind, principal: Omit<RealtimeTicketClaim, 'clientId' | 'clientKind' | 'expiresAt'>, now?: number): IssuedRealtimeTicket;
13
18
  consume(ticket: string, clientId: string, clientKind: RealtimeClientKind, now?: number): RealtimeTicketClaim | undefined;
14
19
  private prune;
15
20
  }
@@ -7,13 +7,14 @@ function ticketKey(ticket) {
7
7
  }
8
8
  var RealtimeTicketStore = class {
9
9
  claims = /* @__PURE__ */ new Map();
10
- issue(clientId, clientKind, now = Date.now()) {
10
+ issue(clientId, clientKind, principal, now = Date.now()) {
11
11
  this.prune(now);
12
12
  if (this.claims.size >= MAX_OUTSTANDING_TICKETS) throw new Error("Too many outstanding realtime tickets");
13
13
  const ticket = crypto.randomBytes(32).toString("base64url");
14
14
  const claim = {
15
15
  clientId,
16
16
  clientKind,
17
+ ...principal,
17
18
  expiresAt: now + TICKET_TTL_MS
18
19
  };
19
20
  this.claims.set(ticketKey(ticket), claim);
@@ -2,11 +2,11 @@ import { createLogger } from "../utils/logger/index.js";
2
2
  import { init_logger } from "../utils/logger.js";
3
3
  import { isTailscaleInstalled } from "../infra/tailscale.js";
4
4
  import { resolveGatewayBindMode } from "../config/gateway-bind.js";
5
- import { getTunnelConsentState } from "../tunnel/consent.js";
6
- import { getTunnelService } from "../tunnel/tunnel-service.js";
7
- import { maybeAutoStartTunnelFromConfig } from "../tunnel/gateway-lifecycle.js";
8
5
  import { collectExposureConflicts } from "./exposure-guards.js";
9
6
  import { getTailscaleExposureState, maybeStartTailscaleFromConfig, stopTailscaleExposure } from "../gateway/tailscale-lifecycle.js";
7
+ import { getTunnelService } from "../tunnel/tunnel-service.js";
8
+ import { getTunnelConsentState } from "../tunnel/consent.js";
9
+ import { maybeAutoStartTunnelFromConfig } from "../tunnel/gateway-lifecycle.js";
10
10
  //#region src/remote-access/exposure-manager.ts
11
11
  init_logger();
12
12
  const log = createLogger("ExposureManager");
@@ -1,5 +1,5 @@
1
+ import { enumerateLanGatewayCandidates } from "../gateway/host.js";
1
2
  import { loadTunnelState } from "../tunnel/tunnel-state.js";
2
- import { resolveLanGatewayUrl } from "../tunnel/tunnel-qr.js";
3
3
  //#region src/share/share-url.ts
4
4
  function resolveShareUrl(token, ctx) {
5
5
  const tunnelState = loadTunnelState();
@@ -32,7 +32,7 @@ function resolveShareUrl(token, ctx) {
32
32
  };
33
33
  }
34
34
  function buildLanUrl(host, port, path) {
35
- const lanBase = resolveLanGatewayUrl(host, port);
35
+ const lanBase = isLoopbackHost(host) ? null : enumerateLanGatewayCandidates(port).find((candidate) => candidate.address === host)?.url ?? `http://${host.includes(":") ? `[${host}]` : host}:${port}`;
36
36
  if (!lanBase) return null;
37
37
  return `${lanBase.replace(/\/+$/, "")}${path}`;
38
38
  }
@@ -0,0 +1,54 @@
1
+ import crypto from 'node:crypto';
2
+ import type { GatewayScope } from '../../gateway/security/gateway-scopes.js';
3
+ export type DevicePlatform = 'ios' | 'android';
4
+ export type DevicePublicKeyJwk = crypto.webcrypto.JsonWebKey;
5
+ export type DeviceRecord = {
6
+ id: string;
7
+ displayName: string;
8
+ platform: DevicePlatform;
9
+ publicKeyJwk: DevicePublicKeyJwk;
10
+ scopes: GatewayScope[];
11
+ createdAt: number;
12
+ lastSeenAt?: number;
13
+ revokedAt?: number;
14
+ };
15
+ export type DeviceAccessIdentity = {
16
+ deviceId: string;
17
+ accessSessionId: string;
18
+ scopes: GatewayScope[];
19
+ };
20
+ export type DeviceTokenPair = {
21
+ accessToken: string;
22
+ accessTokenExpiresAt: number;
23
+ refreshToken: string;
24
+ refreshTokenExpiresAt: number;
25
+ };
26
+ export declare function createDevice(input: {
27
+ id?: string;
28
+ displayName: string;
29
+ platform: DevicePlatform;
30
+ publicKeyJwk: DevicePublicKeyJwk;
31
+ scopes: readonly GatewayScope[];
32
+ now?: number;
33
+ }): DeviceRecord;
34
+ export declare function getDevice(deviceId: string): DeviceRecord | undefined;
35
+ export declare function listDevices(): DeviceRecord[];
36
+ export declare function issueDeviceTokenPair(deviceId: string, now?: number): DeviceTokenPair;
37
+ export declare function authenticateDeviceAccessToken(token: string, now?: number): DeviceAccessIdentity | undefined;
38
+ export declare function buildRefreshProofMessage(input: {
39
+ credentialId: string;
40
+ timestamp: number;
41
+ nonce: string;
42
+ requestId: string;
43
+ nextRefreshToken: string;
44
+ }): string;
45
+ export declare function rotateDeviceRefreshToken(input: {
46
+ refreshToken: string;
47
+ timestamp: number;
48
+ nonce: string;
49
+ requestId: string;
50
+ nextRefreshToken: string;
51
+ signature: string;
52
+ now?: number;
53
+ }): DeviceTokenPair;
54
+ export declare function revokeDevice(deviceId: string, now?: number): boolean;
@@ -0,0 +1,194 @@
1
+ import { getSqliteDatabase, runSqliteWriteTransaction } from "./transaction.js";
2
+ import { parseGatewayScopes } from "../../gateway/security/gateway-scopes.js";
3
+ import crypto from "node:crypto";
4
+ //#region src/storage/sqlite/device-access-repository.ts
5
+ const ACCESS_TOKEN_TTL_MS = 900 * 1e3;
6
+ const REFRESH_TOKEN_TTL_MS = 2160 * 60 * 60 * 1e3;
7
+ const REFRESH_PROOF_MAX_AGE_MS = 300 * 1e3;
8
+ const ACCESS_TOKEN_PREFIX = "xopc_at_";
9
+ const REFRESH_TOKEN_PREFIX = "xopc_rt_";
10
+ function tokenHash(token) {
11
+ return crypto.createHash("sha256").update(token).digest("hex");
12
+ }
13
+ function buildToken(prefix, id) {
14
+ return `${prefix}${id}_${crypto.randomBytes(32).toString("base64url")}`;
15
+ }
16
+ function parseToken(token, prefix) {
17
+ if (!token.startsWith(prefix)) return void 0;
18
+ const separator = token.indexOf("_", prefix.length);
19
+ if (separator <= prefix.length || separator === token.length - 1) return void 0;
20
+ return { id: token.slice(prefix.length, separator) };
21
+ }
22
+ function deviceFromRow(row) {
23
+ return {
24
+ id: row.device_id,
25
+ displayName: row.display_name,
26
+ platform: row.platform,
27
+ publicKeyJwk: JSON.parse(row.public_key_jwk),
28
+ scopes: parseGatewayScopes(row.scopes_json),
29
+ createdAt: row.created_at,
30
+ ...row.last_seen_at === null ? {} : { lastSeenAt: row.last_seen_at },
31
+ ...row.revoked_at === null ? {} : { revokedAt: row.revoked_at }
32
+ };
33
+ }
34
+ function createDevice(input) {
35
+ const id = input.id ?? crypto.randomUUID();
36
+ const createdAt = input.now ?? Date.now();
37
+ getSqliteDatabase().prepare(`
38
+ INSERT INTO devices (
39
+ device_id, display_name, platform, public_key_jwk, scopes_json, created_at
40
+ ) VALUES (?, ?, ?, ?, ?, ?)
41
+ `).run(id, input.displayName, input.platform, JSON.stringify(input.publicKeyJwk), JSON.stringify([...new Set(input.scopes)]), createdAt);
42
+ return getDevice(id);
43
+ }
44
+ function getDevice(deviceId) {
45
+ const row = getSqliteDatabase().prepare("SELECT * FROM devices WHERE device_id = ?").get(deviceId);
46
+ return row ? deviceFromRow(row) : void 0;
47
+ }
48
+ function listDevices() {
49
+ return getSqliteDatabase().prepare("SELECT * FROM devices ORDER BY created_at DESC").all().map(deviceFromRow);
50
+ }
51
+ function issueAccessToken(deviceId, now) {
52
+ const accessSessionId = crypto.randomUUID();
53
+ const accessToken = buildToken(ACCESS_TOKEN_PREFIX, accessSessionId);
54
+ const accessTokenExpiresAt = now + ACCESS_TOKEN_TTL_MS;
55
+ getSqliteDatabase().prepare(`
56
+ INSERT INTO device_access_sessions (
57
+ session_id, device_id, token_hash, expires_at, created_at
58
+ ) VALUES (?, ?, ?, ?, ?)
59
+ `).run(accessSessionId, deviceId, tokenHash(accessToken), accessTokenExpiresAt, now);
60
+ return {
61
+ accessToken,
62
+ accessTokenExpiresAt
63
+ };
64
+ }
65
+ function issueDeviceTokenPair(deviceId, now = Date.now()) {
66
+ const device = getDevice(deviceId);
67
+ if (!device || device.revokedAt !== void 0) throw new Error("Device is unavailable");
68
+ const refreshCredentialId = crypto.randomUUID();
69
+ const refreshToken = buildToken(REFRESH_TOKEN_PREFIX, refreshCredentialId);
70
+ const refreshTokenExpiresAt = now + REFRESH_TOKEN_TTL_MS;
71
+ return {
72
+ ...runSqliteWriteTransaction((db) => {
73
+ const issuedAccess = issueAccessToken(deviceId, now);
74
+ db.prepare(`
75
+ INSERT INTO device_refresh_credentials (
76
+ credential_id, device_id, token_hash, expires_at, created_at
77
+ ) VALUES (?, ?, ?, ?, ?)
78
+ `).run(refreshCredentialId, deviceId, tokenHash(refreshToken), refreshTokenExpiresAt, now);
79
+ return issuedAccess;
80
+ }),
81
+ refreshToken,
82
+ refreshTokenExpiresAt
83
+ };
84
+ }
85
+ function authenticateDeviceAccessToken(token, now = Date.now()) {
86
+ const parsed = parseToken(token, ACCESS_TOKEN_PREFIX);
87
+ if (!parsed) return void 0;
88
+ const row = getSqliteDatabase().prepare(`
89
+ SELECT a.session_id, a.device_id, a.token_hash, a.expires_at, a.revoked_at,
90
+ d.scopes_json, d.revoked_at AS device_revoked_at
91
+ FROM device_access_sessions a
92
+ JOIN devices d ON d.device_id = a.device_id
93
+ WHERE a.session_id = ?
94
+ `).get(parsed.id);
95
+ if (!row || row.expires_at <= now || row.revoked_at !== null || row.device_revoked_at !== null || !crypto.timingSafeEqual(Buffer.from(row.token_hash, "hex"), Buffer.from(tokenHash(token), "hex"))) return;
96
+ getSqliteDatabase().prepare("UPDATE devices SET last_seen_at = ? WHERE device_id = ?").run(now, row.device_id);
97
+ return {
98
+ deviceId: row.device_id,
99
+ accessSessionId: row.session_id,
100
+ scopes: parseGatewayScopes(row.scopes_json)
101
+ };
102
+ }
103
+ function buildRefreshProofMessage(input) {
104
+ return `xopc-device-refresh-v2\n${input.credentialId}\n${input.timestamp}\n${input.nonce}\n${input.requestId}\n${input.nextRefreshToken}`;
105
+ }
106
+ function verifyRefreshProof(publicKeyJwk, message, signature) {
107
+ try {
108
+ const publicKey = crypto.createPublicKey({
109
+ key: publicKeyJwk,
110
+ format: "jwk"
111
+ });
112
+ return crypto.verify("sha256", Buffer.from(message), {
113
+ key: publicKey,
114
+ dsaEncoding: "ieee-p1363"
115
+ }, Buffer.from(signature, "base64url"));
116
+ } catch {
117
+ return false;
118
+ }
119
+ }
120
+ function rotateDeviceRefreshToken(input) {
121
+ const now = input.now ?? Date.now();
122
+ const parsed = parseToken(input.refreshToken, REFRESH_TOKEN_PREFIX);
123
+ const nextParsed = parseToken(input.nextRefreshToken, REFRESH_TOKEN_PREFIX);
124
+ if (!parsed || !nextParsed || parsed.id === nextParsed.id) throw new Error("Invalid refresh credential");
125
+ if (Math.abs(now - input.timestamp) > REFRESH_PROOF_MAX_AGE_MS) throw new Error("Refresh proof expired");
126
+ const result = runSqliteWriteTransaction((db) => {
127
+ const credential = db.prepare(`
128
+ SELECT credential_id, device_id, token_hash, expires_at, replaced_by,
129
+ rotation_request_id, revoked_at
130
+ FROM device_refresh_credentials WHERE credential_id = ?
131
+ `).get(parsed.id);
132
+ const suppliedHash = tokenHash(input.refreshToken);
133
+ if (!credential || credential.token_hash !== suppliedHash) throw new Error("Invalid refresh credential");
134
+ const deviceRow = db.prepare("SELECT * FROM devices WHERE device_id = ?").get(credential.device_id);
135
+ if (!deviceRow || deviceRow.revoked_at !== null) throw new Error("Device is unavailable");
136
+ const message = buildRefreshProofMessage({
137
+ credentialId: credential.credential_id,
138
+ timestamp: input.timestamp,
139
+ nonce: input.nonce,
140
+ requestId: input.requestId,
141
+ nextRefreshToken: input.nextRefreshToken
142
+ });
143
+ if (!verifyRefreshProof(JSON.parse(deviceRow.public_key_jwk), message, input.signature)) throw new Error("Invalid refresh proof");
144
+ if (credential.replaced_by === nextParsed.id && credential.rotation_request_id === input.requestId) {
145
+ const nextCredential = db.prepare(`
146
+ SELECT credential_id, device_id, token_hash, expires_at, replaced_by,
147
+ rotation_request_id, revoked_at
148
+ FROM device_refresh_credentials WHERE credential_id = ?
149
+ `).get(nextParsed.id);
150
+ if (nextCredential?.token_hash === tokenHash(input.nextRefreshToken)) return {
151
+ ...issueAccessToken(credential.device_id, now),
152
+ refreshToken: input.nextRefreshToken,
153
+ refreshTokenExpiresAt: nextCredential.expires_at
154
+ };
155
+ }
156
+ if (credential.replaced_by !== null || credential.revoked_at !== null) {
157
+ db.prepare("UPDATE devices SET revoked_at = ? WHERE device_id = ? AND revoked_at IS NULL").run(now, credential.device_id);
158
+ db.prepare("UPDATE device_access_sessions SET revoked_at = ? WHERE device_id = ? AND revoked_at IS NULL").run(now, credential.device_id);
159
+ db.prepare("UPDATE device_refresh_credentials SET revoked_at = ? WHERE device_id = ? AND revoked_at IS NULL").run(now, credential.device_id);
160
+ return "reused";
161
+ }
162
+ if (credential.expires_at <= now) throw new Error("Refresh credential expired");
163
+ const access = issueAccessToken(credential.device_id, now);
164
+ const refreshTokenExpiresAt = now + REFRESH_TOKEN_TTL_MS;
165
+ db.prepare(`
166
+ INSERT INTO device_refresh_credentials (
167
+ credential_id, device_id, token_hash, expires_at, created_at
168
+ ) VALUES (?, ?, ?, ?, ?)
169
+ `).run(nextParsed.id, credential.device_id, tokenHash(input.nextRefreshToken), refreshTokenExpiresAt, now);
170
+ db.prepare(`
171
+ UPDATE device_refresh_credentials
172
+ SET last_used_at = ?, replaced_by = ?, rotation_request_id = ?, revoked_at = ?
173
+ WHERE credential_id = ?
174
+ `).run(now, nextParsed.id, input.requestId, now, credential.credential_id);
175
+ return {
176
+ ...access,
177
+ refreshToken: input.nextRefreshToken,
178
+ refreshTokenExpiresAt
179
+ };
180
+ });
181
+ if (result === "reused") throw new Error("Refresh credential reuse detected");
182
+ return result;
183
+ }
184
+ function revokeDevice(deviceId, now = Date.now()) {
185
+ return runSqliteWriteTransaction((db) => {
186
+ const changed = db.prepare("UPDATE devices SET revoked_at = ? WHERE device_id = ? AND revoked_at IS NULL").run(now, deviceId).changes > 0;
187
+ db.prepare("UPDATE device_access_sessions SET revoked_at = ? WHERE device_id = ? AND revoked_at IS NULL").run(now, deviceId);
188
+ db.prepare("UPDATE device_refresh_credentials SET revoked_at = ? WHERE device_id = ? AND revoked_at IS NULL").run(now, deviceId);
189
+ db.prepare("DELETE FROM device_push_endpoints WHERE device_id = ?").run(deviceId);
190
+ return changed;
191
+ });
192
+ }
193
+ //#endregion
194
+ export { authenticateDeviceAccessToken, buildRefreshProofMessage, createDevice, getDevice, issueDeviceTokenPair, listDevices, revokeDevice, rotateDeviceRefreshToken };
@@ -0,0 +1,22 @@
1
+ export type DeviceRoute = {
2
+ id: string;
3
+ kind: 'xopc-secure-link' | 'tailscale' | 'custom-https';
4
+ url: string;
5
+ };
6
+ export type DevicePairingSetup = {
7
+ id: string;
8
+ token: string;
9
+ routes: DeviceRoute[];
10
+ expiresAt: number;
11
+ };
12
+ export type PairingConsumeResult = {
13
+ ok: true;
14
+ pairingId: string;
15
+ routes: DeviceRoute[];
16
+ } | {
17
+ ok: false;
18
+ reason: 'invalid' | 'expired' | 'consumed';
19
+ };
20
+ export declare function createDevicePairingSetup(routes: readonly DeviceRoute[], now?: number): DevicePairingSetup;
21
+ export declare function consumeDevicePairingToken(token: string, now?: number): PairingConsumeResult;
22
+ export declare function isDevicePairingSetupActive(pairingId: string, now?: number): boolean;
@@ -0,0 +1,86 @@
1
+ import { getSqliteDatabase, runSqliteWriteTransaction } from "./transaction.js";
2
+ import crypto from "node:crypto";
3
+ //#region src/storage/sqlite/device-pairing-repository.ts
4
+ const PAIRING_TOKEN_PREFIX = "xopc_pair_";
5
+ const PAIRING_TTL_MS = 600 * 1e3;
6
+ const PAIRING_ATTEMPTS = 5;
7
+ function hashToken(token) {
8
+ return crypto.createHash("sha256").update(token).digest("hex");
9
+ }
10
+ function hashesMatch(left, right) {
11
+ return crypto.timingSafeEqual(Buffer.from(left, "hex"), Buffer.from(right, "hex"));
12
+ }
13
+ function tokenId(token) {
14
+ if (!token.startsWith(PAIRING_TOKEN_PREFIX)) return void 0;
15
+ const separator = token.indexOf("_", 10);
16
+ if (separator <= 10 || separator === token.length - 1) return void 0;
17
+ return token.slice(10, separator);
18
+ }
19
+ function createDevicePairingSetup(routes, now = Date.now()) {
20
+ if (routes.length === 0) throw new Error("No secure mobile route is available");
21
+ const id = crypto.randomUUID();
22
+ const token = `${PAIRING_TOKEN_PREFIX}${id}_${crypto.randomBytes(32).toString("base64url")}`;
23
+ const expiresAt = now + PAIRING_TTL_MS;
24
+ runSqliteWriteTransaction((db) => {
25
+ db.prepare("DELETE FROM device_pairing_sessions WHERE expires_at <= ? OR consumed_at IS NOT NULL").run(now);
26
+ db.prepare(`
27
+ INSERT INTO device_pairing_sessions (
28
+ pairing_id, secret_hash, routes_json, expires_at, attempts_remaining, created_at
29
+ ) VALUES (?, ?, ?, ?, ?, ?)
30
+ `).run(id, hashToken(token), JSON.stringify(routes), expiresAt, PAIRING_ATTEMPTS, now);
31
+ });
32
+ return {
33
+ id,
34
+ token,
35
+ routes: [...routes],
36
+ expiresAt
37
+ };
38
+ }
39
+ function consumeDevicePairingToken(token, now = Date.now()) {
40
+ const id = tokenId(token);
41
+ if (!id) return {
42
+ ok: false,
43
+ reason: "invalid"
44
+ };
45
+ return runSqliteWriteTransaction((db) => {
46
+ const row = db.prepare("SELECT * FROM device_pairing_sessions WHERE pairing_id = ?").get(id);
47
+ if (!row || row.attempts_remaining <= 0) return {
48
+ ok: false,
49
+ reason: "invalid"
50
+ };
51
+ if (row.consumed_at !== null) return {
52
+ ok: false,
53
+ reason: "consumed"
54
+ };
55
+ if (row.expires_at <= now) return {
56
+ ok: false,
57
+ reason: "expired"
58
+ };
59
+ if (!hashesMatch(row.secret_hash, hashToken(token))) {
60
+ db.prepare(`
61
+ UPDATE device_pairing_sessions
62
+ SET attempts_remaining = attempts_remaining - 1
63
+ WHERE pairing_id = ? AND attempts_remaining > 0
64
+ `).run(id);
65
+ return {
66
+ ok: false,
67
+ reason: "invalid"
68
+ };
69
+ }
70
+ db.prepare("UPDATE device_pairing_sessions SET consumed_at = ? WHERE pairing_id = ?").run(now, id);
71
+ return {
72
+ ok: true,
73
+ pairingId: id,
74
+ routes: JSON.parse(row.routes_json)
75
+ };
76
+ });
77
+ }
78
+ function isDevicePairingSetupActive(pairingId, now = Date.now()) {
79
+ const row = getSqliteDatabase().prepare(`
80
+ SELECT 1 FROM device_pairing_sessions
81
+ WHERE pairing_id = ? AND expires_at > ? AND consumed_at IS NULL AND attempts_remaining > 0
82
+ `).get(pairingId, now);
83
+ return Boolean(row);
84
+ }
85
+ //#endregion
86
+ export { consumeDevicePairingToken, createDevicePairingSetup, isDevicePairingSetupActive };