@xopcai/xopc 0.0.85 → 0.0.87

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 (407) hide show
  1. package/dist/browser-ext/manifest.json +1 -1
  2. package/dist/extensions/feishu/src/adapters/cli-login.js +3 -3
  3. package/dist/extensions/feishu/src/adapters/cli-login.js.map +1 -1
  4. package/dist/extensions/telegram/src/delivery-chat-id.d.ts +1 -1
  5. package/dist/extensions/telegram/src/delivery-chat-id.js +1 -1
  6. package/dist/extensions/telegram/src/delivery-chat-id.js.map +1 -1
  7. package/dist/extensions/telegram/src/routing-integration.js +1 -0
  8. package/dist/extensions/telegram/src/routing-integration.js.map +1 -1
  9. package/dist/extensions/telegram/xopc.extension.json +1 -1
  10. package/dist/extensions/weixin/src/__tests__/workflow-progress.test.js +2 -2
  11. package/dist/extensions/weixin/src/__tests__/workflow-progress.test.js.map +1 -1
  12. package/dist/extensions/weixin/src/api/api.js +2 -2
  13. package/dist/extensions/weixin/src/api/api.js.map +1 -1
  14. package/dist/extensions/weixin/src/auth/accounts.js +12 -12
  15. package/dist/extensions/weixin/src/auth/accounts.js.map +1 -1
  16. package/dist/extensions/weixin/src/delivery-to.js +2 -2
  17. package/dist/extensions/weixin/src/delivery-to.js.map +1 -1
  18. package/dist/extensions/weixin/src/messaging/debug-mode.js +5 -5
  19. package/dist/extensions/weixin/src/messaging/debug-mode.js.map +1 -1
  20. package/dist/extensions/weixin/src/messaging/inbound.js +11 -11
  21. package/dist/extensions/weixin/src/messaging/inbound.js.map +1 -1
  22. package/dist/extensions/weixin/src/storage/sync-buf.js +4 -4
  23. package/dist/extensions/weixin/src/storage/sync-buf.js.map +1 -1
  24. package/dist/extensions/weixin/src/workflow-progress.d.ts +1 -1
  25. package/dist/extensions/weixin/src/workflow-progress.js.map +1 -1
  26. package/dist/gateway/static/root/assets/agents-BEAbXpuP.js +222 -0
  27. package/dist/gateway/static/root/assets/{apps-page-D7v7649T.js → apps-page-Dg8R-Szf.js} +1 -1
  28. package/dist/gateway/static/root/assets/{channels-settings-nCaMb0a7.js → channels-settings-yohw9YSu.js} +1 -1
  29. package/dist/gateway/static/root/assets/{channels-status-swr-C1gZBcJV.js → channels-status-swr-BSHqqCF1.js} +1 -1
  30. package/dist/gateway/static/root/assets/{cron-api-CoYK0hlm.js → cron-api-0h_QT8U3.js} +1 -1
  31. package/dist/gateway/static/root/assets/{cron-page-DeGo-Vjc.js → cron-page-BkfKFfFk.js} +1 -1
  32. package/dist/gateway/static/root/assets/{dist-DaK4dsss.js → dist-Cmjp2APP.js} +1 -1
  33. package/dist/gateway/static/root/assets/{extension-debug-page-BZngZWbO.js → extension-debug-page-CFa9z_1N.js} +1 -1
  34. package/dist/gateway/static/root/assets/{extension-page-D6JSyV27.js → extension-page-BI8eaTPq.js} +1 -1
  35. package/dist/gateway/static/root/assets/extension-settings-page-x4BB7q1X.js +1 -0
  36. package/dist/gateway/static/root/assets/{fetch-B2MYHbWg.js → fetch-DRqwef_Q.js} +1 -1
  37. package/dist/gateway/static/root/assets/{field-primitives-Zzl22MvN.js → field-primitives-BiNHBo2Y.js} +1 -1
  38. package/dist/gateway/static/root/assets/{heartbeat-config-api-BtIcpG0O.js → heartbeat-config-api-ZRb8qhuz.js} +1 -1
  39. package/dist/gateway/static/root/assets/{index-D4vM3-P7.js → index-Cu7bKuUi.js} +96 -94
  40. package/dist/gateway/static/root/assets/index-a5gWIdZQ.css +1 -0
  41. package/dist/gateway/static/root/assets/{logs-page-_d4UJ-qQ.js → logs-page-BFZ8GgCv.js} +1 -1
  42. package/dist/gateway/static/root/assets/{sessions-page-5N4aF2Wk.js → sessions-page-CD7AfB-2.js} +1 -1
  43. package/dist/gateway/static/root/assets/settings-form-section-DiqqVs6m.js +1 -0
  44. package/dist/gateway/static/root/assets/settings-page-BBOjEQW3.js +3 -0
  45. package/dist/gateway/static/root/assets/{share-preview-page-D4EG_vM1.js → share-preview-page-n1Gprylk.js} +1 -1
  46. package/dist/gateway/static/root/assets/{skills-page-sPAXhh8w.js → skills-page-CcN_gj--.js} +1 -1
  47. package/dist/gateway/static/root/assets/{theme-store-DryYl3qD.js → theme-store-CZOh1nT3.js} +1 -1
  48. package/dist/gateway/static/root/assets/url-Dd8Q7kZZ.js +3 -0
  49. package/dist/gateway/static/root/assets/{utils-CYO9eTCM.js → utils-CkWBfxs4.js} +1 -1
  50. package/dist/gateway/static/root/assets/{voice-api-key-field-Ds51havm.js → voice-api-key-field-O6awz9hi.js} +1 -1
  51. package/dist/gateway/static/root/index.html +5 -5
  52. package/dist/package.js +1 -1
  53. package/dist/src/agent/agent-scope.d.ts +4 -0
  54. package/dist/src/agent/agent-scope.js +53 -10
  55. package/dist/src/agent/agent-scope.js.map +1 -1
  56. package/dist/src/agent/bootstrap/filter-bootstrap-files.js +2 -1
  57. package/dist/src/agent/bootstrap/filter-bootstrap-files.js.map +1 -1
  58. package/dist/src/agent/embedded/session-tool-result-guard.js +2 -1
  59. package/dist/src/agent/embedded/session-tool-result-guard.js.map +1 -1
  60. package/dist/src/agent/embedded/tool-result-truncation.js +2 -1
  61. package/dist/src/agent/embedded/tool-result-truncation.js.map +1 -1
  62. package/dist/src/agent/fallback/candidates.js +2 -2
  63. package/dist/src/agent/fallback/candidates.js.map +1 -1
  64. package/dist/src/agent/goals/persistent-goal-apis.d.ts +0 -2
  65. package/dist/src/agent/goals/persistent-goal-service.js +0 -1
  66. package/dist/src/agent/goals/persistent-goal-service.js.map +1 -1
  67. package/dist/src/agent/image/generation/normalization.js +2 -12
  68. package/dist/src/agent/image/generation/normalization.js.map +1 -1
  69. package/dist/src/agent/image/generation/provider-registry.d.ts +4 -8
  70. package/dist/src/agent/image/generation/provider-registry.js.map +1 -1
  71. package/dist/src/agent/image/generation/runtime.d.ts +2 -2
  72. package/dist/src/agent/image/generation/runtime.js.map +1 -1
  73. package/dist/src/agent/image/generation/types.d.ts +0 -18
  74. package/dist/src/agent/image/image-helpers.js +6 -1
  75. package/dist/src/agent/image/image-helpers.js.map +1 -1
  76. package/dist/src/agent/image/index.d.ts +1 -1
  77. package/dist/src/agent/inbound/inbound-loop.d.ts +5 -0
  78. package/dist/src/agent/inbound/inbound-loop.js +41 -10
  79. package/dist/src/agent/inbound/inbound-loop.js.map +1 -1
  80. package/dist/src/agent/inbound/turn-dispatcher.d.ts +4 -0
  81. package/dist/src/agent/inbound/turn-dispatcher.js +6 -4
  82. package/dist/src/agent/inbound/turn-dispatcher.js.map +1 -1
  83. package/dist/src/agent/mcp/bundle-mcp-materialize.js +2 -1
  84. package/dist/src/agent/mcp/bundle-mcp-materialize.js.map +1 -1
  85. package/dist/src/agent/mcp/bundle-mcp-names.js +2 -1
  86. package/dist/src/agent/mcp/bundle-mcp-names.js.map +1 -1
  87. package/dist/src/agent/mcp/bundle-mcp-runtime.js +2 -1
  88. package/dist/src/agent/mcp/bundle-mcp-runtime.js.map +1 -1
  89. package/dist/src/agent/mcp/mcp-transport-config.js +2 -1
  90. package/dist/src/agent/mcp/mcp-transport-config.js.map +1 -1
  91. package/dist/src/agent/mcp/mcp-transport.js +2 -1
  92. package/dist/src/agent/mcp/mcp-transport.js.map +1 -1
  93. package/dist/src/agent/media-generation/runtime-shared.js +2 -9
  94. package/dist/src/agent/media-generation/runtime-shared.js.map +1 -1
  95. package/dist/src/agent/messaging/command-handler.d.ts +6 -0
  96. package/dist/src/agent/messaging/command-handler.js +5 -0
  97. package/dist/src/agent/messaging/command-handler.js.map +1 -1
  98. package/dist/src/agent/prompt/safety.d.ts +0 -7
  99. package/dist/src/agent/prompt/safety.js +1 -20
  100. package/dist/src/agent/prompt/safety.js.map +1 -1
  101. package/dist/src/agent/service/build-direct-message-content.js +1 -1
  102. package/dist/src/agent/service/build-direct-message-content.js.map +1 -1
  103. package/dist/src/agent/service/direct-turn-helpers.d.ts +3 -1
  104. package/dist/src/agent/service/direct-turn-helpers.js +6 -1
  105. package/dist/src/agent/service/direct-turn-helpers.js.map +1 -1
  106. package/dist/src/agent/service/process-direct-one-shot.d.ts +4 -0
  107. package/dist/src/agent/service/process-direct-one-shot.js +15 -2
  108. package/dist/src/agent/service/process-direct-one-shot.js.map +1 -1
  109. package/dist/src/agent/service/process-direct-streaming.d.ts +4 -0
  110. package/dist/src/agent/service/process-direct-streaming.js +34 -4
  111. package/dist/src/agent/service/process-direct-streaming.js.map +1 -1
  112. package/dist/src/agent/service/webchat-tts.js +1 -1
  113. package/dist/src/agent/service/webchat-tts.js.map +1 -1
  114. package/dist/src/agent/service.d.ts +8 -0
  115. package/dist/src/agent/service.js +21 -1
  116. package/dist/src/agent/service.js.map +1 -1
  117. package/dist/src/agent/tools/create-share-tool.js +27 -20
  118. package/dist/src/agent/tools/create-share-tool.js.map +1 -1
  119. package/dist/src/agent/tools/factory.js +1 -1
  120. package/dist/src/agent/tools/index.d.ts +0 -1
  121. package/dist/src/agent/tools/index.js +4 -5
  122. package/dist/src/agent/tools/shell.js +0 -13
  123. package/dist/src/agent/tools/shell.js.map +1 -1
  124. package/dist/src/agent/tools/workflow-tool.js +10 -4
  125. package/dist/src/agent/tools/workflow-tool.js.map +1 -1
  126. package/dist/src/agent/workflow/builtins/audit-repo.d.ts +5 -1
  127. package/dist/src/agent/workflow/builtins/audit-repo.js +52 -11
  128. package/dist/src/agent/workflow/builtins/audit-repo.js.map +1 -1
  129. package/dist/src/agent/workflow/builtins/debug-incident.d.ts +13 -0
  130. package/dist/src/agent/workflow/builtins/debug-incident.js +155 -0
  131. package/dist/src/agent/workflow/builtins/debug-incident.js.map +1 -0
  132. package/dist/src/agent/workflow/builtins/index.d.ts +3 -1
  133. package/dist/src/agent/workflow/builtins/index.js +11 -1
  134. package/dist/src/agent/workflow/builtins/index.js.map +1 -1
  135. package/dist/src/agent/workflow/builtins/multi-perspective-review.d.ts +6 -1
  136. package/dist/src/agent/workflow/builtins/multi-perspective-review.js +66 -30
  137. package/dist/src/agent/workflow/builtins/multi-perspective-review.js.map +1 -1
  138. package/dist/src/agent/workflow/builtins/pr-review.d.ts +12 -0
  139. package/dist/src/agent/workflow/builtins/pr-review.js +156 -0
  140. package/dist/src/agent/workflow/builtins/pr-review.js.map +1 -0
  141. package/dist/src/agent/workflow/builtins/research.d.ts +5 -1
  142. package/dist/src/agent/workflow/builtins/research.js +37 -6
  143. package/dist/src/agent/workflow/builtins/research.js.map +1 -1
  144. package/dist/src/agent/workflow/catalog.d.ts +5 -0
  145. package/dist/src/agent/workflow/catalog.js +6 -2
  146. package/dist/src/agent/workflow/catalog.js.map +1 -1
  147. package/dist/src/agent/workflow/channel-capability.d.ts +3 -3
  148. package/dist/src/agent/workflow/index.d.ts +1 -1
  149. package/dist/src/agent/workflow/lint.d.ts +38 -0
  150. package/dist/src/agent/workflow/lint.js +74 -0
  151. package/dist/src/agent/workflow/lint.js.map +1 -0
  152. package/dist/src/agent/workflow/parser.js +13 -1
  153. package/dist/src/agent/workflow/parser.js.map +1 -1
  154. package/dist/src/agent/workflow/runtime.d.ts +3 -0
  155. package/dist/src/agent/workflow/runtime.js +76 -3
  156. package/dist/src/agent/workflow/runtime.js.map +1 -1
  157. package/dist/src/agent/workflow/types.d.ts +11 -1
  158. package/dist/src/browser/index.js +4 -4
  159. package/dist/src/browser/manager.d.ts +1 -3
  160. package/dist/src/browser/manager.js +0 -6
  161. package/dist/src/browser/manager.js.map +1 -1
  162. package/dist/src/browser/providers/browser-ext-install.d.ts +4 -4
  163. package/dist/src/browser/providers/browser-ext-install.js +38 -85
  164. package/dist/src/browser/providers/browser-ext-install.js.map +1 -1
  165. package/dist/src/browser/providers/cloakbrowser.d.ts +0 -5
  166. package/dist/src/browser/providers/cloakbrowser.js +2 -55
  167. package/dist/src/browser/providers/cloakbrowser.js.map +1 -1
  168. package/dist/src/channels/attachments/voice-stt-webchat.js +10 -8
  169. package/dist/src/channels/attachments/voice-stt-webchat.js.map +1 -1
  170. package/dist/src/channels/pairing/allow-from-file.js +9 -9
  171. package/dist/src/channels/pairing/allow-from-file.js.map +1 -1
  172. package/dist/src/channels/pairing/pairing-store.js +6 -6
  173. package/dist/src/channels/pairing/pairing-store.js.map +1 -1
  174. package/dist/src/chat-commands/builtins/session.js +1 -1
  175. package/dist/src/chat-commands/builtins/session.js.map +1 -1
  176. package/dist/src/chat-commands/builtins/tts.js +2 -2
  177. package/dist/src/chat-commands/builtins/tts.js.map +1 -1
  178. package/dist/src/chat-commands/builtins/workflow.js +7 -2
  179. package/dist/src/chat-commands/builtins/workflow.js.map +1 -1
  180. package/dist/src/chat-commands/context.d.ts +3 -0
  181. package/dist/src/chat-commands/context.js +21 -3
  182. package/dist/src/chat-commands/context.js.map +1 -1
  183. package/dist/src/chat-commands/session-key.d.ts +4 -37
  184. package/dist/src/chat-commands/session-key.js +49 -85
  185. package/dist/src/chat-commands/session-key.js.map +1 -1
  186. package/dist/src/chat-commands/types.d.ts +2 -0
  187. package/dist/src/cli/commands/agent/interactive.js +2 -2
  188. package/dist/src/cli/commands/agent/interactive.js.map +1 -1
  189. package/dist/src/cli/commands/agent/sessions.js +2 -2
  190. package/dist/src/cli/commands/agent/sessions.js.map +1 -1
  191. package/dist/src/cli/commands/agent.js +4 -5
  192. package/dist/src/cli/commands/agent.js.map +1 -1
  193. package/dist/src/cli/commands/channels.js +1 -5
  194. package/dist/src/cli/commands/channels.js.map +1 -1
  195. package/dist/src/cli/commands/gateway/lifecycle-core.js +1 -1
  196. package/dist/src/cli/commands/gateway/lifecycle-core.js.map +1 -1
  197. package/dist/src/cli/commands/gateway/logs.d.ts +9 -0
  198. package/dist/src/cli/commands/gateway/logs.js +50 -17
  199. package/dist/src/cli/commands/gateway/logs.js.map +1 -1
  200. package/dist/src/cli/commands/image.js +22 -21
  201. package/dist/src/cli/commands/image.js.map +1 -1
  202. package/dist/src/cli/commands/session/utils.js +2 -2
  203. package/dist/src/cli/commands/session/utils.js.map +1 -1
  204. package/dist/src/cli/commands/update.js +26 -46
  205. package/dist/src/cli/commands/update.js.map +1 -1
  206. package/dist/src/cli/utils/session.d.ts +0 -5
  207. package/dist/src/cli/utils/session.js +1 -6
  208. package/dist/src/cli/utils/session.js.map +1 -1
  209. package/dist/src/commands/agents.config.js +1 -1
  210. package/dist/src/commands/agents.config.js.map +1 -1
  211. package/dist/src/config/agent-profile.js +5 -27
  212. package/dist/src/config/agent-profile.js.map +1 -1
  213. package/dist/src/config/index.js +2 -2
  214. package/dist/src/config/model-input.js +2 -5
  215. package/dist/src/config/model-input.js.map +1 -1
  216. package/dist/src/config/schema.d.ts +201 -217
  217. package/dist/src/config/schema.js +54 -39
  218. package/dist/src/config/schema.js.map +1 -1
  219. package/dist/src/config/workspace-path-helpers.d.ts +1 -2
  220. package/dist/src/config/workspace-path-helpers.js.map +1 -1
  221. package/dist/src/daemon/install-plan.js +25 -1
  222. package/dist/src/daemon/install-plan.js.map +1 -1
  223. package/dist/src/daemon/launchd.d.ts +8 -0
  224. package/dist/src/daemon/launchd.js +5 -12
  225. package/dist/src/daemon/launchd.js.map +1 -1
  226. package/dist/src/daemon/schtasks.d.ts +25 -0
  227. package/dist/src/daemon/schtasks.js +166 -46
  228. package/dist/src/daemon/schtasks.js.map +1 -1
  229. package/dist/src/daemon/service.js +5 -4
  230. package/dist/src/daemon/service.js.map +1 -1
  231. package/dist/src/daemon/systemd.d.ts +6 -0
  232. package/dist/src/daemon/systemd.js +18 -3
  233. package/dist/src/daemon/systemd.js.map +1 -1
  234. package/dist/src/extensions/activation-context.js +0 -1
  235. package/dist/src/extensions/activation-context.js.map +1 -1
  236. package/dist/src/extensions/normalize-manifest.js +0 -1
  237. package/dist/src/extensions/normalize-manifest.js.map +1 -1
  238. package/dist/src/extensions/types/manifest.d.ts +0 -2
  239. package/dist/src/gateway/agent-builtin-tools.d.ts +1 -1
  240. package/dist/src/gateway/agent-builtin-tools.js +1 -0
  241. package/dist/src/gateway/agent-builtin-tools.js.map +1 -1
  242. package/dist/src/gateway/agents-admin.js +10 -2
  243. package/dist/src/gateway/agents-admin.js.map +1 -1
  244. package/dist/src/gateway/heartbeat/service.js +1 -1
  245. package/dist/src/gateway/heartbeat/service.js.map +1 -1
  246. package/dist/src/gateway/hono/app.js +1 -1
  247. package/dist/src/gateway/hono/lib/agent-model.d.ts +18 -10
  248. package/dist/src/gateway/hono/lib/agent-model.js +24 -35
  249. package/dist/src/gateway/hono/lib/agent-model.js.map +1 -1
  250. package/dist/src/gateway/hono/lib/config-payload.js +1 -1
  251. package/dist/src/gateway/hono/lib/config-payload.js.map +1 -1
  252. package/dist/src/gateway/hono/lib/safe-voice-config.js +14 -53
  253. package/dist/src/gateway/hono/lib/safe-voice-config.js.map +1 -1
  254. package/dist/src/gateway/hono/routes/config-patch/agents.js +17 -5
  255. package/dist/src/gateway/hono/routes/config-patch/agents.js.map +1 -1
  256. package/dist/src/gateway/hono/routes/config-patch/channels.js +0 -11
  257. package/dist/src/gateway/hono/routes/config-patch/channels.js.map +1 -1
  258. package/dist/src/gateway/hono/routes/goals.js +1 -1
  259. package/dist/src/gateway/hono/routes/goals.js.map +1 -1
  260. package/dist/src/gateway/hono/routes/sessions.js +28 -7
  261. package/dist/src/gateway/hono/routes/sessions.js.map +1 -1
  262. package/dist/src/gateway/hono/routes/shares.js +14 -12
  263. package/dist/src/gateway/hono/routes/shares.js.map +1 -1
  264. package/dist/src/gateway/hono/routes/tunnel.js +1 -1
  265. package/dist/src/gateway/hono/routes/update.js +4 -2
  266. package/dist/src/gateway/hono/routes/update.js.map +1 -1
  267. package/dist/src/gateway/hono/sse.js +16 -33
  268. package/dist/src/gateway/hono/sse.js.map +1 -1
  269. package/dist/src/gateway/lock.js +10 -10
  270. package/dist/src/gateway/lock.js.map +1 -1
  271. package/dist/src/gateway/ports.js +6 -6
  272. package/dist/src/gateway/ports.js.map +1 -1
  273. package/dist/src/gateway/resolve-webchat-session-key.d.ts +19 -0
  274. package/dist/src/gateway/resolve-webchat-session-key.js +46 -0
  275. package/dist/src/gateway/resolve-webchat-session-key.js.map +1 -0
  276. package/dist/src/gateway/service/run-gateway-agent.js +27 -11
  277. package/dist/src/gateway/service/run-gateway-agent.js.map +1 -1
  278. package/dist/src/gateway/service/sessions-api.d.ts +3 -0
  279. package/dist/src/gateway/service/sessions-api.js +8 -0
  280. package/dist/src/gateway/service/sessions-api.js.map +1 -1
  281. package/dist/src/gateway/service.d.ts +0 -2
  282. package/dist/src/gateway/service.js +2 -7
  283. package/dist/src/gateway/service.js.map +1 -1
  284. package/dist/src/gateway/session-reset-service.d.ts +20 -0
  285. package/dist/src/gateway/session-reset-service.js +54 -0
  286. package/dist/src/gateway/session-reset-service.js.map +1 -0
  287. package/dist/src/gateway/startup-readiness.d.ts +1 -1
  288. package/dist/src/gateway/startup-readiness.js +1 -0
  289. package/dist/src/gateway/startup-readiness.js.map +1 -1
  290. package/dist/src/infra/gateway-processes.js +2 -2
  291. package/dist/src/infra/gateway-processes.js.map +1 -1
  292. package/dist/src/infra/run-command.d.ts +16 -0
  293. package/dist/src/infra/run-command.js +67 -0
  294. package/dist/src/infra/run-command.js.map +1 -0
  295. package/dist/src/infra/update-global.d.ts +45 -0
  296. package/dist/src/infra/update-global.js +224 -0
  297. package/dist/src/infra/update-global.js.map +1 -0
  298. package/dist/src/mcp/channel-bridge.js +1 -1
  299. package/dist/src/mcp/channel-shared.js +2 -1
  300. package/dist/src/mcp/channel-shared.js.map +1 -1
  301. package/dist/src/providers/auth-runtime/auth-profile-store.js +1 -1
  302. package/dist/src/providers/auth-runtime/auth-profile-store.js.map +1 -1
  303. package/dist/src/providers/auth-runtime/resolve-auth.js +1 -12
  304. package/dist/src/providers/auth-runtime/resolve-auth.js.map +1 -1
  305. package/dist/src/providers/auth-runtime/types.d.ts +6 -12
  306. package/dist/src/routing/agent-session-key.d.ts +58 -0
  307. package/dist/src/routing/agent-session-key.js +164 -0
  308. package/dist/src/routing/agent-session-key.js.map +1 -0
  309. package/dist/src/routing/index.d.ts +1 -1
  310. package/dist/src/routing/index.js +4 -2
  311. package/dist/src/routing/index.js.map +1 -1
  312. package/dist/src/routing/resolve-route.d.ts +15 -0
  313. package/dist/src/routing/resolve-route.js +41 -20
  314. package/dist/src/routing/resolve-route.js.map +1 -1
  315. package/dist/src/routing/resolve-tui-session-key.d.ts +25 -0
  316. package/dist/src/routing/resolve-tui-session-key.js +54 -0
  317. package/dist/src/routing/resolve-tui-session-key.js.map +1 -0
  318. package/dist/src/routing/session-key-utils.d.ts +24 -0
  319. package/dist/src/routing/session-key-utils.js +92 -0
  320. package/dist/src/routing/session-key-utils.js.map +1 -0
  321. package/dist/src/routing/session-key.d.ts +19 -49
  322. package/dist/src/routing/session-key.js +143 -116
  323. package/dist/src/routing/session-key.js.map +1 -1
  324. package/dist/src/session/index.d.ts +6 -0
  325. package/dist/src/session/index.js +7 -1
  326. package/dist/src/session/init-session-turn.d.ts +30 -0
  327. package/dist/src/session/init-session-turn.js +102 -0
  328. package/dist/src/session/init-session-turn.js.map +1 -0
  329. package/dist/src/session/lifecycle-timestamps.d.ts +8 -0
  330. package/dist/src/session/lifecycle-timestamps.js +16 -0
  331. package/dist/src/session/lifecycle-timestamps.js.map +1 -0
  332. package/dist/src/session/manager.d.ts +7 -1
  333. package/dist/src/session/manager.js +8 -1
  334. package/dist/src/session/manager.js.map +1 -1
  335. package/dist/src/session/parity/transcript-paths.js +2 -2
  336. package/dist/src/session/parity/transcript-paths.js.map +1 -1
  337. package/dist/src/session/parity/xopc-session-disk-entry.d.ts +6 -0
  338. package/dist/src/session/reset-policy.d.ts +32 -0
  339. package/dist/src/session/reset-policy.js +65 -0
  340. package/dist/src/session/reset-policy.js.map +1 -0
  341. package/dist/src/session/reset-triggers.d.ts +20 -0
  342. package/dist/src/session/reset-triggers.js +63 -0
  343. package/dist/src/session/reset-triggers.js.map +1 -0
  344. package/dist/src/session/reset-type.d.ts +12 -0
  345. package/dist/src/session/reset-type.js +25 -0
  346. package/dist/src/session/reset-type.js.map +1 -0
  347. package/dist/src/session/resolve-session.d.ts +30 -0
  348. package/dist/src/session/resolve-session.js +93 -0
  349. package/dist/src/session/resolve-session.js.map +1 -0
  350. package/dist/src/session/session-title.js +3 -2
  351. package/dist/src/session/session-title.js.map +1 -1
  352. package/dist/src/session/store.d.ts +11 -4
  353. package/dist/src/session/store.js +57 -6
  354. package/dist/src/session/store.js.map +1 -1
  355. package/dist/src/session/transcript-events.js +2 -1
  356. package/dist/src/session/transcript-events.js.map +1 -1
  357. package/dist/src/share/share-url.d.ts +33 -0
  358. package/dist/src/share/share-url.js +56 -14
  359. package/dist/src/share/share-url.js.map +1 -1
  360. package/dist/src/tui/backends/embedded-backend.js +4 -9
  361. package/dist/src/tui/backends/embedded-backend.js.map +1 -1
  362. package/dist/src/tui/backends/gateway-sse-backend.js +1 -1
  363. package/dist/src/tui/backends/gateway-sse-backend.js.map +1 -1
  364. package/dist/src/tui/components/chat-log.js +3 -3
  365. package/dist/src/tui/components/chat-log.js.map +1 -1
  366. package/dist/src/tui/theme.d.ts +0 -2
  367. package/dist/src/tui/theme.js +1 -3
  368. package/dist/src/tui/theme.js.map +1 -1
  369. package/dist/src/tui/tui-commands.d.ts +3 -0
  370. package/dist/src/tui/tui-commands.js +45 -10
  371. package/dist/src/tui/tui-commands.js.map +1 -1
  372. package/dist/src/tui/tui-keybindings-file.js +1 -21
  373. package/dist/src/tui/tui-keybindings-file.js.map +1 -1
  374. package/dist/src/tui/tui-session-actions.d.ts +28 -0
  375. package/dist/src/tui/tui-session-actions.js +88 -0
  376. package/dist/src/tui/tui-session-actions.js.map +1 -0
  377. package/dist/src/tui/tui.js +52 -47
  378. package/dist/src/tui/tui.js.map +1 -1
  379. package/dist/src/utils/string-coerce.d.ts +2 -0
  380. package/dist/src/utils/string-coerce.js +10 -1
  381. package/dist/src/utils/string-coerce.js.map +1 -1
  382. package/dist/src/voice/stt/config-slice.d.ts +2 -5
  383. package/dist/src/voice/stt/config-slice.js +5 -26
  384. package/dist/src/voice/stt/config-slice.js.map +1 -1
  385. package/dist/src/voice/stt/types.d.ts +1 -18
  386. package/dist/src/voice/stt/types.js +4 -2
  387. package/dist/src/voice/stt/types.js.map +1 -1
  388. package/dist/src/voice/tts/config-slice.d.ts +3 -7
  389. package/dist/src/voice/tts/config-slice.js +7 -38
  390. package/dist/src/voice/tts/config-slice.js.map +1 -1
  391. package/dist/src/voice/tts/merge-config.js +2 -48
  392. package/dist/src/voice/tts/merge-config.js.map +1 -1
  393. package/dist/src/voice/tts/providers/alibaba-speech.js +1 -1
  394. package/dist/src/voice/tts/providers/alibaba-speech.js.map +1 -1
  395. package/dist/src/voice/tts/types.d.ts +1 -29
  396. package/dist/src/voice/tts/types.js +19 -17
  397. package/dist/src/voice/tts/types.js.map +1 -1
  398. package/package.json +1 -4
  399. package/dist/gateway/static/root/assets/agents-D3_-kNlZ.js +0 -222
  400. package/dist/gateway/static/root/assets/extension-settings-page-8PZcmWI7.js +0 -1
  401. package/dist/gateway/static/root/assets/index-ew_2L2We.css +0 -1
  402. package/dist/gateway/static/root/assets/settings-form-section-D_tgb8r2.js +0 -1
  403. package/dist/gateway/static/root/assets/settings-page-C18xBt4X.js +0 -3
  404. package/dist/gateway/static/root/assets/url-BwNL6Rgk.js +0 -3
  405. package/dist/src/agent/tools/browser-legacy-tools.d.ts +0 -17
  406. package/dist/src/agent/tools/browser-legacy-tools.js +0 -766
  407. package/dist/src/agent/tools/browser-legacy-tools.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"shares.js","names":[],"sources":["../../../../../src/gateway/hono/routes/shares.ts"],"sourcesContent":["import type { Context, Hono } from 'hono';\nimport { createReadStream } from 'node:fs';\nimport { stat } from 'node:fs/promises';\nimport { createHash } from 'node:crypto';\nimport { Readable } from 'node:stream';\n\nimport { extractToken } from '../../auth.js';\nimport { getClientIpFromHeaders } from '../../security/loopback.js';\nimport { getShareStore, shareResponseContentType } from '../../../share/share-store.js';\nimport { getSiteShareStore } from '../../../share/site-share-store.js';\nimport { resolveSiteShareConfig } from '../../../share/site-share-config.js';\nimport { resolveShareUrl } from '../../../share/share-url.js';\nimport { consumeSharePublicLimit } from '../../../share/share-rate-limit.js';\nimport { logShareAudit } from '../../../share/share-audit.js';\nimport {\n renderShareLandingPage,\n renderShareExpiredPage,\n renderFolderLandingPage,\n} from '../../../share/share-landing.js';\nimport type { ShareExpiredReason } from '../../../share/share-landing.js';\nimport type { ShareConfig, ShareRecord } from '../../../share/share-types.js';\nimport { resolveGatewayEffectiveHost } from '../../../config/gateway-bind.js';\nimport { SHARE_CONFIG_DEFAULTS } from '../../../share/share-types.js';\nimport { createZipStream, planDirectoryFiles } from '../../../share/share-zip.js';\nimport {\n audienceDefaults,\n cleanupStagedSite,\n decideShareKind,\n forgetStagedSite,\n makeDescription,\n makeTitle,\n probeShareTarget,\n rememberStagedSite,\n stageSingleHtmlAsSite,\n type ShareAudience,\n type ShareAutoMode,\n} from '../../../share/share-auto.js';\nimport {\n deleteThumbnail,\n placeholderSvg,\n readThumbnail,\n scheduleThumbnail,\n thumbnailContentType,\n thumbnailExists,\n} from '../../../share/share-thumbnail.js';\nimport type { AuthenticatedRouteDeps } from './deps.js';\nimport type { GatewayService } from '../../service.js';\n\nfunction getShareUrlContext(service: GatewayService) {\n const gateway = service.currentConfig.gateway;\n return {\n gatewayHost: resolveGatewayEffectiveHost(service.currentConfig),\n gatewayPort: gateway.port ?? 18790,\n };\n}\n\nfunction thumbnailRenderContext(service: GatewayService) {\n const cfg = { ...SHARE_CONFIG_DEFAULTS, ...resolveShareConfig(service) };\n const port = service.currentConfig.gateway.port ?? 18790;\n // Always use loopback for the internal renderer — never the public tunnel URL.\n const internalBaseUrl = cfg.thumbnail.internalGatewayUrl ?? `http://127.0.0.1:${port}`;\n return { config: cfg.thumbnail, internalBaseUrl };\n}\n\nfunction resolveShareConfig(service: GatewayService): Partial<ShareConfig> {\n const raw = (service.currentConfig.gateway as Record<string, unknown>)?.share;\n if (!raw || typeof raw !== 'object') return {};\n return raw as Partial<ShareConfig>;\n}\n\nfunction hashGatewayToken(token: string): string {\n return createHash('sha256').update(token, 'utf8').digest('hex').slice(0, 12);\n}\n\nconst MAX_CONCURRENT_DOWNLOADS_PER_TOKEN = 5;\nconst activeDownloads = new Map<string, number>();\nconst activeZipStreams = new Map<string, number>();\n\nfunction acquireDownloadSlot(token: string): boolean {\n const current = activeDownloads.get(token) ?? 0;\n if (current >= MAX_CONCURRENT_DOWNLOADS_PER_TOKEN) return false;\n activeDownloads.set(token, current + 1);\n return true;\n}\n\nfunction releaseDownloadSlot(token: string): void {\n const current = activeDownloads.get(token) ?? 0;\n if (current <= 1) activeDownloads.delete(token);\n else activeDownloads.set(token, current - 1);\n}\n\nfunction acquireZipSlot(token: string, limit: number): boolean {\n const current = activeZipStreams.get(token) ?? 0;\n if (current >= limit) return false;\n activeZipStreams.set(token, current + 1);\n return true;\n}\n\nfunction releaseZipSlot(token: string): void {\n const current = activeZipStreams.get(token) ?? 0;\n if (current <= 1) activeZipStreams.delete(token);\n else activeZipStreams.set(token, current - 1);\n}\n\n/**\n * Whether the browser can render this MIME natively (when served with\n * `Content-Disposition: inline`). Honours the per-deployment whitelist so\n * admins can block specific types.\n */\nfunction isPreviewableInline(mime: string, whitelist: string[]): boolean {\n return whitelist.includes(mime);\n}\n\n/**\n * Whether the type benefits from being rendered through the SPA preview page\n * (e.g. markdown — the browser would otherwise show raw source). Independent\n * of the inline whitelist: SPA preview pulls the bytes via the share API and\n * renders client-side, so admins control reach via TTL/maxViews, not MIME.\n */\nfunction isRichSpaPreviewable(mime: string): boolean {\n return (\n mime === 'text/markdown' ||\n mime === 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' ||\n mime === 'application/vnd.openxmlformats-officedocument.presentationml.presentation'\n );\n}\n\nfunction rfc5987ContentDisposition(disposition: 'inline' | 'attachment', fileName: string): string {\n const ascii = fileName.replace(/[^\\x20-\\x7e]/g, '_').replace(/\"/g, '');\n const utf8 = encodeURIComponent(fileName);\n return `${disposition}; filename=\"${ascii}\"; filename*=UTF-8''${utf8}`;\n}\n\n// ── Public routes (no auth required) ──────────────────────────────────────────\n\nexport function registerSharePublicRoutes(app: Hono, service: GatewayService): void {\n const store = getShareStore(resolveShareConfig(service));\n\n /** Landing page — does NOT consume downloadCount. */\n app.get('/s/:token', async (c) => {\n const clientIp = getClientIpFromHeaders({ get: (n: string) => c.req.header(n) ?? undefined });\n const rateResult = consumeSharePublicLimit(clientIp);\n if (!rateResult.allowed) {\n c.header('Retry-After', String(Math.ceil(rateResult.retryAfterMs / 1000)));\n return c.text('Too many requests', 429);\n }\n\n const token = c.req.param('token');\n const record = store.getByToken(token);\n if (!record) return c.html(renderShareExpiredPage('not_found'), 404);\n\n const validation = store.validateAccess(record);\n if (!validation.valid) {\n logShareAudit(\n 'share.access_denied',\n { shareId: record.id, tokenPrefix: token.slice(0, 8), reason: validation.reason, clientIp },\n `Share access denied: ${validation.reason}`,\n );\n return c.html(renderShareExpiredPage(validation.reason as ShareExpiredReason), 410);\n }\n\n if (record.kind === 'directory') {\n return renderDirectoryLanding(c, store, service, record, token);\n }\n\n // File: support direct download / inline preview shortcuts\n if (c.req.query('dl') === '1') {\n return handleFileDownload(c, store, record, clientIp);\n }\n if (c.req.query('inline') === '1') {\n const cfg = { ...SHARE_CONFIG_DEFAULTS, ...resolveShareConfig(service) };\n if (cfg.inlinePreviewMimes.includes(record.mimeType)) {\n return handleFileDownload(c, store, record, clientIp, true);\n }\n }\n\n const downloadPath = `/s/${token}/download`;\n const cfg = { ...SHARE_CONFIG_DEFAULTS, ...resolveShareConfig(service) };\n const previewable = isPreviewableInline(record.mimeType, cfg.inlinePreviewMimes);\n const richPreviewable = isRichSpaPreviewable(record.mimeType);\n const og = buildLandingOg(service, record);\n return c.html(\n renderShareLandingPage(record, downloadPath, {\n inlineUrl: previewable ? `/s/${token}?inline=1` : null,\n previewUrl: richPreviewable ? `/#/share/${encodeURIComponent(token)}` : null,\n og,\n }),\n );\n });\n\n /** Single-file download — POST so unfurl/scrapers don't consume views. */\n app.post('/s/:token/download', async (c) => {\n const clientIp = getClientIpFromHeaders({ get: (n: string) => c.req.header(n) ?? undefined });\n const rateResult = consumeSharePublicLimit(clientIp);\n if (!rateResult.allowed) {\n c.header('Retry-After', String(Math.ceil(rateResult.retryAfterMs / 1000)));\n return c.text('Too many requests', 429);\n }\n\n const token = c.req.param('token');\n const record = store.getByToken(token);\n if (!record) return c.html(renderShareExpiredPage('not_found'), 404);\n\n const validation = store.validateAccess(record);\n if (!validation.valid) {\n logShareAudit(\n 'share.access_denied',\n { shareId: record.id, tokenPrefix: token.slice(0, 8), reason: validation.reason, clientIp },\n `Share download denied: ${validation.reason}`,\n );\n return c.html(renderShareExpiredPage(validation.reason as ShareExpiredReason), 410);\n }\n\n if (record.kind === 'directory') {\n return c.html(renderShareExpiredPage('not_found'), 404);\n }\n return handleFileDownload(c, store, record, clientIp);\n });\n\n /** Directory child file (GET — preview counts as download per product). */\n app.get('/s/:token/file', async (c) => {\n const clientIp = getClientIpFromHeaders({ get: (n: string) => c.req.header(n) ?? undefined });\n const rateResult = consumeSharePublicLimit(clientIp);\n if (!rateResult.allowed) {\n c.header('Retry-After', String(Math.ceil(rateResult.retryAfterMs / 1000)));\n return c.text('Too many requests', 429);\n }\n\n const token = c.req.param('token');\n const record = store.getByToken(token);\n if (!record) return c.html(renderShareExpiredPage('not_found'), 404);\n if (record.kind !== 'directory') return c.html(renderShareExpiredPage('not_found'), 404);\n\n const validation = store.validateAccess(record);\n if (!validation.valid) {\n return c.html(renderShareExpiredPage(validation.reason as ShareExpiredReason), 410);\n }\n\n const relPath = c.req.query('path') ?? '';\n const inline = c.req.query('inline') === '1' || c.req.query('dl') !== '1';\n return handleDirectoryFile(c, store, service, record, relPath, clientIp, inline);\n });\n\n /** Directory JSON listing. */\n app.get('/s/:token/tree', async (c) => {\n const clientIp = getClientIpFromHeaders({ get: (n: string) => c.req.header(n) ?? undefined });\n const rateResult = consumeSharePublicLimit(clientIp);\n if (!rateResult.allowed) {\n c.header('Retry-After', String(Math.ceil(rateResult.retryAfterMs / 1000)));\n return c.text('Too many requests', 429);\n }\n\n const token = c.req.param('token');\n const record = store.getByToken(token);\n if (!record) return c.json({ ok: false, error: { message: 'not_found' } }, 404);\n if (record.kind !== 'directory') return c.json({ ok: false, error: { message: 'not_directory' } }, 400);\n\n const validation = store.validateAccess(record);\n if (!validation.valid) {\n return c.json({ ok: false, error: { message: validation.reason } }, 410);\n }\n\n const path = c.req.query('path') ?? '';\n // Tree HTML browser pages: render landing for the sub-path\n if ((c.req.header('accept') ?? '').includes('text/html')) {\n return renderDirectoryLanding(c, store, service, record, token, path);\n }\n try {\n const listing = await store.listDirectory(record, path);\n return c.json({ ok: true, payload: listing });\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n return c.json({ ok: false, error: { message } }, 400);\n }\n });\n\n /** Folder ZIP download (whole share or sub-path). */\n app.get('/s/:token/zip', async (c) => {\n const clientIp = getClientIpFromHeaders({ get: (n: string) => c.req.header(n) ?? undefined });\n const rateResult = consumeSharePublicLimit(clientIp);\n if (!rateResult.allowed) {\n c.header('Retry-After', String(Math.ceil(rateResult.retryAfterMs / 1000)));\n return c.text('Too many requests', 429);\n }\n\n const token = c.req.param('token');\n const record = store.getByToken(token);\n if (!record) return c.html(renderShareExpiredPage('not_found'), 404);\n if (record.kind !== 'directory') return c.html(renderShareExpiredPage('not_found'), 404);\n\n const validation = store.validateAccess(record);\n if (!validation.valid) {\n return c.html(renderShareExpiredPage(validation.reason as ShareExpiredReason), 410);\n }\n\n return handleDirectoryZip(c, store, service, record, c.req.query('path') ?? '', clientIp);\n });\n\n /** Metadata (for link preview cards / unfurl). Does NOT consume views. */\n app.get('/s/:token/meta', async (c) => {\n const clientIp = getClientIpFromHeaders({ get: (n: string) => c.req.header(n) ?? undefined });\n const rateResult = consumeSharePublicLimit(clientIp);\n if (!rateResult.allowed) {\n c.header('Retry-After', String(Math.ceil(rateResult.retryAfterMs / 1000)));\n return c.text('Too many requests', 429);\n }\n\n const token = c.req.param('token');\n const record = store.getByToken(token);\n if (!record) return c.json({ valid: false }, 404);\n\n const validation = store.validateAccess(record);\n const remainingViews = record.maxViews !== null ? Math.max(0, record.maxViews - record.downloadCount) : null;\n\n return c.json({\n kind: record.kind,\n fileName: record.fileName,\n fileSize: record.fileSize,\n mimeType: record.mimeType,\n description: record.description ?? null,\n expiresAt: record.expiresAt,\n remainingViews,\n valid: validation.valid,\n directory: record.directory ?? null,\n });\n });\n\n /** HEAD check. */\n app.on('HEAD', '/s/:token', async (c) => {\n const token = c.req.param('token');\n const record = store.getByToken(token);\n if (!record) return c.body(null, 404);\n const validation = store.validateAccess(record);\n return c.body(null, validation.valid ? 200 : 410);\n });\n\n /** Thumbnail (jpeg/png/svg) — does NOT consume views. */\n app.get('/s/:token/thumbnail', async (c) => {\n const clientIp = getClientIpFromHeaders({ get: (n: string) => c.req.header(n) ?? undefined });\n const rateResult = consumeSharePublicLimit(clientIp);\n if (!rateResult.allowed) {\n c.header('Retry-After', String(Math.ceil(rateResult.retryAfterMs / 1000)));\n return c.text('Too many requests', 429);\n }\n const token = c.req.param('token');\n const record = store.getByToken(token);\n if (!record) return c.body(null, 404);\n const validation = store.validateAccess(record);\n if (!validation.valid) return c.body(null, 410);\n\n const cached = await readThumbnail(token);\n if (cached) {\n return new Response(cached, {\n status: 200,\n headers: {\n 'Content-Type': thumbnailContentType(cached),\n 'Cache-Control': 'public, max-age=300',\n 'X-Content-Type-Options': 'nosniff',\n },\n });\n }\n // Schedule and return a placeholder so social-card scrapers always get an image.\n scheduleThumbnail({ scope: 'file', token, recordId: record.id }, thumbnailRenderContext(service));\n const placeholder = placeholderSvg(record.fileName, record.kind === 'directory' ? 'folder' : 'file');\n return new Response(placeholder, {\n status: 200,\n headers: {\n 'Content-Type': 'image/svg+xml; charset=utf-8',\n 'Cache-Control': 'public, max-age=10',\n 'X-Content-Type-Options': 'nosniff',\n },\n });\n });\n\n app.on('HEAD', '/s/:token/thumbnail', async (c) => {\n const token = c.req.param('token');\n const record = store.getByToken(token);\n if (!record) return c.body(null, 404);\n const validation = store.validateAccess(record);\n if (!validation.valid) return c.body(null, 410);\n const ready = await thumbnailExists(token);\n return c.body(null, ready ? 200 : 202);\n });\n\n /** Site-share thumbnail. Shape mirrors /s/:token/thumbnail. */\n app.get('/site/:token/thumbnail', async (c) => {\n const clientIp = getClientIpFromHeaders({ get: (n: string) => c.req.header(n) ?? undefined });\n const rateResult = consumeSharePublicLimit(clientIp);\n if (!rateResult.allowed) {\n c.header('Retry-After', String(Math.ceil(rateResult.retryAfterMs / 1000)));\n return c.text('Too many requests', 429);\n }\n const token = c.req.param('token');\n const siteStore = getSiteShareStore(resolveSiteShareConfig(service));\n const record = siteStore.getByToken(token);\n if (!record) return c.body(null, 404);\n const validation = siteStore.validateAccess(record);\n if (!validation.valid) return c.body(null, 410);\n\n const cached = await readThumbnail(token);\n if (cached) {\n return new Response(cached, {\n status: 200,\n headers: {\n 'Content-Type': thumbnailContentType(cached),\n 'Cache-Control': 'public, max-age=300',\n 'X-Content-Type-Options': 'nosniff',\n },\n });\n }\n scheduleThumbnail({ scope: 'site', token, recordId: record.id }, thumbnailRenderContext(service));\n const placeholder = placeholderSvg(record.description ?? 'site share', 'html');\n return new Response(placeholder, {\n status: 200,\n headers: {\n 'Content-Type': 'image/svg+xml; charset=utf-8',\n 'Cache-Control': 'public, max-age=10',\n 'X-Content-Type-Options': 'nosniff',\n },\n });\n });\n\n app.on('HEAD', '/site/:token/thumbnail', async (c) => {\n const token = c.req.param('token');\n const ready = await thumbnailExists(token);\n return c.body(null, ready ? 200 : 202);\n });\n\n // Wire share-store cleanup → drop on-disk thumbnail file.\n store.setCleanupHook((rec) => {\n void deleteThumbnail(rec.token);\n });\n}\n\n// ── Authenticated routes ──────────────────────────────────────────────────────\n\nexport function registerShareRoutes(authenticated: Hono, deps: AuthenticatedRouteDeps): void {\n const { service } = deps;\n const store = getShareStore(resolveShareConfig(service));\n const siteStoreEager = getSiteShareStore(resolveSiteShareConfig(service));\n // Register once: when a site share is revoked / expires, drop its staging dir (if any).\n siteStoreEager.setCleanupHook((rec) => {\n const dir = forgetStagedSite(rec.id);\n if (dir) void cleanupStagedSite(dir);\n });\n\n authenticated.post('/api/shares', async (c) => {\n const gatewayToken = extractToken({ authorization: c.req.header('authorization') ?? undefined });\n if (!gatewayToken) return c.json({ ok: false, error: { message: 'Token required' } }, 401);\n\n let body: Record<string, unknown>;\n try {\n body = (await c.req.json()) as Record<string, unknown>;\n } catch {\n return c.json({ ok: false, error: { message: 'Invalid JSON' } }, 400);\n }\n\n const path = typeof body.path === 'string' ? body.path.trim() : '';\n if (!path) return c.json({ ok: false, error: { message: 'Missing path' } }, 400);\n\n const sessionKey = typeof body.sessionKey === 'string' ? body.sessionKey.trim() : undefined;\n const agentId = typeof body.agentId === 'string' ? body.agentId.trim() : undefined;\n\n const workspaceRoot = await resolveWorkspaceRootForShare(service, sessionKey, agentId);\n if (!workspaceRoot) {\n return c.json({ ok: false, error: { message: 'Workspace not configured' } }, 400);\n }\n\n const ttlMs = typeof body.ttlMs === 'number' ? body.ttlMs : undefined;\n const maxViews = body.maxViews === null ? null : typeof body.maxViews === 'number' ? body.maxViews : undefined;\n const description = typeof body.description === 'string' ? body.description.trim() || undefined : undefined;\n const kind = body.kind === 'directory' || body.kind === 'file' ? body.kind : undefined;\n const directoryMode = body.directoryMode === 'zip-only' ? 'zip-only' : body.directoryMode === 'browse' ? 'browse' : undefined;\n const followSymlinks = body.followSymlinks === true;\n const maxFileCount = typeof body.maxFileCount === 'number' ? body.maxFileCount : undefined;\n const maxFolderSize = typeof body.maxFolderSize === 'number' ? body.maxFolderSize : undefined;\n const maxDepth = typeof body.maxDepth === 'number' ? body.maxDepth : undefined;\n\n try {\n store.updateConfig(resolveShareConfig(service));\n const record = await store.create({\n path,\n ttlMs,\n maxViews,\n description,\n sessionKey,\n agentId,\n workspaceRoot,\n gatewayTokenHash: hashGatewayToken(gatewayToken),\n kind,\n directoryMode,\n followSymlinks,\n maxFileCount,\n maxFolderSize,\n maxDepth,\n });\n\n const urlCtx = getShareUrlContext(service);\n const resolved = resolveShareUrl(record.token, urlCtx);\n\n return c.json(\n {\n ok: true,\n payload: {\n id: record.id,\n token: record.token,\n kind: record.kind,\n shareUrl: resolved.shareUrl,\n lanUrl: resolved.lanUrl,\n reachability: resolved.reachability,\n reachabilityHint: resolved.reachabilityHint,\n expiresAt: record.expiresAt,\n maxViews: record.maxViews,\n fileName: record.fileName,\n fileSize: record.fileSize,\n directory: record.directory ?? null,\n },\n },\n 201,\n );\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n return c.json({ ok: false, error: { message } }, 400);\n }\n });\n\n /**\n * Smart share — picks file vs site, fills sensible defaults, returns the\n * payload the mobile share-sheet needs (title, description, thumbnailUrl,\n * reachability) in a single round-trip.\n */\n authenticated.post('/api/shares/auto', async (c) => {\n const gatewayToken = extractToken({ authorization: c.req.header('authorization') ?? undefined });\n if (!gatewayToken) return c.json({ ok: false, error: { message: 'Token required' } }, 401);\n\n let body: Record<string, unknown>;\n try {\n body = (await c.req.json()) as Record<string, unknown>;\n } catch {\n return c.json({ ok: false, error: { message: 'Invalid JSON' } }, 400);\n }\n\n const path = typeof body.path === 'string' ? body.path.trim() : '';\n if (!path) return c.json({ ok: false, error: { message: 'Missing path' } }, 400);\n const sessionKey = typeof body.sessionKey === 'string' ? body.sessionKey.trim() : undefined;\n const agentId = typeof body.agentId === 'string' ? body.agentId.trim() : undefined;\n const mode = (typeof body.mode === 'string' && ['auto', 'force-file', 'force-site', 'force-zip'].includes(body.mode))\n ? (body.mode as ShareAutoMode)\n : 'auto';\n const audience: ShareAudience | undefined =\n body.audience === 'friend' || body.audience === 'colleague' || body.audience === 'public'\n ? body.audience\n : undefined;\n const title = typeof body.title === 'string' ? body.title : undefined;\n const description = typeof body.description === 'string' ? body.description : undefined;\n const ttlOverride = typeof body.ttlMs === 'number' ? body.ttlMs : undefined;\n const maxViewsOverride =\n body.maxViews === null ? null : typeof body.maxViews === 'number' ? body.maxViews : undefined;\n const wantThumbnail = body.thumbnail !== false;\n\n const workspaceRoot = await resolveWorkspaceRootForShare(service, sessionKey, agentId);\n if (!workspaceRoot) {\n return c.json({ ok: false, error: { message: 'Workspace not configured' } }, 400);\n }\n\n let probe;\n try {\n probe = await probeShareTarget(workspaceRoot, path);\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n return c.json({ ok: false, error: { message } }, 400);\n }\n\n let decision;\n try {\n decision = decideShareKind(probe, mode);\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n return c.json({ ok: false, error: { message } }, 400);\n }\n\n const defaults = audienceDefaults(audience);\n const ttlMs = ttlOverride ?? defaults.ttlMs;\n const maxViews = maxViewsOverride !== undefined ? maxViewsOverride : defaults.maxViews;\n const tokenHash = hashGatewayToken(gatewayToken);\n const urlCtx = getShareUrlContext(service);\n\n try {\n if (decision.kind === 'site') {\n const siteStore = getSiteShareStore(resolveSiteShareConfig(service));\n // Single HTML file → stage into a temp dir as index.html so it can be\n // served as a site (recipient lands on a rendered page, not the\n // file-landing). The staging dir is auto-cleaned on revoke/expire.\n let sitePath = path;\n let stagedDir: string | null = null;\n if (probe.kind === 'file') {\n const staged = await stageSingleHtmlAsSite(workspaceRoot, probe.absolutePath);\n sitePath = staged.relativePath;\n stagedDir = staged.stagingDir;\n }\n const siteRec = await siteStore.create({\n kind: 'static',\n path: sitePath,\n ttlMs,\n description,\n subdomain: typeof body.subdomain === 'string' ? body.subdomain : undefined,\n spaFallback: true,\n rewriteMode: 'html-css',\n sessionKey,\n agentId,\n workspaceRoot,\n gatewayTokenHash: tokenHash,\n });\n if (stagedDir) rememberStagedSite(siteRec.id, stagedDir);\n const cfg = siteStore.getConfig();\n const gatewayPort = service.currentConfig.gateway.port ?? 18790;\n const gatewayHost = resolveGatewayEffectiveHost(service.currentConfig);\n const subdomainLabel = siteRec.subdomain ?? siteRec.token;\n const tunnelUp = !!(await import('../../../tunnel/tunnel-state.js')).loadTunnelState();\n const shareUrl = tunnelUp\n ? `https://${subdomainLabel}.${cfg.publicHostSuffix}/`\n : `http://${gatewayHost}:${gatewayPort}/site/${siteRec.token}/`;\n const reachability = tunnelUp ? 'public' : (gatewayHost === '127.0.0.1' || gatewayHost === 'localhost' || gatewayHost === '::1' ? 'local-only' : 'lan');\n const thumbnailUrl = wantThumbnail\n ? `${tunnelUp ? `https://${subdomainLabel}.${cfg.publicHostSuffix}` : `http://${gatewayHost}:${gatewayPort}`}/site/${siteRec.token}/thumbnail`\n : '';\n if (wantThumbnail) {\n scheduleThumbnail({ scope: 'site', token: siteRec.token, recordId: siteRec.id }, thumbnailRenderContext(service));\n siteStore.setThumbnailStatus(siteRec.id, 'pending');\n }\n const titleOut = makeTitle(probe.kind === 'directory' ? path.split('/').pop() || path : path, title);\n return c.json({\n ok: true,\n payload: {\n share: {\n id: siteRec.id,\n kind: 'site',\n title: titleOut,\n description: makeDescription({ audience, expiresAt: siteRec.expiresAt, override: description }),\n shareUrl,\n lanUrl: null,\n reachability,\n reachabilityHint: reachability === 'public' ? null : (reachability === 'lan' ? '当前局域网内可访问,开启远程隧道后对外网可达' : '当前仅本机可访问,开启远程隧道后对外可达'),\n expiresAt: siteRec.expiresAt,\n maxViews: null,\n },\n thumbnail: {\n url: thumbnailUrl,\n status: wantThumbnail ? 'pending' : 'unavailable',\n width: SHARE_CONFIG_DEFAULTS.thumbnail.viewportWidth,\n height: SHARE_CONFIG_DEFAULTS.thumbnail.viewportHeight,\n },\n routing: { reason: decision.reason, hint: decision.hint },\n },\n }, 201);\n }\n\n return await createFileShareResponse({\n c, service, store, probe, decision,\n ttlMs, maxViews, title, description, audience,\n workspaceRoot, tokenHash, urlCtx, wantThumbnail,\n });\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n return c.json({ ok: false, error: { message } }, 400);\n }\n });\n\n authenticated.get('/api/shares', async (c) => {\n store.updateConfig(resolveShareConfig(service));\n const shares = store.getAllShares();\n const urlCtx = getShareUrlContext(service);\n const now = Date.now();\n\n const items = shares.map((r) => {\n const resolved = resolveShareUrl(r.token, urlCtx);\n const expired = now >= new Date(r.expiresAt).getTime();\n return {\n id: r.id,\n kind: r.kind,\n fileName: r.fileName,\n workspaceRelativePath: r.workspaceRelativePath,\n shareUrl: resolved.shareUrl,\n lanUrl: resolved.lanUrl,\n reachability: resolved.reachability,\n createdAt: r.createdAt,\n expiresAt: r.expiresAt,\n downloadCount: r.downloadCount,\n maxViews: r.maxViews,\n revoked: r.revoked,\n expired,\n description: r.description ?? null,\n fileSize: r.fileSize,\n mimeType: r.mimeType,\n directory: r.directory ?? null,\n };\n });\n\n return c.json({ ok: true, payload: { shares: items } });\n });\n\n authenticated.get('/api/shares/:id', async (c) => {\n const id = c.req.param('id');\n const record = store.getById(id);\n if (!record) return c.json({ ok: false, error: { message: 'Not found' } }, 404);\n\n const urlCtx = getShareUrlContext(service);\n const resolved = resolveShareUrl(record.token, urlCtx);\n const expired = Date.now() >= new Date(record.expiresAt).getTime();\n\n return c.json({\n ok: true,\n payload: {\n ...record,\n token: undefined,\n shareUrl: resolved.shareUrl,\n lanUrl: resolved.lanUrl,\n reachability: resolved.reachability,\n expired,\n },\n });\n });\n\n authenticated.delete('/api/shares/:id', async (c) => {\n const id = c.req.param('id');\n const success = store.revoke(id);\n if (!success) return c.json({ ok: false, error: { message: 'Not found' } }, 404);\n return c.json({ ok: true });\n });\n\n authenticated.delete('/api/shares', async (c) => {\n let body: Record<string, unknown> = {};\n try {\n body = (await c.req.json()) as Record<string, unknown>;\n } catch {\n /* empty body = no-op */\n }\n\n if (body.expired === true) {\n const count = store.revokeExpired();\n return c.json({ ok: true, payload: { revokedCount: count } });\n }\n\n const ids = Array.isArray(body.ids) ? (body.ids as string[]).filter((x) => typeof x === 'string') : [];\n if (ids.length === 0) {\n return c.json({ ok: false, error: { message: 'Provide ids array or expired: true' } }, 400);\n }\n const count = store.revokeMany(ids);\n return c.json({ ok: true, payload: { revokedCount: count } });\n });\n\n authenticated.patch('/api/shares/:id', async (c) => {\n const id = c.req.param('id');\n let body: Record<string, unknown>;\n try {\n body = (await c.req.json()) as Record<string, unknown>;\n } catch {\n return c.json({ ok: false, error: { message: 'Invalid JSON' } }, 400);\n }\n\n const patch: { extendTtlMs?: number; maxViews?: number | null } = {};\n if (typeof body.extendTtlMs === 'number') patch.extendTtlMs = body.extendTtlMs;\n if (body.maxViews === null || typeof body.maxViews === 'number') patch.maxViews = body.maxViews as number | null;\n\n const updated = store.update(id, patch);\n if (!updated) return c.json({ ok: false, error: { message: 'Not found' } }, 404);\n\n const urlCtx = getShareUrlContext(service);\n const resolved = resolveShareUrl(updated.token, urlCtx);\n\n return c.json({\n ok: true,\n payload: {\n id: updated.id,\n expiresAt: updated.expiresAt,\n maxViews: updated.maxViews,\n shareUrl: resolved.shareUrl,\n },\n });\n });\n}\n\n// ── Directory landing / file / zip helpers ────────────────────────────────────\n\nasync function renderDirectoryLanding(\n c: Context,\n store: ReturnType<typeof getShareStore>,\n service: GatewayService,\n record: ShareRecord,\n token: string,\n subPath = '',\n): Promise<Response> {\n store.updateConfig(resolveShareConfig(service));\n const urls = {\n tree: (p: string) => (p ? `/s/${token}/tree?path=${encodeURIComponent(p)}` : `/s/${token}`),\n file: (p: string) => `/s/${token}/file?path=${encodeURIComponent(p)}`,\n zip: (p: string) => (p ? `/s/${token}/zip?path=${encodeURIComponent(p)}` : `/s/${token}/zip`),\n };\n\n let listing: import('../../../share/share-store.js').DirectoryListing | null = null;\n if (record.directory?.mode !== 'zip-only') {\n try {\n listing = await store.listDirectory(record, subPath);\n } catch (err) {\n logShareAudit(\n 'share.access_denied',\n { shareId: record.id, tokenPrefix: record.token.slice(0, 8), subPath, reason: String(err) },\n `Directory listing failed`,\n );\n return c.html(renderShareExpiredPage('not_found'), 404);\n }\n }\n const og = buildLandingOg(service, record);\n return c.html(renderFolderLandingPage(record, listing, urls, { og })) as unknown as Response;\n}\n\nfunction buildLandingOg(service: GatewayService, record: ShareRecord) {\n const resolved = resolveShareUrl(record.token, getShareUrlContext(service));\n // Only emit OG tags when the share is genuinely public — otherwise WeChat\n // and friends would silently fall back to \"no preview\" on unreachable URLs.\n if (resolved.reachability !== 'public') return undefined;\n return {\n absoluteShareUrl: resolved.shareUrl,\n absoluteThumbnailUrl: `${resolved.shareUrl}/thumbnail`,\n title: record.fileName,\n description: record.description ?? undefined,\n };\n}\n\nasync function handleDirectoryFile(\n c: Context,\n store: ReturnType<typeof getShareStore>,\n service: GatewayService,\n record: ShareRecord,\n relPath: string,\n clientIp: string,\n inline: boolean,\n): Promise<Response> {\n if (!acquireDownloadSlot(record.token)) {\n return new Response('Too many concurrent downloads for this share', { status: 429 });\n }\n\n try {\n const resolved = await store.resolveDirectoryChild(record, relPath);\n if (resolved.ok !== true) {\n logShareAudit(\n 'share.access_denied',\n { shareId: record.id, tokenPrefix: record.token.slice(0, 8), reason: resolved.reason, clientIp, relPath },\n `Directory child resolution failed: ${resolved.reason}`,\n );\n return c.html(renderShareExpiredPage('not_found'), 404);\n }\n const integrity = await store.validateFileIntegrity(record);\n if (!integrity.valid) {\n return c.html(renderShareExpiredPage((integrity.reason ?? 'file_deleted') as ShareExpiredReason), 410);\n }\n\n const fileStat = await stat(resolved.absolutePath);\n if (!fileStat.isFile()) {\n return c.html(renderShareExpiredPage('not_found'), 404);\n }\n\n // Re-check size against configured max\n store.updateConfig(resolveShareConfig(service));\n const cfg = store.getConfig();\n if (fileStat.size > cfg.maxFileSize) {\n return c.html(renderShareExpiredPage('not_found'), 410);\n }\n\n // Inline preview MIME guard\n const cfgPreview = { ...SHARE_CONFIG_DEFAULTS, ...resolveShareConfig(service) };\n const baseName = relPath.split('/').pop() || record.fileName;\n const { resolveMimeType } = await import('../../../share/share-store.js');\n const mime = resolveMimeType(baseName);\n const useInline = inline && cfgPreview.inlinePreviewMimes.includes(mime);\n\n store.incrementDownloadCount(record.id);\n logShareAudit(\n 'share.access',\n { shareId: record.id, tokenPrefix: record.token.slice(0, 8), clientIp, relPath, mode: useInline ? 'inline' : 'attachment' },\n `Directory file served: ${baseName}`,\n );\n\n const stream = createReadStream(resolved.absolutePath);\n const webStream = Readable.toWeb(stream) as ReadableStream;\n const disposition = rfc5987ContentDisposition(useInline ? 'inline' : 'attachment', baseName);\n\n return new Response(webStream, {\n status: 200,\n headers: {\n 'Content-Type': shareResponseContentType(mime),\n 'Content-Disposition': disposition,\n 'Content-Length': String(fileStat.size),\n 'Cache-Control': 'private, no-store',\n 'X-Content-Type-Options': 'nosniff',\n 'X-Frame-Options': 'DENY',\n 'Referrer-Policy': 'no-referrer',\n },\n });\n } finally {\n releaseDownloadSlot(record.token);\n }\n}\n\nasync function handleDirectoryZip(\n c: Context,\n store: ReturnType<typeof getShareStore>,\n service: GatewayService,\n record: ShareRecord,\n subPath: string,\n clientIp: string,\n): Promise<Response> {\n store.updateConfig(resolveShareConfig(service));\n const cfg = store.getConfig();\n const zipLimit = cfg.directory.zipConcurrency;\n if (!acquireZipSlot(record.token, zipLimit)) {\n return new Response('Too many concurrent ZIP streams for this share', { status: 429 });\n }\n\n try {\n const resolved = await store.resolveDirectoryChild(record, subPath);\n if (resolved.ok !== true) {\n return c.html(renderShareExpiredPage('not_found'), 404);\n }\n const integrity = await store.validateFileIntegrity(record);\n if (!integrity.valid) {\n return c.html(renderShareExpiredPage((integrity.reason ?? 'file_deleted') as ShareExpiredReason), 410);\n }\n\n const files = await planDirectoryFiles(record, {\n rootRelativePath: subPath,\n maxFileCount: cfg.directory.maxFileCount,\n maxFolderSize: cfg.directory.maxFolderSize,\n followSymlinks: record.directory?.followSymlinks ?? false,\n maxDepth: record.directory?.maxDepth ?? cfg.directory.maxDepth,\n });\n\n store.incrementDownloadCount(record.id);\n logShareAudit(\n 'share.access',\n { shareId: record.id, tokenPrefix: record.token.slice(0, 8), clientIp, mode: 'zip', subPath, fileCount: files.length },\n `Directory zip served: ${record.fileName}${subPath ? '/' + subPath : ''}`,\n );\n\n const zipName = subPath\n ? `${record.fileName}-${subPath.replace(/[\\\\/]/g, '_')}.zip`\n : `${record.fileName}.zip`;\n const stream = createZipStream({ files });\n const webStream = Readable.toWeb(stream) as ReadableStream;\n\n return new Response(webStream, {\n status: 200,\n headers: {\n 'Content-Type': 'application/zip',\n 'Content-Disposition': rfc5987ContentDisposition('attachment', zipName),\n 'Cache-Control': 'private, no-store',\n 'X-Content-Type-Options': 'nosniff',\n 'X-Frame-Options': 'DENY',\n 'Referrer-Policy': 'no-referrer',\n },\n });\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n return new Response(`zip build failed: ${message}`, { status: 500 });\n } finally {\n releaseZipSlot(record.token);\n }\n}\n\n// ── Helpers ───────────────────────────────────────────────────────────────────\n\nasync function createFileShareResponse(args: {\n c: Context;\n service: GatewayService;\n store: ReturnType<typeof getShareStore>;\n probe: Awaited<ReturnType<typeof probeShareTarget>>;\n decision: ReturnType<typeof decideShareKind>;\n ttlMs: number;\n maxViews: number | null | undefined;\n title?: string;\n description?: string;\n audience?: ShareAudience;\n workspaceRoot: string;\n tokenHash: string;\n urlCtx: ReturnType<typeof getShareUrlContext>;\n wantThumbnail: boolean;\n}): Promise<Response> {\n const { c, service, store, probe, decision, ttlMs, maxViews, title, description, audience, workspaceRoot, tokenHash, urlCtx, wantThumbnail } = args;\n store.updateConfig(resolveShareConfig(service));\n const record = await store.create({\n path: relPathFromAbs(workspaceRoot, probe.absolutePath),\n workspaceRoot,\n gatewayTokenHash: tokenHash,\n ttlMs,\n maxViews: maxViews === undefined ? undefined : maxViews,\n description,\n kind: probe.kind === 'directory' ? 'directory' : 'file',\n directoryMode: decision.kind === 'zip' ? 'zip-only' : (probe.kind === 'directory' ? 'browse' : undefined),\n });\n const resolved = resolveShareUrl(record.token, urlCtx);\n const titleOut = makeTitle(record.fileName, title);\n const descOut = makeDescription({ audience, expiresAt: record.expiresAt, override: description });\n const thumbnailUrl = wantThumbnail\n ? `${resolved.shareUrl}/thumbnail`\n : '';\n if (wantThumbnail) {\n scheduleThumbnail({ scope: 'file', token: record.token, recordId: record.id }, thumbnailRenderContext(service));\n store.setThumbnailStatus(record.id, 'pending');\n }\n return c.json({\n ok: true,\n payload: {\n share: {\n id: record.id,\n kind: decision.kind,\n title: titleOut,\n description: descOut,\n shareUrl: resolved.shareUrl,\n lanUrl: resolved.lanUrl,\n reachability: resolved.reachability,\n reachabilityHint: resolved.reachabilityHint,\n expiresAt: record.expiresAt,\n maxViews: record.maxViews,\n },\n thumbnail: {\n url: thumbnailUrl,\n status: wantThumbnail ? 'pending' : 'unavailable',\n width: SHARE_CONFIG_DEFAULTS.thumbnail.viewportWidth,\n height: SHARE_CONFIG_DEFAULTS.thumbnail.viewportHeight,\n },\n routing: { reason: decision.reason, hint: decision.hint },\n },\n }, 201) as unknown as Response;\n}\n\nfunction relPathFromAbs(workspaceRoot: string, abs: string): string {\n const root = workspaceRoot.replace(/[\\\\/]+$/, '');\n if (abs === root) return '';\n if (abs.startsWith(`${root}/`)) return abs.slice(root.length + 1);\n if (abs.startsWith(`${root}\\\\`)) return abs.slice(root.length + 1).replace(/\\\\/g, '/');\n // Fall back to basename — store.create will resolve again under workspace.\n return abs.split(/[\\\\/]/).pop() ?? abs;\n}\n\nasync function resolveWorkspaceRootForShare(\n service: GatewayService,\n sessionKey: string | undefined,\n agentId: string | undefined,\n): Promise<string | null> {\n const cfg = service.currentConfig;\n\n if (sessionKey) {\n try {\n return await service.sessions.getEffectiveWorkspacePath(sessionKey);\n } catch {\n /* fall through to agentId */\n }\n }\n\n const { getWorkspacePath } = await import('../../../config/workspace-path-helpers.js');\n const { resolveAgentWorkspaceDir, normalizeAgentId, resolveDefaultAgentId } = await import(\n '../../../agent/agent-scope.js'\n );\n\n if (agentId) {\n const normalized = normalizeAgentId(agentId);\n return resolveAgentWorkspaceDir(cfg, normalized);\n }\n\n const root = getWorkspacePath(cfg);\n if (root) return root;\n\n const defaultId = resolveDefaultAgentId(cfg);\n return resolveAgentWorkspaceDir(cfg, defaultId);\n}\n\nasync function handleFileDownload(\n c: Context,\n store: ReturnType<typeof getShareStore>,\n record: ShareRecord,\n clientIp: string,\n inline = false,\n): Promise<Response> {\n if (!acquireDownloadSlot(record.token)) {\n return new Response('Too many concurrent downloads for this share', { status: 429 });\n }\n\n try {\n const integrity = await store.validateFileIntegrity(record);\n if (!integrity.valid) {\n logShareAudit(\n 'share.access_denied',\n { shareId: record.id, tokenPrefix: record.token.slice(0, 8), reason: integrity.reason, clientIp },\n `Share file integrity check failed: ${integrity.reason}`,\n );\n return new Response(renderShareExpiredPage((integrity.reason ?? 'file_deleted') as ShareExpiredReason), {\n status: 410,\n headers: { 'Content-Type': 'text/html; charset=utf-8' },\n });\n }\n\n store.incrementDownloadCount(record.id);\n\n logShareAudit(\n 'share.access',\n { shareId: record.id, tokenPrefix: record.token.slice(0, 8), clientIp, downloadCount: record.downloadCount },\n `Share downloaded: ${record.fileName}`,\n );\n\n const fileStat = await stat(record.absolutePath);\n const stream = createReadStream(record.absolutePath);\n const webStream = Readable.toWeb(stream) as ReadableStream;\n const disposition = rfc5987ContentDisposition(inline ? 'inline' : 'attachment', record.fileName);\n\n return new Response(webStream, {\n status: 200,\n headers: {\n 'Content-Type': shareResponseContentType(record.mimeType),\n 'Content-Disposition': disposition,\n 'Content-Length': String(fileStat.size),\n 'Cache-Control': 'private, no-store',\n 'X-Content-Type-Options': 'nosniff',\n 'X-Frame-Options': 'DENY',\n 'Referrer-Policy': 'no-referrer',\n },\n });\n } finally {\n releaseDownloadSlot(record.token);\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAgDA,SAAS,mBAAmB,SAAyB;CACnD,MAAM,UAAU,QAAQ,cAAc;AACtC,QAAO;EACL,aAAa,4BAA4B,QAAQ,cAAc;EAC/D,aAAa,QAAQ,QAAQ;EAC9B;;AAGH,SAAS,uBAAuB,SAAyB;CACvD,MAAM,MAAM;EAAE,GAAG;EAAuB,GAAG,mBAAmB,QAAQ;EAAE;CACxE,MAAM,OAAO,QAAQ,cAAc,QAAQ,QAAQ;CAEnD,MAAM,kBAAkB,IAAI,UAAU,sBAAsB,oBAAoB;AAChF,QAAO;EAAE,QAAQ,IAAI;EAAW;EAAiB;;AAGnD,SAAS,mBAAmB,SAA+C;CACzE,MAAM,MAAO,QAAQ,cAAc,SAAqC;AACxE,KAAI,CAAC,OAAO,OAAO,QAAQ,SAAU,QAAO,EAAE;AAC9C,QAAO;;AAGT,SAAS,iBAAiB,OAAuB;AAC/C,QAAO,WAAW,SAAS,CAAC,OAAO,OAAO,OAAO,CAAC,OAAO,MAAM,CAAC,MAAM,GAAG,GAAG;;AAG9E,MAAM,qCAAqC;AAC3C,MAAM,kCAAkB,IAAI,KAAqB;AACjD,MAAM,mCAAmB,IAAI,KAAqB;AAElD,SAAS,oBAAoB,OAAwB;CACnD,MAAM,UAAU,gBAAgB,IAAI,MAAM,IAAI;AAC9C,KAAI,WAAW,mCAAoC,QAAO;AAC1D,iBAAgB,IAAI,OAAO,UAAU,EAAE;AACvC,QAAO;;AAGT,SAAS,oBAAoB,OAAqB;CAChD,MAAM,UAAU,gBAAgB,IAAI,MAAM,IAAI;AAC9C,KAAI,WAAW,EAAG,iBAAgB,OAAO,MAAM;KAC1C,iBAAgB,IAAI,OAAO,UAAU,EAAE;;AAG9C,SAAS,eAAe,OAAe,OAAwB;CAC7D,MAAM,UAAU,iBAAiB,IAAI,MAAM,IAAI;AAC/C,KAAI,WAAW,MAAO,QAAO;AAC7B,kBAAiB,IAAI,OAAO,UAAU,EAAE;AACxC,QAAO;;AAGT,SAAS,eAAe,OAAqB;CAC3C,MAAM,UAAU,iBAAiB,IAAI,MAAM,IAAI;AAC/C,KAAI,WAAW,EAAG,kBAAiB,OAAO,MAAM;KAC3C,kBAAiB,IAAI,OAAO,UAAU,EAAE;;;;;;;AAQ/C,SAAS,oBAAoB,MAAc,WAA8B;AACvE,QAAO,UAAU,SAAS,KAAK;;;;;;;;AASjC,SAAS,qBAAqB,MAAuB;AACnD,QACE,SAAS,mBACT,SAAS,6EACT,SAAS;;AAIb,SAAS,0BAA0B,aAAsC,UAA0B;AAGjG,QAAO,GAAG,YAAY,cAFR,SAAS,QAAQ,iBAAiB,IAAI,CAAC,QAAQ,MAAM,GAE1B,CAAC,sBAD7B,mBAAmB,SACoC;;AAKtE,SAAgB,0BAA0B,KAAW,SAA+B;CAClF,MAAM,QAAQ,cAAc,mBAAmB,QAAQ,CAAC;;AAGxD,KAAI,IAAI,aAAa,OAAO,MAAM;EAChC,MAAM,WAAW,uBAAuB,EAAE,MAAM,MAAc,EAAE,IAAI,OAAO,EAAE,IAAI,KAAA,GAAW,CAAC;EAC7F,MAAM,aAAa,wBAAwB,SAAS;AACpD,MAAI,CAAC,WAAW,SAAS;AACvB,KAAE,OAAO,eAAe,OAAO,KAAK,KAAK,WAAW,eAAe,IAAK,CAAC,CAAC;AAC1E,UAAO,EAAE,KAAK,qBAAqB,IAAI;;EAGzC,MAAM,QAAQ,EAAE,IAAI,MAAM,QAAQ;EAClC,MAAM,SAAS,MAAM,WAAW,MAAM;AACtC,MAAI,CAAC,OAAQ,QAAO,EAAE,KAAK,uBAAuB,YAAY,EAAE,IAAI;EAEpE,MAAM,aAAa,MAAM,eAAe,OAAO;AAC/C,MAAI,CAAC,WAAW,OAAO;AACrB,iBACE,uBACA;IAAE,SAAS,OAAO;IAAI,aAAa,MAAM,MAAM,GAAG,EAAE;IAAE,QAAQ,WAAW;IAAQ;IAAU,EAC3F,wBAAwB,WAAW,SACpC;AACD,UAAO,EAAE,KAAK,uBAAuB,WAAW,OAA6B,EAAE,IAAI;;AAGrF,MAAI,OAAO,SAAS,YAClB,QAAO,uBAAuB,GAAG,OAAO,SAAS,QAAQ,MAAM;AAIjE,MAAI,EAAE,IAAI,MAAM,KAAK,KAAK,IACxB,QAAO,mBAAmB,GAAG,OAAO,QAAQ,SAAS;AAEvD,MAAI,EAAE,IAAI,MAAM,SAAS,KAAK;OAExB;IADU,GAAG;IAAuB,GAAG,mBAAmB,QAAQ;IAC/D,CAAC,mBAAmB,SAAS,OAAO,SAAS,CAClD,QAAO,mBAAmB,GAAG,OAAO,QAAQ,UAAU,KAAK;;EAI/D,MAAM,eAAe,MAAM,MAAM;EACjC,MAAM,MAAM;GAAE,GAAG;GAAuB,GAAG,mBAAmB,QAAQ;GAAE;EACxE,MAAM,cAAc,oBAAoB,OAAO,UAAU,IAAI,mBAAmB;EAChF,MAAM,kBAAkB,qBAAqB,OAAO,SAAS;EAC7D,MAAM,KAAK,eAAe,SAAS,OAAO;AAC1C,SAAO,EAAE,KACP,uBAAuB,QAAQ,cAAc;GAC3C,WAAW,cAAc,MAAM,MAAM,aAAa;GAClD,YAAY,kBAAkB,YAAY,mBAAmB,MAAM,KAAK;GACxE;GACD,CAAC,CACH;GACD;;AAGF,KAAI,KAAK,sBAAsB,OAAO,MAAM;EAC1C,MAAM,WAAW,uBAAuB,EAAE,MAAM,MAAc,EAAE,IAAI,OAAO,EAAE,IAAI,KAAA,GAAW,CAAC;EAC7F,MAAM,aAAa,wBAAwB,SAAS;AACpD,MAAI,CAAC,WAAW,SAAS;AACvB,KAAE,OAAO,eAAe,OAAO,KAAK,KAAK,WAAW,eAAe,IAAK,CAAC,CAAC;AAC1E,UAAO,EAAE,KAAK,qBAAqB,IAAI;;EAGzC,MAAM,QAAQ,EAAE,IAAI,MAAM,QAAQ;EAClC,MAAM,SAAS,MAAM,WAAW,MAAM;AACtC,MAAI,CAAC,OAAQ,QAAO,EAAE,KAAK,uBAAuB,YAAY,EAAE,IAAI;EAEpE,MAAM,aAAa,MAAM,eAAe,OAAO;AAC/C,MAAI,CAAC,WAAW,OAAO;AACrB,iBACE,uBACA;IAAE,SAAS,OAAO;IAAI,aAAa,MAAM,MAAM,GAAG,EAAE;IAAE,QAAQ,WAAW;IAAQ;IAAU,EAC3F,0BAA0B,WAAW,SACtC;AACD,UAAO,EAAE,KAAK,uBAAuB,WAAW,OAA6B,EAAE,IAAI;;AAGrF,MAAI,OAAO,SAAS,YAClB,QAAO,EAAE,KAAK,uBAAuB,YAAY,EAAE,IAAI;AAEzD,SAAO,mBAAmB,GAAG,OAAO,QAAQ,SAAS;GACrD;;AAGF,KAAI,IAAI,kBAAkB,OAAO,MAAM;EACrC,MAAM,WAAW,uBAAuB,EAAE,MAAM,MAAc,EAAE,IAAI,OAAO,EAAE,IAAI,KAAA,GAAW,CAAC;EAC7F,MAAM,aAAa,wBAAwB,SAAS;AACpD,MAAI,CAAC,WAAW,SAAS;AACvB,KAAE,OAAO,eAAe,OAAO,KAAK,KAAK,WAAW,eAAe,IAAK,CAAC,CAAC;AAC1E,UAAO,EAAE,KAAK,qBAAqB,IAAI;;EAGzC,MAAM,QAAQ,EAAE,IAAI,MAAM,QAAQ;EAClC,MAAM,SAAS,MAAM,WAAW,MAAM;AACtC,MAAI,CAAC,OAAQ,QAAO,EAAE,KAAK,uBAAuB,YAAY,EAAE,IAAI;AACpE,MAAI,OAAO,SAAS,YAAa,QAAO,EAAE,KAAK,uBAAuB,YAAY,EAAE,IAAI;EAExF,MAAM,aAAa,MAAM,eAAe,OAAO;AAC/C,MAAI,CAAC,WAAW,MACd,QAAO,EAAE,KAAK,uBAAuB,WAAW,OAA6B,EAAE,IAAI;AAKrF,SAAO,oBAAoB,GAAG,OAAO,SAAS,QAF9B,EAAE,IAAI,MAAM,OAAO,IAAI,IAEwB,UADhD,EAAE,IAAI,MAAM,SAAS,KAAK,OAAO,EAAE,IAAI,MAAM,KAAK,KAAK,IACU;GAChF;;AAGF,KAAI,IAAI,kBAAkB,OAAO,MAAM;EAErC,MAAM,aAAa,wBADF,uBAAuB,EAAE,MAAM,MAAc,EAAE,IAAI,OAAO,EAAE,IAAI,KAAA,GAAW,CACzC,CAAC;AACpD,MAAI,CAAC,WAAW,SAAS;AACvB,KAAE,OAAO,eAAe,OAAO,KAAK,KAAK,WAAW,eAAe,IAAK,CAAC,CAAC;AAC1E,UAAO,EAAE,KAAK,qBAAqB,IAAI;;EAGzC,MAAM,QAAQ,EAAE,IAAI,MAAM,QAAQ;EAClC,MAAM,SAAS,MAAM,WAAW,MAAM;AACtC,MAAI,CAAC,OAAQ,QAAO,EAAE,KAAK;GAAE,IAAI;GAAO,OAAO,EAAE,SAAS,aAAa;GAAE,EAAE,IAAI;AAC/E,MAAI,OAAO,SAAS,YAAa,QAAO,EAAE,KAAK;GAAE,IAAI;GAAO,OAAO,EAAE,SAAS,iBAAiB;GAAE,EAAE,IAAI;EAEvG,MAAM,aAAa,MAAM,eAAe,OAAO;AAC/C,MAAI,CAAC,WAAW,MACd,QAAO,EAAE,KAAK;GAAE,IAAI;GAAO,OAAO,EAAE,SAAS,WAAW,QAAQ;GAAE,EAAE,IAAI;EAG1E,MAAM,OAAO,EAAE,IAAI,MAAM,OAAO,IAAI;AAEpC,OAAK,EAAE,IAAI,OAAO,SAAS,IAAI,IAAI,SAAS,YAAY,CACtD,QAAO,uBAAuB,GAAG,OAAO,SAAS,QAAQ,OAAO,KAAK;AAEvE,MAAI;GACF,MAAM,UAAU,MAAM,MAAM,cAAc,QAAQ,KAAK;AACvD,UAAO,EAAE,KAAK;IAAE,IAAI;IAAM,SAAS;IAAS,CAAC;WACtC,KAAK;GACZ,MAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;AAChE,UAAO,EAAE,KAAK;IAAE,IAAI;IAAO,OAAO,EAAE,SAAS;IAAE,EAAE,IAAI;;GAEvD;;AAGF,KAAI,IAAI,iBAAiB,OAAO,MAAM;EACpC,MAAM,WAAW,uBAAuB,EAAE,MAAM,MAAc,EAAE,IAAI,OAAO,EAAE,IAAI,KAAA,GAAW,CAAC;EAC7F,MAAM,aAAa,wBAAwB,SAAS;AACpD,MAAI,CAAC,WAAW,SAAS;AACvB,KAAE,OAAO,eAAe,OAAO,KAAK,KAAK,WAAW,eAAe,IAAK,CAAC,CAAC;AAC1E,UAAO,EAAE,KAAK,qBAAqB,IAAI;;EAGzC,MAAM,QAAQ,EAAE,IAAI,MAAM,QAAQ;EAClC,MAAM,SAAS,MAAM,WAAW,MAAM;AACtC,MAAI,CAAC,OAAQ,QAAO,EAAE,KAAK,uBAAuB,YAAY,EAAE,IAAI;AACpE,MAAI,OAAO,SAAS,YAAa,QAAO,EAAE,KAAK,uBAAuB,YAAY,EAAE,IAAI;EAExF,MAAM,aAAa,MAAM,eAAe,OAAO;AAC/C,MAAI,CAAC,WAAW,MACd,QAAO,EAAE,KAAK,uBAAuB,WAAW,OAA6B,EAAE,IAAI;AAGrF,SAAO,mBAAmB,GAAG,OAAO,SAAS,QAAQ,EAAE,IAAI,MAAM,OAAO,IAAI,IAAI,SAAS;GACzF;;AAGF,KAAI,IAAI,kBAAkB,OAAO,MAAM;EAErC,MAAM,aAAa,wBADF,uBAAuB,EAAE,MAAM,MAAc,EAAE,IAAI,OAAO,EAAE,IAAI,KAAA,GAAW,CACzC,CAAC;AACpD,MAAI,CAAC,WAAW,SAAS;AACvB,KAAE,OAAO,eAAe,OAAO,KAAK,KAAK,WAAW,eAAe,IAAK,CAAC,CAAC;AAC1E,UAAO,EAAE,KAAK,qBAAqB,IAAI;;EAGzC,MAAM,QAAQ,EAAE,IAAI,MAAM,QAAQ;EAClC,MAAM,SAAS,MAAM,WAAW,MAAM;AACtC,MAAI,CAAC,OAAQ,QAAO,EAAE,KAAK,EAAE,OAAO,OAAO,EAAE,IAAI;EAEjD,MAAM,aAAa,MAAM,eAAe,OAAO;EAC/C,MAAM,iBAAiB,OAAO,aAAa,OAAO,KAAK,IAAI,GAAG,OAAO,WAAW,OAAO,cAAc,GAAG;AAExG,SAAO,EAAE,KAAK;GACZ,MAAM,OAAO;GACb,UAAU,OAAO;GACjB,UAAU,OAAO;GACjB,UAAU,OAAO;GACjB,aAAa,OAAO,eAAe;GACnC,WAAW,OAAO;GAClB;GACA,OAAO,WAAW;GAClB,WAAW,OAAO,aAAa;GAChC,CAAC;GACF;;AAGF,KAAI,GAAG,QAAQ,aAAa,OAAO,MAAM;EACvC,MAAM,QAAQ,EAAE,IAAI,MAAM,QAAQ;EAClC,MAAM,SAAS,MAAM,WAAW,MAAM;AACtC,MAAI,CAAC,OAAQ,QAAO,EAAE,KAAK,MAAM,IAAI;EACrC,MAAM,aAAa,MAAM,eAAe,OAAO;AAC/C,SAAO,EAAE,KAAK,MAAM,WAAW,QAAQ,MAAM,IAAI;GACjD;;AAGF,KAAI,IAAI,uBAAuB,OAAO,MAAM;EAE1C,MAAM,aAAa,wBADF,uBAAuB,EAAE,MAAM,MAAc,EAAE,IAAI,OAAO,EAAE,IAAI,KAAA,GAAW,CACzC,CAAC;AACpD,MAAI,CAAC,WAAW,SAAS;AACvB,KAAE,OAAO,eAAe,OAAO,KAAK,KAAK,WAAW,eAAe,IAAK,CAAC,CAAC;AAC1E,UAAO,EAAE,KAAK,qBAAqB,IAAI;;EAEzC,MAAM,QAAQ,EAAE,IAAI,MAAM,QAAQ;EAClC,MAAM,SAAS,MAAM,WAAW,MAAM;AACtC,MAAI,CAAC,OAAQ,QAAO,EAAE,KAAK,MAAM,IAAI;AAErC,MAAI,CADe,MAAM,eAAe,OACzB,CAAC,MAAO,QAAO,EAAE,KAAK,MAAM,IAAI;EAE/C,MAAM,SAAS,MAAM,cAAc,MAAM;AACzC,MAAI,OACF,QAAO,IAAI,SAAS,QAAQ;GAC1B,QAAQ;GACR,SAAS;IACP,gBAAgB,qBAAqB,OAAO;IAC5C,iBAAiB;IACjB,0BAA0B;IAC3B;GACF,CAAC;AAGJ,oBAAkB;GAAE,OAAO;GAAQ;GAAO,UAAU,OAAO;GAAI,EAAE,uBAAuB,QAAQ,CAAC;EACjG,MAAM,cAAc,eAAe,OAAO,UAAU,OAAO,SAAS,cAAc,WAAW,OAAO;AACpG,SAAO,IAAI,SAAS,aAAa;GAC/B,QAAQ;GACR,SAAS;IACP,gBAAgB;IAChB,iBAAiB;IACjB,0BAA0B;IAC3B;GACF,CAAC;GACF;AAEF,KAAI,GAAG,QAAQ,uBAAuB,OAAO,MAAM;EACjD,MAAM,QAAQ,EAAE,IAAI,MAAM,QAAQ;EAClC,MAAM,SAAS,MAAM,WAAW,MAAM;AACtC,MAAI,CAAC,OAAQ,QAAO,EAAE,KAAK,MAAM,IAAI;AAErC,MAAI,CADe,MAAM,eAAe,OACzB,CAAC,MAAO,QAAO,EAAE,KAAK,MAAM,IAAI;EAC/C,MAAM,QAAQ,MAAM,gBAAgB,MAAM;AAC1C,SAAO,EAAE,KAAK,MAAM,QAAQ,MAAM,IAAI;GACtC;;AAGF,KAAI,IAAI,0BAA0B,OAAO,MAAM;EAE7C,MAAM,aAAa,wBADF,uBAAuB,EAAE,MAAM,MAAc,EAAE,IAAI,OAAO,EAAE,IAAI,KAAA,GAAW,CACzC,CAAC;AACpD,MAAI,CAAC,WAAW,SAAS;AACvB,KAAE,OAAO,eAAe,OAAO,KAAK,KAAK,WAAW,eAAe,IAAK,CAAC,CAAC;AAC1E,UAAO,EAAE,KAAK,qBAAqB,IAAI;;EAEzC,MAAM,QAAQ,EAAE,IAAI,MAAM,QAAQ;EAClC,MAAM,YAAY,kBAAkB,uBAAuB,QAAQ,CAAC;EACpE,MAAM,SAAS,UAAU,WAAW,MAAM;AAC1C,MAAI,CAAC,OAAQ,QAAO,EAAE,KAAK,MAAM,IAAI;AAErC,MAAI,CADe,UAAU,eAAe,OAC7B,CAAC,MAAO,QAAO,EAAE,KAAK,MAAM,IAAI;EAE/C,MAAM,SAAS,MAAM,cAAc,MAAM;AACzC,MAAI,OACF,QAAO,IAAI,SAAS,QAAQ;GAC1B,QAAQ;GACR,SAAS;IACP,gBAAgB,qBAAqB,OAAO;IAC5C,iBAAiB;IACjB,0BAA0B;IAC3B;GACF,CAAC;AAEJ,oBAAkB;GAAE,OAAO;GAAQ;GAAO,UAAU,OAAO;GAAI,EAAE,uBAAuB,QAAQ,CAAC;EACjG,MAAM,cAAc,eAAe,OAAO,eAAe,cAAc,OAAO;AAC9E,SAAO,IAAI,SAAS,aAAa;GAC/B,QAAQ;GACR,SAAS;IACP,gBAAgB;IAChB,iBAAiB;IACjB,0BAA0B;IAC3B;GACF,CAAC;GACF;AAEF,KAAI,GAAG,QAAQ,0BAA0B,OAAO,MAAM;EAEpD,MAAM,QAAQ,MAAM,gBADN,EAAE,IAAI,MAAM,QACe,CAAC;AAC1C,SAAO,EAAE,KAAK,MAAM,QAAQ,MAAM,IAAI;GACtC;AAGF,OAAM,gBAAgB,QAAQ;AACvB,kBAAgB,IAAI,MAAM;GAC/B;;AAKJ,SAAgB,oBAAoB,eAAqB,MAAoC;CAC3F,MAAM,EAAE,YAAY;CACpB,MAAM,QAAQ,cAAc,mBAAmB,QAAQ,CAAC;AACjC,mBAAkB,uBAAuB,QAAQ,CAE1D,CAAC,gBAAgB,QAAQ;EACrC,MAAM,MAAM,iBAAiB,IAAI,GAAG;AACpC,MAAI,IAAU,mBAAkB,IAAI;GACpC;AAEF,eAAc,KAAK,eAAe,OAAO,MAAM;EAC7C,MAAM,eAAe,aAAa,EAAE,eAAe,EAAE,IAAI,OAAO,gBAAgB,IAAI,KAAA,GAAW,CAAC;AAChG,MAAI,CAAC,aAAc,QAAO,EAAE,KAAK;GAAE,IAAI;GAAO,OAAO,EAAE,SAAS,kBAAkB;GAAE,EAAE,IAAI;EAE1F,IAAI;AACJ,MAAI;AACF,UAAQ,MAAM,EAAE,IAAI,MAAM;UACpB;AACN,UAAO,EAAE,KAAK;IAAE,IAAI;IAAO,OAAO,EAAE,SAAS,gBAAgB;IAAE,EAAE,IAAI;;EAGvE,MAAM,OAAO,OAAO,KAAK,SAAS,WAAW,KAAK,KAAK,MAAM,GAAG;AAChE,MAAI,CAAC,KAAM,QAAO,EAAE,KAAK;GAAE,IAAI;GAAO,OAAO,EAAE,SAAS,gBAAgB;GAAE,EAAE,IAAI;EAEhF,MAAM,aAAa,OAAO,KAAK,eAAe,WAAW,KAAK,WAAW,MAAM,GAAG,KAAA;EAClF,MAAM,UAAU,OAAO,KAAK,YAAY,WAAW,KAAK,QAAQ,MAAM,GAAG,KAAA;EAEzE,MAAM,gBAAgB,MAAM,6BAA6B,SAAS,YAAY,QAAQ;AACtF,MAAI,CAAC,cACH,QAAO,EAAE,KAAK;GAAE,IAAI;GAAO,OAAO,EAAE,SAAS,4BAA4B;GAAE,EAAE,IAAI;EAGnF,MAAM,QAAQ,OAAO,KAAK,UAAU,WAAW,KAAK,QAAQ,KAAA;EAC5D,MAAM,WAAW,KAAK,aAAa,OAAO,OAAO,OAAO,KAAK,aAAa,WAAW,KAAK,WAAW,KAAA;EACrG,MAAM,cAAc,OAAO,KAAK,gBAAgB,WAAW,KAAK,YAAY,MAAM,IAAI,KAAA,IAAY,KAAA;EAClG,MAAM,OAAO,KAAK,SAAS,eAAe,KAAK,SAAS,SAAS,KAAK,OAAO,KAAA;EAC7E,MAAM,gBAAgB,KAAK,kBAAkB,aAAa,aAAa,KAAK,kBAAkB,WAAW,WAAW,KAAA;EACpH,MAAM,iBAAiB,KAAK,mBAAmB;EAC/C,MAAM,eAAe,OAAO,KAAK,iBAAiB,WAAW,KAAK,eAAe,KAAA;EACjF,MAAM,gBAAgB,OAAO,KAAK,kBAAkB,WAAW,KAAK,gBAAgB,KAAA;EACpF,MAAM,WAAW,OAAO,KAAK,aAAa,WAAW,KAAK,WAAW,KAAA;AAErE,MAAI;AACF,SAAM,aAAa,mBAAmB,QAAQ,CAAC;GAC/C,MAAM,SAAS,MAAM,MAAM,OAAO;IAChC;IACA;IACA;IACA;IACA;IACA;IACA;IACA,kBAAkB,iBAAiB,aAAa;IAChD;IACA;IACA;IACA;IACA;IACA;IACD,CAAC;GAEF,MAAM,SAAS,mBAAmB,QAAQ;GAC1C,MAAM,WAAW,gBAAgB,OAAO,OAAO,OAAO;AAEtD,UAAO,EAAE,KACP;IACE,IAAI;IACJ,SAAS;KACP,IAAI,OAAO;KACX,OAAO,OAAO;KACd,MAAM,OAAO;KACb,UAAU,SAAS;KACnB,QAAQ,SAAS;KACjB,cAAc,SAAS;KACvB,kBAAkB,SAAS;KAC3B,WAAW,OAAO;KAClB,UAAU,OAAO;KACjB,UAAU,OAAO;KACjB,UAAU,OAAO;KACjB,WAAW,OAAO,aAAa;KAChC;IACF,EACD,IACD;WACM,KAAK;GACZ,MAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;AAChE,UAAO,EAAE,KAAK;IAAE,IAAI;IAAO,OAAO,EAAE,SAAS;IAAE,EAAE,IAAI;;GAEvD;;;;;;AAOF,eAAc,KAAK,oBAAoB,OAAO,MAAM;EAClD,MAAM,eAAe,aAAa,EAAE,eAAe,EAAE,IAAI,OAAO,gBAAgB,IAAI,KAAA,GAAW,CAAC;AAChG,MAAI,CAAC,aAAc,QAAO,EAAE,KAAK;GAAE,IAAI;GAAO,OAAO,EAAE,SAAS,kBAAkB;GAAE,EAAE,IAAI;EAE1F,IAAI;AACJ,MAAI;AACF,UAAQ,MAAM,EAAE,IAAI,MAAM;UACpB;AACN,UAAO,EAAE,KAAK;IAAE,IAAI;IAAO,OAAO,EAAE,SAAS,gBAAgB;IAAE,EAAE,IAAI;;EAGvE,MAAM,OAAO,OAAO,KAAK,SAAS,WAAW,KAAK,KAAK,MAAM,GAAG;AAChE,MAAI,CAAC,KAAM,QAAO,EAAE,KAAK;GAAE,IAAI;GAAO,OAAO,EAAE,SAAS,gBAAgB;GAAE,EAAE,IAAI;EAChF,MAAM,aAAa,OAAO,KAAK,eAAe,WAAW,KAAK,WAAW,MAAM,GAAG,KAAA;EAClF,MAAM,UAAU,OAAO,KAAK,YAAY,WAAW,KAAK,QAAQ,MAAM,GAAG,KAAA;EACzE,MAAM,OAAQ,OAAO,KAAK,SAAS,YAAY;GAAC;GAAQ;GAAc;GAAc;GAAY,CAAC,SAAS,KAAK,KAAK,GAC/G,KAAK,OACN;EACJ,MAAM,WACJ,KAAK,aAAa,YAAY,KAAK,aAAa,eAAe,KAAK,aAAa,WAC7E,KAAK,WACL,KAAA;EACN,MAAM,QAAQ,OAAO,KAAK,UAAU,WAAW,KAAK,QAAQ,KAAA;EAC5D,MAAM,cAAc,OAAO,KAAK,gBAAgB,WAAW,KAAK,cAAc,KAAA;EAC9E,MAAM,cAAc,OAAO,KAAK,UAAU,WAAW,KAAK,QAAQ,KAAA;EAClE,MAAM,mBACJ,KAAK,aAAa,OAAO,OAAO,OAAO,KAAK,aAAa,WAAW,KAAK,WAAW,KAAA;EACtF,MAAM,gBAAgB,KAAK,cAAc;EAEzC,MAAM,gBAAgB,MAAM,6BAA6B,SAAS,YAAY,QAAQ;AACtF,MAAI,CAAC,cACH,QAAO,EAAE,KAAK;GAAE,IAAI;GAAO,OAAO,EAAE,SAAS,4BAA4B;GAAE,EAAE,IAAI;EAGnF,IAAI;AACJ,MAAI;AACF,WAAQ,MAAM,iBAAiB,eAAe,KAAK;WAC5C,KAAK;GACZ,MAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;AAChE,UAAO,EAAE,KAAK;IAAE,IAAI;IAAO,OAAO,EAAE,SAAS;IAAE,EAAE,IAAI;;EAGvD,IAAI;AACJ,MAAI;AACF,cAAW,gBAAgB,OAAO,KAAK;WAChC,KAAK;GACZ,MAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;AAChE,UAAO,EAAE,KAAK;IAAE,IAAI;IAAO,OAAO,EAAE,SAAS;IAAE,EAAE,IAAI;;EAGvD,MAAM,WAAW,iBAAiB,SAAS;EAC3C,MAAM,QAAQ,eAAe,SAAS;EACtC,MAAM,WAAW,qBAAqB,KAAA,IAAY,mBAAmB,SAAS;EAC9E,MAAM,YAAY,iBAAiB,aAAa;EAChD,MAAM,SAAS,mBAAmB,QAAQ;AAE1C,MAAI;AACF,OAAI,SAAS,SAAS,QAAQ;IAC5B,MAAM,YAAY,kBAAkB,uBAAuB,QAAQ,CAAC;IAIpE,IAAI,WAAW;IACf,IAAI,YAA2B;AAC/B,QAAI,MAAM,SAAS,QAAQ;KACzB,MAAM,SAAS,MAAM,sBAAsB,eAAe,MAAM,aAAa;AAC7E,gBAAW,OAAO;AAClB,iBAAY,OAAO;;IAErB,MAAM,UAAU,MAAM,UAAU,OAAO;KACrC,MAAM;KACN,MAAM;KACN;KACA;KACA,WAAW,OAAO,KAAK,cAAc,WAAW,KAAK,YAAY,KAAA;KACjE,aAAa;KACb,aAAa;KACb;KACA;KACA;KACA,kBAAkB;KACnB,CAAC;AACF,QAAI,UAAW,oBAAmB,QAAQ,IAAI,UAAU;IACxD,MAAM,MAAM,UAAU,WAAW;IACjC,MAAM,cAAc,QAAQ,cAAc,QAAQ,QAAQ;IAC1D,MAAM,cAAc,4BAA4B,QAAQ,cAAc;IACtE,MAAM,iBAAiB,QAAQ,aAAa,QAAQ;IACpD,MAAM,WAAW,CAAC,EAAE,MAAM,OAAO,oCAAoC,iBAAiB;IACtF,MAAM,WAAW,WACb,WAAW,eAAe,GAAG,IAAI,iBAAiB,KAClD,UAAU,YAAY,GAAG,YAAY,QAAQ,QAAQ,MAAM;IAC/D,MAAM,eAAe,WAAW,WAAY,gBAAgB,eAAe,gBAAgB,eAAe,gBAAgB,QAAQ,eAAe;IACjJ,MAAM,eAAe,gBACjB,GAAG,WAAW,WAAW,eAAe,GAAG,IAAI,qBAAqB,UAAU,YAAY,GAAG,cAAc,QAAQ,QAAQ,MAAM,cACjI;AACJ,QAAI,eAAe;AACjB,uBAAkB;MAAE,OAAO;MAAQ,OAAO,QAAQ;MAAO,UAAU,QAAQ;MAAI,EAAE,uBAAuB,QAAQ,CAAC;AACjH,eAAU,mBAAmB,QAAQ,IAAI,UAAU;;IAErD,MAAM,WAAW,UAAU,MAAM,SAAS,cAAc,KAAK,MAAM,IAAI,CAAC,KAAK,IAAI,OAAO,MAAM,MAAM;AACpG,WAAO,EAAE,KAAK;KACZ,IAAI;KACJ,SAAS;MACP,OAAO;OACL,IAAI,QAAQ;OACZ,MAAM;OACN,OAAO;OACP,aAAa,gBAAgB;QAAE;QAAU,WAAW,QAAQ;QAAW,UAAU;QAAa,CAAC;OAC/F;OACA,QAAQ;OACR;OACA,kBAAkB,iBAAiB,WAAW,OAAQ,iBAAiB,QAAQ,2BAA2B;OAC1G,WAAW,QAAQ;OACnB,UAAU;OACX;MACD,WAAW;OACT,KAAK;OACL,QAAQ,gBAAgB,YAAY;OACpC,OAAO,sBAAsB,UAAU;OACvC,QAAQ,sBAAsB,UAAU;OACzC;MACD,SAAS;OAAE,QAAQ,SAAS;OAAQ,MAAM,SAAS;OAAM;MAC1D;KACF,EAAE,IAAI;;AAGT,UAAO,MAAM,wBAAwB;IACnC;IAAG;IAAS;IAAO;IAAO;IAC1B;IAAO;IAAU;IAAO;IAAa;IACrC;IAAe;IAAW;IAAQ;IACnC,CAAC;WACK,KAAK;GACZ,MAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;AAChE,UAAO,EAAE,KAAK;IAAE,IAAI;IAAO,OAAO,EAAE,SAAS;IAAE,EAAE,IAAI;;GAEvD;AAEF,eAAc,IAAI,eAAe,OAAO,MAAM;AAC5C,QAAM,aAAa,mBAAmB,QAAQ,CAAC;EAC/C,MAAM,SAAS,MAAM,cAAc;EACnC,MAAM,SAAS,mBAAmB,QAAQ;EAC1C,MAAM,MAAM,KAAK,KAAK;EAEtB,MAAM,QAAQ,OAAO,KAAK,MAAM;GAC9B,MAAM,WAAW,gBAAgB,EAAE,OAAO,OAAO;GACjD,MAAM,UAAU,OAAO,IAAI,KAAK,EAAE,UAAU,CAAC,SAAS;AACtD,UAAO;IACL,IAAI,EAAE;IACN,MAAM,EAAE;IACR,UAAU,EAAE;IACZ,uBAAuB,EAAE;IACzB,UAAU,SAAS;IACnB,QAAQ,SAAS;IACjB,cAAc,SAAS;IACvB,WAAW,EAAE;IACb,WAAW,EAAE;IACb,eAAe,EAAE;IACjB,UAAU,EAAE;IACZ,SAAS,EAAE;IACX;IACA,aAAa,EAAE,eAAe;IAC9B,UAAU,EAAE;IACZ,UAAU,EAAE;IACZ,WAAW,EAAE,aAAa;IAC3B;IACD;AAEF,SAAO,EAAE,KAAK;GAAE,IAAI;GAAM,SAAS,EAAE,QAAQ,OAAO;GAAE,CAAC;GACvD;AAEF,eAAc,IAAI,mBAAmB,OAAO,MAAM;EAChD,MAAM,KAAK,EAAE,IAAI,MAAM,KAAK;EAC5B,MAAM,SAAS,MAAM,QAAQ,GAAG;AAChC,MAAI,CAAC,OAAQ,QAAO,EAAE,KAAK;GAAE,IAAI;GAAO,OAAO,EAAE,SAAS,aAAa;GAAE,EAAE,IAAI;EAE/E,MAAM,SAAS,mBAAmB,QAAQ;EAC1C,MAAM,WAAW,gBAAgB,OAAO,OAAO,OAAO;EACtD,MAAM,UAAU,KAAK,KAAK,IAAI,IAAI,KAAK,OAAO,UAAU,CAAC,SAAS;AAElE,SAAO,EAAE,KAAK;GACZ,IAAI;GACJ,SAAS;IACP,GAAG;IACH,OAAO,KAAA;IACP,UAAU,SAAS;IACnB,QAAQ,SAAS;IACjB,cAAc,SAAS;IACvB;IACD;GACF,CAAC;GACF;AAEF,eAAc,OAAO,mBAAmB,OAAO,MAAM;EACnD,MAAM,KAAK,EAAE,IAAI,MAAM,KAAK;AAE5B,MAAI,CADY,MAAM,OAAO,GACjB,CAAE,QAAO,EAAE,KAAK;GAAE,IAAI;GAAO,OAAO,EAAE,SAAS,aAAa;GAAE,EAAE,IAAI;AAChF,SAAO,EAAE,KAAK,EAAE,IAAI,MAAM,CAAC;GAC3B;AAEF,eAAc,OAAO,eAAe,OAAO,MAAM;EAC/C,IAAI,OAAgC,EAAE;AACtC,MAAI;AACF,UAAQ,MAAM,EAAE,IAAI,MAAM;UACpB;AAIR,MAAI,KAAK,YAAY,MAAM;GACzB,MAAM,QAAQ,MAAM,eAAe;AACnC,UAAO,EAAE,KAAK;IAAE,IAAI;IAAM,SAAS,EAAE,cAAc,OAAO;IAAE,CAAC;;EAG/D,MAAM,MAAM,MAAM,QAAQ,KAAK,IAAI,GAAI,KAAK,IAAiB,QAAQ,MAAM,OAAO,MAAM,SAAS,GAAG,EAAE;AACtG,MAAI,IAAI,WAAW,EACjB,QAAO,EAAE,KAAK;GAAE,IAAI;GAAO,OAAO,EAAE,SAAS,sCAAsC;GAAE,EAAE,IAAI;EAE7F,MAAM,QAAQ,MAAM,WAAW,IAAI;AACnC,SAAO,EAAE,KAAK;GAAE,IAAI;GAAM,SAAS,EAAE,cAAc,OAAO;GAAE,CAAC;GAC7D;AAEF,eAAc,MAAM,mBAAmB,OAAO,MAAM;EAClD,MAAM,KAAK,EAAE,IAAI,MAAM,KAAK;EAC5B,IAAI;AACJ,MAAI;AACF,UAAQ,MAAM,EAAE,IAAI,MAAM;UACpB;AACN,UAAO,EAAE,KAAK;IAAE,IAAI;IAAO,OAAO,EAAE,SAAS,gBAAgB;IAAE,EAAE,IAAI;;EAGvE,MAAM,QAA4D,EAAE;AACpE,MAAI,OAAO,KAAK,gBAAgB,SAAU,OAAM,cAAc,KAAK;AACnE,MAAI,KAAK,aAAa,QAAQ,OAAO,KAAK,aAAa,SAAU,OAAM,WAAW,KAAK;EAEvF,MAAM,UAAU,MAAM,OAAO,IAAI,MAAM;AACvC,MAAI,CAAC,QAAS,QAAO,EAAE,KAAK;GAAE,IAAI;GAAO,OAAO,EAAE,SAAS,aAAa;GAAE,EAAE,IAAI;EAEhF,MAAM,SAAS,mBAAmB,QAAQ;EAC1C,MAAM,WAAW,gBAAgB,QAAQ,OAAO,OAAO;AAEvD,SAAO,EAAE,KAAK;GACZ,IAAI;GACJ,SAAS;IACP,IAAI,QAAQ;IACZ,WAAW,QAAQ;IACnB,UAAU,QAAQ;IAClB,UAAU,SAAS;IACpB;GACF,CAAC;GACF;;AAKJ,eAAe,uBACb,GACA,OACA,SACA,QACA,OACA,UAAU,IACS;AACnB,OAAM,aAAa,mBAAmB,QAAQ,CAAC;CAC/C,MAAM,OAAO;EACX,OAAO,MAAe,IAAI,MAAM,MAAM,aAAa,mBAAmB,EAAE,KAAK,MAAM;EACnF,OAAO,MAAc,MAAM,MAAM,aAAa,mBAAmB,EAAE;EACnE,MAAM,MAAe,IAAI,MAAM,MAAM,YAAY,mBAAmB,EAAE,KAAK,MAAM,MAAM;EACxF;CAED,IAAI,UAA2E;AAC/E,KAAI,OAAO,WAAW,SAAS,WAC7B,KAAI;AACF,YAAU,MAAM,MAAM,cAAc,QAAQ,QAAQ;UAC7C,KAAK;AACZ,gBACE,uBACA;GAAE,SAAS,OAAO;GAAI,aAAa,OAAO,MAAM,MAAM,GAAG,EAAE;GAAE;GAAS,QAAQ,OAAO,IAAI;GAAE,EAC3F,2BACD;AACD,SAAO,EAAE,KAAK,uBAAuB,YAAY,EAAE,IAAI;;CAG3D,MAAM,KAAK,eAAe,SAAS,OAAO;AAC1C,QAAO,EAAE,KAAK,wBAAwB,QAAQ,SAAS,MAAM,EAAE,IAAI,CAAC,CAAC;;AAGvE,SAAS,eAAe,SAAyB,QAAqB;CACpE,MAAM,WAAW,gBAAgB,OAAO,OAAO,mBAAmB,QAAQ,CAAC;AAG3E,KAAI,SAAS,iBAAiB,SAAU,QAAO,KAAA;AAC/C,QAAO;EACL,kBAAkB,SAAS;EAC3B,sBAAsB,GAAG,SAAS,SAAS;EAC3C,OAAO,OAAO;EACd,aAAa,OAAO,eAAe,KAAA;EACpC;;AAGH,eAAe,oBACb,GACA,OACA,SACA,QACA,SACA,UACA,QACmB;AACnB,KAAI,CAAC,oBAAoB,OAAO,MAAM,CACpC,QAAO,IAAI,SAAS,gDAAgD,EAAE,QAAQ,KAAK,CAAC;AAGtF,KAAI;EACF,MAAM,WAAW,MAAM,MAAM,sBAAsB,QAAQ,QAAQ;AACnE,MAAI,SAAS,OAAO,MAAM;AACxB,iBACE,uBACA;IAAE,SAAS,OAAO;IAAI,aAAa,OAAO,MAAM,MAAM,GAAG,EAAE;IAAE,QAAQ,SAAS;IAAQ;IAAU;IAAS,EACzG,sCAAsC,SAAS,SAChD;AACD,UAAO,EAAE,KAAK,uBAAuB,YAAY,EAAE,IAAI;;EAEzD,MAAM,YAAY,MAAM,MAAM,sBAAsB,OAAO;AAC3D,MAAI,CAAC,UAAU,MACb,QAAO,EAAE,KAAK,uBAAwB,UAAU,UAAU,eAAsC,EAAE,IAAI;EAGxG,MAAM,WAAW,MAAM,KAAK,SAAS,aAAa;AAClD,MAAI,CAAC,SAAS,QAAQ,CACpB,QAAO,EAAE,KAAK,uBAAuB,YAAY,EAAE,IAAI;AAIzD,QAAM,aAAa,mBAAmB,QAAQ,CAAC;EAC/C,MAAM,MAAM,MAAM,WAAW;AAC7B,MAAI,SAAS,OAAO,IAAI,YACtB,QAAO,EAAE,KAAK,uBAAuB,YAAY,EAAE,IAAI;EAIzD,MAAM,aAAa;GAAE,GAAG;GAAuB,GAAG,mBAAmB,QAAQ;GAAE;EAC/E,MAAM,WAAW,QAAQ,MAAM,IAAI,CAAC,KAAK,IAAI,OAAO;EACpD,MAAM,EAAE,oBAAoB,MAAM,OAAO;EACzC,MAAM,OAAO,gBAAgB,SAAS;EACtC,MAAM,YAAY,UAAU,WAAW,mBAAmB,SAAS,KAAK;AAExE,QAAM,uBAAuB,OAAO,GAAG;AACvC,gBACE,gBACA;GAAE,SAAS,OAAO;GAAI,aAAa,OAAO,MAAM,MAAM,GAAG,EAAE;GAAE;GAAU;GAAS,MAAM,YAAY,WAAW;GAAc,EAC3H,0BAA0B,WAC3B;EAED,MAAM,SAAS,iBAAiB,SAAS,aAAa;EACtD,MAAM,YAAY,SAAS,MAAM,OAAO;EACxC,MAAM,cAAc,0BAA0B,YAAY,WAAW,cAAc,SAAS;AAE5F,SAAO,IAAI,SAAS,WAAW;GAC7B,QAAQ;GACR,SAAS;IACP,gBAAgB,yBAAyB,KAAK;IAC9C,uBAAuB;IACvB,kBAAkB,OAAO,SAAS,KAAK;IACvC,iBAAiB;IACjB,0BAA0B;IAC1B,mBAAmB;IACnB,mBAAmB;IACpB;GACF,CAAC;WACM;AACR,sBAAoB,OAAO,MAAM;;;AAIrC,eAAe,mBACb,GACA,OACA,SACA,QACA,SACA,UACmB;AACnB,OAAM,aAAa,mBAAmB,QAAQ,CAAC;CAC/C,MAAM,MAAM,MAAM,WAAW;CAC7B,MAAM,WAAW,IAAI,UAAU;AAC/B,KAAI,CAAC,eAAe,OAAO,OAAO,SAAS,CACzC,QAAO,IAAI,SAAS,kDAAkD,EAAE,QAAQ,KAAK,CAAC;AAGxF,KAAI;AAEF,OAAI,MADmB,MAAM,sBAAsB,QAAQ,QAAQ,EACtD,OAAO,KAClB,QAAO,EAAE,KAAK,uBAAuB,YAAY,EAAE,IAAI;EAEzD,MAAM,YAAY,MAAM,MAAM,sBAAsB,OAAO;AAC3D,MAAI,CAAC,UAAU,MACb,QAAO,EAAE,KAAK,uBAAwB,UAAU,UAAU,eAAsC,EAAE,IAAI;EAGxG,MAAM,QAAQ,MAAM,mBAAmB,QAAQ;GAC7C,kBAAkB;GAClB,cAAc,IAAI,UAAU;GAC5B,eAAe,IAAI,UAAU;GAC7B,gBAAgB,OAAO,WAAW,kBAAkB;GACpD,UAAU,OAAO,WAAW,YAAY,IAAI,UAAU;GACvD,CAAC;AAEF,QAAM,uBAAuB,OAAO,GAAG;AACvC,gBACE,gBACA;GAAE,SAAS,OAAO;GAAI,aAAa,OAAO,MAAM,MAAM,GAAG,EAAE;GAAE;GAAU,MAAM;GAAO;GAAS,WAAW,MAAM;GAAQ,EACtH,yBAAyB,OAAO,WAAW,UAAU,MAAM,UAAU,KACtE;EAED,MAAM,UAAU,UACZ,GAAG,OAAO,SAAS,GAAG,QAAQ,QAAQ,UAAU,IAAI,CAAC,QACrD,GAAG,OAAO,SAAS;EACvB,MAAM,SAAS,gBAAgB,EAAE,OAAO,CAAC;EACzC,MAAM,YAAY,SAAS,MAAM,OAAO;AAExC,SAAO,IAAI,SAAS,WAAW;GAC7B,QAAQ;GACR,SAAS;IACP,gBAAgB;IAChB,uBAAuB,0BAA0B,cAAc,QAAQ;IACvE,iBAAiB;IACjB,0BAA0B;IAC1B,mBAAmB;IACnB,mBAAmB;IACpB;GACF,CAAC;UACK,KAAK;EACZ,MAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;AAChE,SAAO,IAAI,SAAS,qBAAqB,WAAW,EAAE,QAAQ,KAAK,CAAC;WAC5D;AACR,iBAAe,OAAO,MAAM;;;AAMhC,eAAe,wBAAwB,MAejB;CACpB,MAAM,EAAE,GAAG,SAAS,OAAO,OAAO,UAAU,OAAO,UAAU,OAAO,aAAa,UAAU,eAAe,WAAW,QAAQ,kBAAkB;AAC/I,OAAM,aAAa,mBAAmB,QAAQ,CAAC;CAC/C,MAAM,SAAS,MAAM,MAAM,OAAO;EAChC,MAAM,eAAe,eAAe,MAAM,aAAa;EACvD;EACA,kBAAkB;EAClB;EACA,UAAU,aAAa,KAAA,IAAY,KAAA,IAAY;EAC/C;EACA,MAAM,MAAM,SAAS,cAAc,cAAc;EACjD,eAAe,SAAS,SAAS,QAAQ,aAAc,MAAM,SAAS,cAAc,WAAW,KAAA;EAChG,CAAC;CACF,MAAM,WAAW,gBAAgB,OAAO,OAAO,OAAO;CACtD,MAAM,WAAW,UAAU,OAAO,UAAU,MAAM;CAClD,MAAM,UAAU,gBAAgB;EAAE;EAAU,WAAW,OAAO;EAAW,UAAU;EAAa,CAAC;CACjG,MAAM,eAAe,gBACjB,GAAG,SAAS,SAAS,cACrB;AACJ,KAAI,eAAe;AACjB,oBAAkB;GAAE,OAAO;GAAQ,OAAO,OAAO;GAAO,UAAU,OAAO;GAAI,EAAE,uBAAuB,QAAQ,CAAC;AAC/G,QAAM,mBAAmB,OAAO,IAAI,UAAU;;AAEhD,QAAO,EAAE,KAAK;EACZ,IAAI;EACJ,SAAS;GACP,OAAO;IACL,IAAI,OAAO;IACX,MAAM,SAAS;IACf,OAAO;IACP,aAAa;IACb,UAAU,SAAS;IACnB,QAAQ,SAAS;IACjB,cAAc,SAAS;IACvB,kBAAkB,SAAS;IAC3B,WAAW,OAAO;IAClB,UAAU,OAAO;IAClB;GACD,WAAW;IACT,KAAK;IACL,QAAQ,gBAAgB,YAAY;IACpC,OAAO,sBAAsB,UAAU;IACvC,QAAQ,sBAAsB,UAAU;IACzC;GACD,SAAS;IAAE,QAAQ,SAAS;IAAQ,MAAM,SAAS;IAAM;GAC1D;EACF,EAAE,IAAI;;AAGT,SAAS,eAAe,eAAuB,KAAqB;CAClE,MAAM,OAAO,cAAc,QAAQ,WAAW,GAAG;AACjD,KAAI,QAAQ,KAAM,QAAO;AACzB,KAAI,IAAI,WAAW,GAAG,KAAK,GAAG,CAAE,QAAO,IAAI,MAAM,KAAK,SAAS,EAAE;AACjE,KAAI,IAAI,WAAW,GAAG,KAAK,IAAI,CAAE,QAAO,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC,QAAQ,OAAO,IAAI;AAEtF,QAAO,IAAI,MAAM,QAAQ,CAAC,KAAK,IAAI;;AAGrC,eAAe,6BACb,SACA,YACA,SACwB;CACxB,MAAM,MAAM,QAAQ;AAEpB,KAAI,WACF,KAAI;AACF,SAAO,MAAM,QAAQ,SAAS,0BAA0B,WAAW;SAC7D;CAKV,MAAM,EAAE,qBAAqB,MAAM,OAAO;CAC1C,MAAM,EAAE,0BAA0B,kBAAkB,0BAA0B,MAAM,OAClF;AAGF,KAAI,QAEF,QAAO,yBAAyB,KADb,iBAAiB,QACW,CAAC;CAGlD,MAAM,OAAO,iBAAiB,IAAI;AAClC,KAAI,KAAM,QAAO;AAGjB,QAAO,yBAAyB,KADd,sBAAsB,IACM,CAAC;;AAGjD,eAAe,mBACb,GACA,OACA,QACA,UACA,SAAS,OACU;AACnB,KAAI,CAAC,oBAAoB,OAAO,MAAM,CACpC,QAAO,IAAI,SAAS,gDAAgD,EAAE,QAAQ,KAAK,CAAC;AAGtF,KAAI;EACF,MAAM,YAAY,MAAM,MAAM,sBAAsB,OAAO;AAC3D,MAAI,CAAC,UAAU,OAAO;AACpB,iBACE,uBACA;IAAE,SAAS,OAAO;IAAI,aAAa,OAAO,MAAM,MAAM,GAAG,EAAE;IAAE,QAAQ,UAAU;IAAQ;IAAU,EACjG,sCAAsC,UAAU,SACjD;AACD,UAAO,IAAI,SAAS,uBAAwB,UAAU,UAAU,eAAsC,EAAE;IACtG,QAAQ;IACR,SAAS,EAAE,gBAAgB,4BAA4B;IACxD,CAAC;;AAGJ,QAAM,uBAAuB,OAAO,GAAG;AAEvC,gBACE,gBACA;GAAE,SAAS,OAAO;GAAI,aAAa,OAAO,MAAM,MAAM,GAAG,EAAE;GAAE;GAAU,eAAe,OAAO;GAAe,EAC5G,qBAAqB,OAAO,WAC7B;EAED,MAAM,WAAW,MAAM,KAAK,OAAO,aAAa;EAChD,MAAM,SAAS,iBAAiB,OAAO,aAAa;EACpD,MAAM,YAAY,SAAS,MAAM,OAAO;EACxC,MAAM,cAAc,0BAA0B,SAAS,WAAW,cAAc,OAAO,SAAS;AAEhG,SAAO,IAAI,SAAS,WAAW;GAC7B,QAAQ;GACR,SAAS;IACP,gBAAgB,yBAAyB,OAAO,SAAS;IACzD,uBAAuB;IACvB,kBAAkB,OAAO,SAAS,KAAK;IACvC,iBAAiB;IACjB,0BAA0B;IAC1B,mBAAmB;IACnB,mBAAmB;IACpB;GACF,CAAC;WACM;AACR,sBAAoB,OAAO,MAAM"}
1
+ {"version":3,"file":"shares.js","names":[],"sources":["../../../../../src/gateway/hono/routes/shares.ts"],"sourcesContent":["import type { Context, Hono } from 'hono';\nimport { createReadStream } from 'node:fs';\nimport { stat } from 'node:fs/promises';\nimport { createHash } from 'node:crypto';\nimport { Readable } from 'node:stream';\n\nimport { extractToken } from '../../auth.js';\nimport { getClientIpFromHeaders } from '../../security/loopback.js';\nimport { getShareStore, shareResponseContentType } from '../../../share/share-store.js';\nimport { getSiteShareStore } from '../../../share/site-share-store.js';\nimport { resolveSiteShareConfig } from '../../../share/site-share-config.js';\nimport { resolveShareUrl, resolveSiteShareUrl } from '../../../share/share-url.js';\nimport { resolveReverseProxyPublicUrl } from '../../public-url.js';\nimport { consumeSharePublicLimit } from '../../../share/share-rate-limit.js';\nimport { logShareAudit } from '../../../share/share-audit.js';\nimport {\n renderShareLandingPage,\n renderShareExpiredPage,\n renderFolderLandingPage,\n} from '../../../share/share-landing.js';\nimport type { ShareExpiredReason } from '../../../share/share-landing.js';\nimport type { ShareConfig, ShareRecord } from '../../../share/share-types.js';\nimport { resolveGatewayEffectiveHost } from '../../../config/gateway-bind.js';\nimport { SHARE_CONFIG_DEFAULTS } from '../../../share/share-types.js';\nimport { createZipStream, planDirectoryFiles } from '../../../share/share-zip.js';\nimport {\n audienceDefaults,\n cleanupStagedSite,\n decideShareKind,\n forgetStagedSite,\n makeDescription,\n makeTitle,\n probeShareTarget,\n rememberStagedSite,\n stageSingleHtmlAsSite,\n type ShareAudience,\n type ShareAutoMode,\n} from '../../../share/share-auto.js';\nimport {\n deleteThumbnail,\n placeholderSvg,\n readThumbnail,\n scheduleThumbnail,\n thumbnailContentType,\n thumbnailExists,\n} from '../../../share/share-thumbnail.js';\nimport type { AuthenticatedRouteDeps } from './deps.js';\nimport type { GatewayService } from '../../service.js';\n\nfunction getShareUrlContext(service: GatewayService) {\n const gateway = service.currentConfig.gateway;\n return {\n gatewayHost: resolveGatewayEffectiveHost(service.currentConfig),\n gatewayPort: gateway.port ?? 18790,\n reverseProxyPublicUrl: resolveReverseProxyPublicUrl(service.currentConfig),\n };\n}\n\nfunction thumbnailRenderContext(service: GatewayService) {\n const cfg = { ...SHARE_CONFIG_DEFAULTS, ...resolveShareConfig(service) };\n const port = service.currentConfig.gateway.port ?? 18790;\n // Always use loopback for the internal renderer — never the public tunnel URL.\n const internalBaseUrl = cfg.thumbnail.internalGatewayUrl ?? `http://127.0.0.1:${port}`;\n return { config: cfg.thumbnail, internalBaseUrl };\n}\n\nfunction resolveShareConfig(service: GatewayService): Partial<ShareConfig> {\n const raw = (service.currentConfig.gateway as Record<string, unknown>)?.share;\n if (!raw || typeof raw !== 'object') return {};\n return raw as Partial<ShareConfig>;\n}\n\nfunction hashGatewayToken(token: string): string {\n return createHash('sha256').update(token, 'utf8').digest('hex').slice(0, 12);\n}\n\nconst MAX_CONCURRENT_DOWNLOADS_PER_TOKEN = 5;\nconst activeDownloads = new Map<string, number>();\nconst activeZipStreams = new Map<string, number>();\n\nfunction acquireDownloadSlot(token: string): boolean {\n const current = activeDownloads.get(token) ?? 0;\n if (current >= MAX_CONCURRENT_DOWNLOADS_PER_TOKEN) return false;\n activeDownloads.set(token, current + 1);\n return true;\n}\n\nfunction releaseDownloadSlot(token: string): void {\n const current = activeDownloads.get(token) ?? 0;\n if (current <= 1) activeDownloads.delete(token);\n else activeDownloads.set(token, current - 1);\n}\n\nfunction acquireZipSlot(token: string, limit: number): boolean {\n const current = activeZipStreams.get(token) ?? 0;\n if (current >= limit) return false;\n activeZipStreams.set(token, current + 1);\n return true;\n}\n\nfunction releaseZipSlot(token: string): void {\n const current = activeZipStreams.get(token) ?? 0;\n if (current <= 1) activeZipStreams.delete(token);\n else activeZipStreams.set(token, current - 1);\n}\n\n/**\n * Whether the browser can render this MIME natively (when served with\n * `Content-Disposition: inline`). Honours the per-deployment whitelist so\n * admins can block specific types.\n */\nfunction isPreviewableInline(mime: string, whitelist: string[]): boolean {\n return whitelist.includes(mime);\n}\n\n/**\n * Whether the type benefits from being rendered through the SPA preview page\n * (e.g. markdown — the browser would otherwise show raw source). Independent\n * of the inline whitelist: SPA preview pulls the bytes via the share API and\n * renders client-side, so admins control reach via TTL/maxViews, not MIME.\n */\nfunction isRichSpaPreviewable(mime: string): boolean {\n return (\n mime === 'text/markdown' ||\n mime === 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' ||\n mime === 'application/vnd.openxmlformats-officedocument.presentationml.presentation'\n );\n}\n\nfunction rfc5987ContentDisposition(disposition: 'inline' | 'attachment', fileName: string): string {\n const ascii = fileName.replace(/[^\\x20-\\x7e]/g, '_').replace(/\"/g, '');\n const utf8 = encodeURIComponent(fileName);\n return `${disposition}; filename=\"${ascii}\"; filename*=UTF-8''${utf8}`;\n}\n\n// ── Public routes (no auth required) ──────────────────────────────────────────\n\nexport function registerSharePublicRoutes(app: Hono, service: GatewayService): void {\n const store = getShareStore(resolveShareConfig(service));\n\n /** Landing page — does NOT consume downloadCount. */\n app.get('/s/:token', async (c) => {\n const clientIp = getClientIpFromHeaders({ get: (n: string) => c.req.header(n) ?? undefined });\n const rateResult = consumeSharePublicLimit(clientIp);\n if (!rateResult.allowed) {\n c.header('Retry-After', String(Math.ceil(rateResult.retryAfterMs / 1000)));\n return c.text('Too many requests', 429);\n }\n\n const token = c.req.param('token');\n const record = store.getByToken(token);\n if (!record) return c.html(renderShareExpiredPage('not_found'), 404);\n\n const validation = store.validateAccess(record);\n if (!validation.valid) {\n logShareAudit(\n 'share.access_denied',\n { shareId: record.id, tokenPrefix: token.slice(0, 8), reason: validation.reason, clientIp },\n `Share access denied: ${validation.reason}`,\n );\n return c.html(renderShareExpiredPage(validation.reason as ShareExpiredReason), 410);\n }\n\n if (record.kind === 'directory') {\n return renderDirectoryLanding(c, store, service, record, token);\n }\n\n // File: support direct download / inline preview shortcuts\n if (c.req.query('dl') === '1') {\n return handleFileDownload(c, store, record, clientIp);\n }\n if (c.req.query('inline') === '1') {\n const cfg = { ...SHARE_CONFIG_DEFAULTS, ...resolveShareConfig(service) };\n if (cfg.inlinePreviewMimes.includes(record.mimeType)) {\n return handleFileDownload(c, store, record, clientIp, true);\n }\n }\n\n const downloadPath = `/s/${token}/download`;\n const cfg = { ...SHARE_CONFIG_DEFAULTS, ...resolveShareConfig(service) };\n const previewable = isPreviewableInline(record.mimeType, cfg.inlinePreviewMimes);\n const richPreviewable = isRichSpaPreviewable(record.mimeType);\n const og = buildLandingOg(service, record);\n return c.html(\n renderShareLandingPage(record, downloadPath, {\n inlineUrl: previewable ? `/s/${token}?inline=1` : null,\n previewUrl: richPreviewable ? `/#/share/${encodeURIComponent(token)}` : null,\n og,\n }),\n );\n });\n\n /** Single-file download — POST so unfurl/scrapers don't consume views. */\n app.post('/s/:token/download', async (c) => {\n const clientIp = getClientIpFromHeaders({ get: (n: string) => c.req.header(n) ?? undefined });\n const rateResult = consumeSharePublicLimit(clientIp);\n if (!rateResult.allowed) {\n c.header('Retry-After', String(Math.ceil(rateResult.retryAfterMs / 1000)));\n return c.text('Too many requests', 429);\n }\n\n const token = c.req.param('token');\n const record = store.getByToken(token);\n if (!record) return c.html(renderShareExpiredPage('not_found'), 404);\n\n const validation = store.validateAccess(record);\n if (!validation.valid) {\n logShareAudit(\n 'share.access_denied',\n { shareId: record.id, tokenPrefix: token.slice(0, 8), reason: validation.reason, clientIp },\n `Share download denied: ${validation.reason}`,\n );\n return c.html(renderShareExpiredPage(validation.reason as ShareExpiredReason), 410);\n }\n\n if (record.kind === 'directory') {\n return c.html(renderShareExpiredPage('not_found'), 404);\n }\n return handleFileDownload(c, store, record, clientIp);\n });\n\n /** Directory child file (GET — preview counts as download per product). */\n app.get('/s/:token/file', async (c) => {\n const clientIp = getClientIpFromHeaders({ get: (n: string) => c.req.header(n) ?? undefined });\n const rateResult = consumeSharePublicLimit(clientIp);\n if (!rateResult.allowed) {\n c.header('Retry-After', String(Math.ceil(rateResult.retryAfterMs / 1000)));\n return c.text('Too many requests', 429);\n }\n\n const token = c.req.param('token');\n const record = store.getByToken(token);\n if (!record) return c.html(renderShareExpiredPage('not_found'), 404);\n if (record.kind !== 'directory') return c.html(renderShareExpiredPage('not_found'), 404);\n\n const validation = store.validateAccess(record);\n if (!validation.valid) {\n return c.html(renderShareExpiredPage(validation.reason as ShareExpiredReason), 410);\n }\n\n const relPath = c.req.query('path') ?? '';\n const inline = c.req.query('inline') === '1' || c.req.query('dl') !== '1';\n return handleDirectoryFile(c, store, service, record, relPath, clientIp, inline);\n });\n\n /** Directory JSON listing. */\n app.get('/s/:token/tree', async (c) => {\n const clientIp = getClientIpFromHeaders({ get: (n: string) => c.req.header(n) ?? undefined });\n const rateResult = consumeSharePublicLimit(clientIp);\n if (!rateResult.allowed) {\n c.header('Retry-After', String(Math.ceil(rateResult.retryAfterMs / 1000)));\n return c.text('Too many requests', 429);\n }\n\n const token = c.req.param('token');\n const record = store.getByToken(token);\n if (!record) return c.json({ ok: false, error: { message: 'not_found' } }, 404);\n if (record.kind !== 'directory') return c.json({ ok: false, error: { message: 'not_directory' } }, 400);\n\n const validation = store.validateAccess(record);\n if (!validation.valid) {\n return c.json({ ok: false, error: { message: validation.reason } }, 410);\n }\n\n const path = c.req.query('path') ?? '';\n // Tree HTML browser pages: render landing for the sub-path\n if ((c.req.header('accept') ?? '').includes('text/html')) {\n return renderDirectoryLanding(c, store, service, record, token, path);\n }\n try {\n const listing = await store.listDirectory(record, path);\n return c.json({ ok: true, payload: listing });\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n return c.json({ ok: false, error: { message } }, 400);\n }\n });\n\n /** Folder ZIP download (whole share or sub-path). */\n app.get('/s/:token/zip', async (c) => {\n const clientIp = getClientIpFromHeaders({ get: (n: string) => c.req.header(n) ?? undefined });\n const rateResult = consumeSharePublicLimit(clientIp);\n if (!rateResult.allowed) {\n c.header('Retry-After', String(Math.ceil(rateResult.retryAfterMs / 1000)));\n return c.text('Too many requests', 429);\n }\n\n const token = c.req.param('token');\n const record = store.getByToken(token);\n if (!record) return c.html(renderShareExpiredPage('not_found'), 404);\n if (record.kind !== 'directory') return c.html(renderShareExpiredPage('not_found'), 404);\n\n const validation = store.validateAccess(record);\n if (!validation.valid) {\n return c.html(renderShareExpiredPage(validation.reason as ShareExpiredReason), 410);\n }\n\n return handleDirectoryZip(c, store, service, record, c.req.query('path') ?? '', clientIp);\n });\n\n /** Metadata (for link preview cards / unfurl). Does NOT consume views. */\n app.get('/s/:token/meta', async (c) => {\n const clientIp = getClientIpFromHeaders({ get: (n: string) => c.req.header(n) ?? undefined });\n const rateResult = consumeSharePublicLimit(clientIp);\n if (!rateResult.allowed) {\n c.header('Retry-After', String(Math.ceil(rateResult.retryAfterMs / 1000)));\n return c.text('Too many requests', 429);\n }\n\n const token = c.req.param('token');\n const record = store.getByToken(token);\n if (!record) return c.json({ valid: false }, 404);\n\n const validation = store.validateAccess(record);\n const remainingViews = record.maxViews !== null ? Math.max(0, record.maxViews - record.downloadCount) : null;\n\n return c.json({\n kind: record.kind,\n fileName: record.fileName,\n fileSize: record.fileSize,\n mimeType: record.mimeType,\n description: record.description ?? null,\n expiresAt: record.expiresAt,\n remainingViews,\n valid: validation.valid,\n directory: record.directory ?? null,\n });\n });\n\n /** HEAD check. */\n app.on('HEAD', '/s/:token', async (c) => {\n const token = c.req.param('token');\n const record = store.getByToken(token);\n if (!record) return c.body(null, 404);\n const validation = store.validateAccess(record);\n return c.body(null, validation.valid ? 200 : 410);\n });\n\n /** Thumbnail (jpeg/png/svg) — does NOT consume views. */\n app.get('/s/:token/thumbnail', async (c) => {\n const clientIp = getClientIpFromHeaders({ get: (n: string) => c.req.header(n) ?? undefined });\n const rateResult = consumeSharePublicLimit(clientIp);\n if (!rateResult.allowed) {\n c.header('Retry-After', String(Math.ceil(rateResult.retryAfterMs / 1000)));\n return c.text('Too many requests', 429);\n }\n const token = c.req.param('token');\n const record = store.getByToken(token);\n if (!record) return c.body(null, 404);\n const validation = store.validateAccess(record);\n if (!validation.valid) return c.body(null, 410);\n\n const cached = await readThumbnail(token);\n if (cached) {\n return new Response(cached, {\n status: 200,\n headers: {\n 'Content-Type': thumbnailContentType(cached),\n 'Cache-Control': 'public, max-age=300',\n 'X-Content-Type-Options': 'nosniff',\n },\n });\n }\n // Schedule and return a placeholder so social-card scrapers always get an image.\n scheduleThumbnail({ scope: 'file', token, recordId: record.id }, thumbnailRenderContext(service));\n const placeholder = placeholderSvg(record.fileName, record.kind === 'directory' ? 'folder' : 'file');\n return new Response(placeholder, {\n status: 200,\n headers: {\n 'Content-Type': 'image/svg+xml; charset=utf-8',\n 'Cache-Control': 'public, max-age=10',\n 'X-Content-Type-Options': 'nosniff',\n },\n });\n });\n\n app.on('HEAD', '/s/:token/thumbnail', async (c) => {\n const token = c.req.param('token');\n const record = store.getByToken(token);\n if (!record) return c.body(null, 404);\n const validation = store.validateAccess(record);\n if (!validation.valid) return c.body(null, 410);\n const ready = await thumbnailExists(token);\n return c.body(null, ready ? 200 : 202);\n });\n\n /** Site-share thumbnail. Shape mirrors /s/:token/thumbnail. */\n app.get('/site/:token/thumbnail', async (c) => {\n const clientIp = getClientIpFromHeaders({ get: (n: string) => c.req.header(n) ?? undefined });\n const rateResult = consumeSharePublicLimit(clientIp);\n if (!rateResult.allowed) {\n c.header('Retry-After', String(Math.ceil(rateResult.retryAfterMs / 1000)));\n return c.text('Too many requests', 429);\n }\n const token = c.req.param('token');\n const siteStore = getSiteShareStore(resolveSiteShareConfig(service));\n const record = siteStore.getByToken(token);\n if (!record) return c.body(null, 404);\n const validation = siteStore.validateAccess(record);\n if (!validation.valid) return c.body(null, 410);\n\n const cached = await readThumbnail(token);\n if (cached) {\n return new Response(cached, {\n status: 200,\n headers: {\n 'Content-Type': thumbnailContentType(cached),\n 'Cache-Control': 'public, max-age=300',\n 'X-Content-Type-Options': 'nosniff',\n },\n });\n }\n scheduleThumbnail({ scope: 'site', token, recordId: record.id }, thumbnailRenderContext(service));\n const placeholder = placeholderSvg(record.description ?? 'site share', 'html');\n return new Response(placeholder, {\n status: 200,\n headers: {\n 'Content-Type': 'image/svg+xml; charset=utf-8',\n 'Cache-Control': 'public, max-age=10',\n 'X-Content-Type-Options': 'nosniff',\n },\n });\n });\n\n app.on('HEAD', '/site/:token/thumbnail', async (c) => {\n const token = c.req.param('token');\n const ready = await thumbnailExists(token);\n return c.body(null, ready ? 200 : 202);\n });\n\n // Wire share-store cleanup → drop on-disk thumbnail file.\n store.setCleanupHook((rec) => {\n void deleteThumbnail(rec.token);\n });\n}\n\n// ── Authenticated routes ──────────────────────────────────────────────────────\n\nexport function registerShareRoutes(authenticated: Hono, deps: AuthenticatedRouteDeps): void {\n const { service } = deps;\n const store = getShareStore(resolveShareConfig(service));\n const siteStoreEager = getSiteShareStore(resolveSiteShareConfig(service));\n // Register once: when a site share is revoked / expires, drop its staging dir (if any).\n siteStoreEager.setCleanupHook((rec) => {\n const dir = forgetStagedSite(rec.id);\n if (dir) void cleanupStagedSite(dir);\n });\n\n authenticated.post('/api/shares', async (c) => {\n const gatewayToken = extractToken({ authorization: c.req.header('authorization') ?? undefined });\n if (!gatewayToken) return c.json({ ok: false, error: { message: 'Token required' } }, 401);\n\n let body: Record<string, unknown>;\n try {\n body = (await c.req.json()) as Record<string, unknown>;\n } catch {\n return c.json({ ok: false, error: { message: 'Invalid JSON' } }, 400);\n }\n\n const path = typeof body.path === 'string' ? body.path.trim() : '';\n if (!path) return c.json({ ok: false, error: { message: 'Missing path' } }, 400);\n\n const sessionKey = typeof body.sessionKey === 'string' ? body.sessionKey.trim() : undefined;\n const agentId = typeof body.agentId === 'string' ? body.agentId.trim() : undefined;\n\n const workspaceRoot = await resolveWorkspaceRootForShare(service, sessionKey, agentId);\n if (!workspaceRoot) {\n return c.json({ ok: false, error: { message: 'Workspace not configured' } }, 400);\n }\n\n const ttlMs = typeof body.ttlMs === 'number' ? body.ttlMs : undefined;\n const maxViews = body.maxViews === null ? null : typeof body.maxViews === 'number' ? body.maxViews : undefined;\n const description = typeof body.description === 'string' ? body.description.trim() || undefined : undefined;\n const kind = body.kind === 'directory' || body.kind === 'file' ? body.kind : undefined;\n const directoryMode = body.directoryMode === 'zip-only' ? 'zip-only' : body.directoryMode === 'browse' ? 'browse' : undefined;\n const followSymlinks = body.followSymlinks === true;\n const maxFileCount = typeof body.maxFileCount === 'number' ? body.maxFileCount : undefined;\n const maxFolderSize = typeof body.maxFolderSize === 'number' ? body.maxFolderSize : undefined;\n const maxDepth = typeof body.maxDepth === 'number' ? body.maxDepth : undefined;\n\n try {\n store.updateConfig(resolveShareConfig(service));\n const record = await store.create({\n path,\n ttlMs,\n maxViews,\n description,\n sessionKey,\n agentId,\n workspaceRoot,\n gatewayTokenHash: hashGatewayToken(gatewayToken),\n kind,\n directoryMode,\n followSymlinks,\n maxFileCount,\n maxFolderSize,\n maxDepth,\n });\n\n const urlCtx = getShareUrlContext(service);\n const resolved = resolveShareUrl(record.token, urlCtx);\n\n return c.json(\n {\n ok: true,\n payload: {\n id: record.id,\n token: record.token,\n kind: record.kind,\n shareUrl: resolved.shareUrl,\n lanUrl: resolved.lanUrl,\n reachability: resolved.reachability,\n reachabilityHint: resolved.reachabilityHint,\n expiresAt: record.expiresAt,\n maxViews: record.maxViews,\n fileName: record.fileName,\n fileSize: record.fileSize,\n directory: record.directory ?? null,\n },\n },\n 201,\n );\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n return c.json({ ok: false, error: { message } }, 400);\n }\n });\n\n /**\n * Smart share — picks file vs site, fills sensible defaults, returns the\n * payload the mobile share-sheet needs (title, description, thumbnailUrl,\n * reachability) in a single round-trip.\n */\n authenticated.post('/api/shares/auto', async (c) => {\n const gatewayToken = extractToken({ authorization: c.req.header('authorization') ?? undefined });\n if (!gatewayToken) return c.json({ ok: false, error: { message: 'Token required' } }, 401);\n\n let body: Record<string, unknown>;\n try {\n body = (await c.req.json()) as Record<string, unknown>;\n } catch {\n return c.json({ ok: false, error: { message: 'Invalid JSON' } }, 400);\n }\n\n const path = typeof body.path === 'string' ? body.path.trim() : '';\n if (!path) return c.json({ ok: false, error: { message: 'Missing path' } }, 400);\n const sessionKey = typeof body.sessionKey === 'string' ? body.sessionKey.trim() : undefined;\n const agentId = typeof body.agentId === 'string' ? body.agentId.trim() : undefined;\n const mode = (typeof body.mode === 'string' && ['auto', 'force-file', 'force-site', 'force-zip'].includes(body.mode))\n ? (body.mode as ShareAutoMode)\n : 'auto';\n const audience: ShareAudience | undefined =\n body.audience === 'friend' || body.audience === 'colleague' || body.audience === 'public'\n ? body.audience\n : undefined;\n const title = typeof body.title === 'string' ? body.title : undefined;\n const description = typeof body.description === 'string' ? body.description : undefined;\n const ttlOverride = typeof body.ttlMs === 'number' ? body.ttlMs : undefined;\n const maxViewsOverride =\n body.maxViews === null ? null : typeof body.maxViews === 'number' ? body.maxViews : undefined;\n const wantThumbnail = body.thumbnail !== false;\n\n const workspaceRoot = await resolveWorkspaceRootForShare(service, sessionKey, agentId);\n if (!workspaceRoot) {\n return c.json({ ok: false, error: { message: 'Workspace not configured' } }, 400);\n }\n\n let probe;\n try {\n probe = await probeShareTarget(workspaceRoot, path);\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n return c.json({ ok: false, error: { message } }, 400);\n }\n\n let decision;\n try {\n decision = decideShareKind(probe, mode);\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n return c.json({ ok: false, error: { message } }, 400);\n }\n\n const defaults = audienceDefaults(audience);\n const ttlMs = ttlOverride ?? defaults.ttlMs;\n const maxViews = maxViewsOverride !== undefined ? maxViewsOverride : defaults.maxViews;\n const tokenHash = hashGatewayToken(gatewayToken);\n const urlCtx = getShareUrlContext(service);\n\n try {\n if (decision.kind === 'site') {\n const siteStore = getSiteShareStore(resolveSiteShareConfig(service));\n // Single HTML file → stage into a temp dir as index.html so it can be\n // served as a site (recipient lands on a rendered page, not the\n // file-landing). The staging dir is auto-cleaned on revoke/expire.\n let sitePath = path;\n let stagedDir: string | null = null;\n if (probe.kind === 'file') {\n const staged = await stageSingleHtmlAsSite(workspaceRoot, probe.absolutePath);\n sitePath = staged.relativePath;\n stagedDir = staged.stagingDir;\n }\n const siteRec = await siteStore.create({\n kind: 'static',\n path: sitePath,\n ttlMs,\n description,\n subdomain: typeof body.subdomain === 'string' ? body.subdomain : undefined,\n spaFallback: true,\n rewriteMode: 'html-css',\n sessionKey,\n agentId,\n workspaceRoot,\n gatewayTokenHash: tokenHash,\n });\n if (stagedDir) rememberStagedSite(siteRec.id, stagedDir);\n const cfg = siteStore.getConfig();\n const subdomainLabel = siteRec.subdomain ?? siteRec.token;\n const resolved = resolveSiteShareUrl({\n ...urlCtx,\n token: siteRec.token,\n subdomainLabel,\n publicHostSuffix: cfg.publicHostSuffix,\n });\n if (wantThumbnail) {\n scheduleThumbnail({ scope: 'site', token: siteRec.token, recordId: siteRec.id }, thumbnailRenderContext(service));\n siteStore.setThumbnailStatus(siteRec.id, 'pending');\n }\n const titleOut = makeTitle(probe.kind === 'directory' ? path.split('/').pop() || path : path, title);\n return c.json({\n ok: true,\n payload: {\n share: {\n id: siteRec.id,\n kind: 'site',\n title: titleOut,\n description: makeDescription({ audience, expiresAt: siteRec.expiresAt, override: description }),\n shareUrl: resolved.shareUrl,\n lanUrl: null,\n reachability: resolved.reachability,\n reachabilityHint: resolved.reachabilityHint,\n expiresAt: siteRec.expiresAt,\n maxViews: null,\n },\n thumbnail: {\n url: wantThumbnail ? resolved.thumbnailUrl : '',\n status: wantThumbnail ? 'pending' : 'unavailable',\n width: SHARE_CONFIG_DEFAULTS.thumbnail.viewportWidth,\n height: SHARE_CONFIG_DEFAULTS.thumbnail.viewportHeight,\n },\n routing: { reason: decision.reason, hint: decision.hint },\n },\n }, 201);\n }\n\n return await createFileShareResponse({\n c, service, store, probe, decision,\n ttlMs, maxViews, title, description, audience,\n workspaceRoot, tokenHash, urlCtx, wantThumbnail,\n });\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n return c.json({ ok: false, error: { message } }, 400);\n }\n });\n\n authenticated.get('/api/shares', async (c) => {\n store.updateConfig(resolveShareConfig(service));\n const shares = store.getAllShares();\n const urlCtx = getShareUrlContext(service);\n const now = Date.now();\n\n const items = shares.map((r) => {\n const resolved = resolveShareUrl(r.token, urlCtx);\n const expired = now >= new Date(r.expiresAt).getTime();\n return {\n id: r.id,\n kind: r.kind,\n fileName: r.fileName,\n workspaceRelativePath: r.workspaceRelativePath,\n shareUrl: resolved.shareUrl,\n lanUrl: resolved.lanUrl,\n reachability: resolved.reachability,\n createdAt: r.createdAt,\n expiresAt: r.expiresAt,\n downloadCount: r.downloadCount,\n maxViews: r.maxViews,\n revoked: r.revoked,\n expired,\n description: r.description ?? null,\n fileSize: r.fileSize,\n mimeType: r.mimeType,\n directory: r.directory ?? null,\n };\n });\n\n return c.json({ ok: true, payload: { shares: items } });\n });\n\n authenticated.get('/api/shares/:id', async (c) => {\n const id = c.req.param('id');\n const record = store.getById(id);\n if (!record) return c.json({ ok: false, error: { message: 'Not found' } }, 404);\n\n const urlCtx = getShareUrlContext(service);\n const resolved = resolveShareUrl(record.token, urlCtx);\n const expired = Date.now() >= new Date(record.expiresAt).getTime();\n\n return c.json({\n ok: true,\n payload: {\n ...record,\n token: undefined,\n shareUrl: resolved.shareUrl,\n lanUrl: resolved.lanUrl,\n reachability: resolved.reachability,\n expired,\n },\n });\n });\n\n authenticated.delete('/api/shares/:id', async (c) => {\n const id = c.req.param('id');\n const success = store.revoke(id);\n if (!success) return c.json({ ok: false, error: { message: 'Not found' } }, 404);\n return c.json({ ok: true });\n });\n\n authenticated.delete('/api/shares', async (c) => {\n let body: Record<string, unknown> = {};\n try {\n body = (await c.req.json()) as Record<string, unknown>;\n } catch {\n /* empty body = no-op */\n }\n\n if (body.expired === true) {\n const count = store.revokeExpired();\n return c.json({ ok: true, payload: { revokedCount: count } });\n }\n\n const ids = Array.isArray(body.ids) ? (body.ids as string[]).filter((x) => typeof x === 'string') : [];\n if (ids.length === 0) {\n return c.json({ ok: false, error: { message: 'Provide ids array or expired: true' } }, 400);\n }\n const count = store.revokeMany(ids);\n return c.json({ ok: true, payload: { revokedCount: count } });\n });\n\n authenticated.patch('/api/shares/:id', async (c) => {\n const id = c.req.param('id');\n let body: Record<string, unknown>;\n try {\n body = (await c.req.json()) as Record<string, unknown>;\n } catch {\n return c.json({ ok: false, error: { message: 'Invalid JSON' } }, 400);\n }\n\n const patch: { extendTtlMs?: number; maxViews?: number | null } = {};\n if (typeof body.extendTtlMs === 'number') patch.extendTtlMs = body.extendTtlMs;\n if (body.maxViews === null || typeof body.maxViews === 'number') patch.maxViews = body.maxViews as number | null;\n\n const updated = store.update(id, patch);\n if (!updated) return c.json({ ok: false, error: { message: 'Not found' } }, 404);\n\n const urlCtx = getShareUrlContext(service);\n const resolved = resolveShareUrl(updated.token, urlCtx);\n\n return c.json({\n ok: true,\n payload: {\n id: updated.id,\n expiresAt: updated.expiresAt,\n maxViews: updated.maxViews,\n shareUrl: resolved.shareUrl,\n },\n });\n });\n}\n\n// ── Directory landing / file / zip helpers ────────────────────────────────────\n\nasync function renderDirectoryLanding(\n c: Context,\n store: ReturnType<typeof getShareStore>,\n service: GatewayService,\n record: ShareRecord,\n token: string,\n subPath = '',\n): Promise<Response> {\n store.updateConfig(resolveShareConfig(service));\n const urls = {\n tree: (p: string) => (p ? `/s/${token}/tree?path=${encodeURIComponent(p)}` : `/s/${token}`),\n file: (p: string) => `/s/${token}/file?path=${encodeURIComponent(p)}`,\n zip: (p: string) => (p ? `/s/${token}/zip?path=${encodeURIComponent(p)}` : `/s/${token}/zip`),\n };\n\n let listing: import('../../../share/share-store.js').DirectoryListing | null = null;\n if (record.directory?.mode !== 'zip-only') {\n try {\n listing = await store.listDirectory(record, subPath);\n } catch (err) {\n logShareAudit(\n 'share.access_denied',\n { shareId: record.id, tokenPrefix: record.token.slice(0, 8), subPath, reason: String(err) },\n `Directory listing failed`,\n );\n return c.html(renderShareExpiredPage('not_found'), 404);\n }\n }\n const og = buildLandingOg(service, record);\n return c.html(renderFolderLandingPage(record, listing, urls, { og })) as unknown as Response;\n}\n\nfunction buildLandingOg(service: GatewayService, record: ShareRecord) {\n const resolved = resolveShareUrl(record.token, getShareUrlContext(service));\n // Only emit OG tags when the share is genuinely public — otherwise WeChat\n // and friends would silently fall back to \"no preview\" on unreachable URLs.\n if (resolved.reachability !== 'public') return undefined;\n return {\n absoluteShareUrl: resolved.shareUrl,\n absoluteThumbnailUrl: `${resolved.shareUrl}/thumbnail`,\n title: record.fileName,\n description: record.description ?? undefined,\n };\n}\n\nasync function handleDirectoryFile(\n c: Context,\n store: ReturnType<typeof getShareStore>,\n service: GatewayService,\n record: ShareRecord,\n relPath: string,\n clientIp: string,\n inline: boolean,\n): Promise<Response> {\n if (!acquireDownloadSlot(record.token)) {\n return new Response('Too many concurrent downloads for this share', { status: 429 });\n }\n\n try {\n const resolved = await store.resolveDirectoryChild(record, relPath);\n if (resolved.ok !== true) {\n logShareAudit(\n 'share.access_denied',\n { shareId: record.id, tokenPrefix: record.token.slice(0, 8), reason: resolved.reason, clientIp, relPath },\n `Directory child resolution failed: ${resolved.reason}`,\n );\n return c.html(renderShareExpiredPage('not_found'), 404);\n }\n const integrity = await store.validateFileIntegrity(record);\n if (!integrity.valid) {\n return c.html(renderShareExpiredPage((integrity.reason ?? 'file_deleted') as ShareExpiredReason), 410);\n }\n\n const fileStat = await stat(resolved.absolutePath);\n if (!fileStat.isFile()) {\n return c.html(renderShareExpiredPage('not_found'), 404);\n }\n\n // Re-check size against configured max\n store.updateConfig(resolveShareConfig(service));\n const cfg = store.getConfig();\n if (fileStat.size > cfg.maxFileSize) {\n return c.html(renderShareExpiredPage('not_found'), 410);\n }\n\n // Inline preview MIME guard\n const cfgPreview = { ...SHARE_CONFIG_DEFAULTS, ...resolveShareConfig(service) };\n const baseName = relPath.split('/').pop() || record.fileName;\n const { resolveMimeType } = await import('../../../share/share-store.js');\n const mime = resolveMimeType(baseName);\n const useInline = inline && cfgPreview.inlinePreviewMimes.includes(mime);\n\n store.incrementDownloadCount(record.id);\n logShareAudit(\n 'share.access',\n { shareId: record.id, tokenPrefix: record.token.slice(0, 8), clientIp, relPath, mode: useInline ? 'inline' : 'attachment' },\n `Directory file served: ${baseName}`,\n );\n\n const stream = createReadStream(resolved.absolutePath);\n const webStream = Readable.toWeb(stream) as ReadableStream;\n const disposition = rfc5987ContentDisposition(useInline ? 'inline' : 'attachment', baseName);\n\n return new Response(webStream, {\n status: 200,\n headers: {\n 'Content-Type': shareResponseContentType(mime),\n 'Content-Disposition': disposition,\n 'Content-Length': String(fileStat.size),\n 'Cache-Control': 'private, no-store',\n 'X-Content-Type-Options': 'nosniff',\n 'X-Frame-Options': 'DENY',\n 'Referrer-Policy': 'no-referrer',\n },\n });\n } finally {\n releaseDownloadSlot(record.token);\n }\n}\n\nasync function handleDirectoryZip(\n c: Context,\n store: ReturnType<typeof getShareStore>,\n service: GatewayService,\n record: ShareRecord,\n subPath: string,\n clientIp: string,\n): Promise<Response> {\n store.updateConfig(resolveShareConfig(service));\n const cfg = store.getConfig();\n const zipLimit = cfg.directory.zipConcurrency;\n if (!acquireZipSlot(record.token, zipLimit)) {\n return new Response('Too many concurrent ZIP streams for this share', { status: 429 });\n }\n\n try {\n const resolved = await store.resolveDirectoryChild(record, subPath);\n if (resolved.ok !== true) {\n return c.html(renderShareExpiredPage('not_found'), 404);\n }\n const integrity = await store.validateFileIntegrity(record);\n if (!integrity.valid) {\n return c.html(renderShareExpiredPage((integrity.reason ?? 'file_deleted') as ShareExpiredReason), 410);\n }\n\n const files = await planDirectoryFiles(record, {\n rootRelativePath: subPath,\n maxFileCount: cfg.directory.maxFileCount,\n maxFolderSize: cfg.directory.maxFolderSize,\n followSymlinks: record.directory?.followSymlinks ?? false,\n maxDepth: record.directory?.maxDepth ?? cfg.directory.maxDepth,\n });\n\n store.incrementDownloadCount(record.id);\n logShareAudit(\n 'share.access',\n { shareId: record.id, tokenPrefix: record.token.slice(0, 8), clientIp, mode: 'zip', subPath, fileCount: files.length },\n `Directory zip served: ${record.fileName}${subPath ? '/' + subPath : ''}`,\n );\n\n const zipName = subPath\n ? `${record.fileName}-${subPath.replace(/[\\\\/]/g, '_')}.zip`\n : `${record.fileName}.zip`;\n const stream = createZipStream({ files });\n const webStream = Readable.toWeb(stream) as ReadableStream;\n\n return new Response(webStream, {\n status: 200,\n headers: {\n 'Content-Type': 'application/zip',\n 'Content-Disposition': rfc5987ContentDisposition('attachment', zipName),\n 'Cache-Control': 'private, no-store',\n 'X-Content-Type-Options': 'nosniff',\n 'X-Frame-Options': 'DENY',\n 'Referrer-Policy': 'no-referrer',\n },\n });\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n return new Response(`zip build failed: ${message}`, { status: 500 });\n } finally {\n releaseZipSlot(record.token);\n }\n}\n\n// ── Helpers ───────────────────────────────────────────────────────────────────\n\nasync function createFileShareResponse(args: {\n c: Context;\n service: GatewayService;\n store: ReturnType<typeof getShareStore>;\n probe: Awaited<ReturnType<typeof probeShareTarget>>;\n decision: ReturnType<typeof decideShareKind>;\n ttlMs: number;\n maxViews: number | null | undefined;\n title?: string;\n description?: string;\n audience?: ShareAudience;\n workspaceRoot: string;\n tokenHash: string;\n urlCtx: ReturnType<typeof getShareUrlContext>;\n wantThumbnail: boolean;\n}): Promise<Response> {\n const { c, service, store, probe, decision, ttlMs, maxViews, title, description, audience, workspaceRoot, tokenHash, urlCtx, wantThumbnail } = args;\n store.updateConfig(resolveShareConfig(service));\n const record = await store.create({\n path: relPathFromAbs(workspaceRoot, probe.absolutePath),\n workspaceRoot,\n gatewayTokenHash: tokenHash,\n ttlMs,\n maxViews: maxViews === undefined ? undefined : maxViews,\n description,\n kind: probe.kind === 'directory' ? 'directory' : 'file',\n directoryMode: decision.kind === 'zip' ? 'zip-only' : (probe.kind === 'directory' ? 'browse' : undefined),\n });\n const resolved = resolveShareUrl(record.token, urlCtx);\n const titleOut = makeTitle(record.fileName, title);\n const descOut = makeDescription({ audience, expiresAt: record.expiresAt, override: description });\n const thumbnailUrl = wantThumbnail\n ? `${resolved.shareUrl}/thumbnail`\n : '';\n if (wantThumbnail) {\n scheduleThumbnail({ scope: 'file', token: record.token, recordId: record.id }, thumbnailRenderContext(service));\n store.setThumbnailStatus(record.id, 'pending');\n }\n return c.json({\n ok: true,\n payload: {\n share: {\n id: record.id,\n kind: decision.kind,\n title: titleOut,\n description: descOut,\n shareUrl: resolved.shareUrl,\n lanUrl: resolved.lanUrl,\n reachability: resolved.reachability,\n reachabilityHint: resolved.reachabilityHint,\n expiresAt: record.expiresAt,\n maxViews: record.maxViews,\n },\n thumbnail: {\n url: thumbnailUrl,\n status: wantThumbnail ? 'pending' : 'unavailable',\n width: SHARE_CONFIG_DEFAULTS.thumbnail.viewportWidth,\n height: SHARE_CONFIG_DEFAULTS.thumbnail.viewportHeight,\n },\n routing: { reason: decision.reason, hint: decision.hint },\n },\n }, 201) as unknown as Response;\n}\n\nfunction relPathFromAbs(workspaceRoot: string, abs: string): string {\n const root = workspaceRoot.replace(/[\\\\/]+$/, '');\n if (abs === root) return '';\n if (abs.startsWith(`${root}/`)) return abs.slice(root.length + 1);\n if (abs.startsWith(`${root}\\\\`)) return abs.slice(root.length + 1).replace(/\\\\/g, '/');\n // Fall back to basename — store.create will resolve again under workspace.\n return abs.split(/[\\\\/]/).pop() ?? abs;\n}\n\nasync function resolveWorkspaceRootForShare(\n service: GatewayService,\n sessionKey: string | undefined,\n agentId: string | undefined,\n): Promise<string | null> {\n const cfg = service.currentConfig;\n\n if (sessionKey) {\n try {\n return await service.sessions.getEffectiveWorkspacePath(sessionKey);\n } catch {\n /* fall through to agentId */\n }\n }\n\n const { getWorkspacePath } = await import('../../../config/workspace-path-helpers.js');\n const { resolveAgentWorkspaceDir, normalizeAgentId, resolveDefaultAgentId } = await import(\n '../../../agent/agent-scope.js'\n );\n\n if (agentId) {\n const normalized = normalizeAgentId(agentId);\n return resolveAgentWorkspaceDir(cfg, normalized);\n }\n\n const root = getWorkspacePath(cfg);\n if (root) return root;\n\n const defaultId = resolveDefaultAgentId(cfg);\n return resolveAgentWorkspaceDir(cfg, defaultId);\n}\n\nasync function handleFileDownload(\n c: Context,\n store: ReturnType<typeof getShareStore>,\n record: ShareRecord,\n clientIp: string,\n inline = false,\n): Promise<Response> {\n if (!acquireDownloadSlot(record.token)) {\n return new Response('Too many concurrent downloads for this share', { status: 429 });\n }\n\n try {\n const integrity = await store.validateFileIntegrity(record);\n if (!integrity.valid) {\n logShareAudit(\n 'share.access_denied',\n { shareId: record.id, tokenPrefix: record.token.slice(0, 8), reason: integrity.reason, clientIp },\n `Share file integrity check failed: ${integrity.reason}`,\n );\n return new Response(renderShareExpiredPage((integrity.reason ?? 'file_deleted') as ShareExpiredReason), {\n status: 410,\n headers: { 'Content-Type': 'text/html; charset=utf-8' },\n });\n }\n\n store.incrementDownloadCount(record.id);\n\n logShareAudit(\n 'share.access',\n { shareId: record.id, tokenPrefix: record.token.slice(0, 8), clientIp, downloadCount: record.downloadCount },\n `Share downloaded: ${record.fileName}`,\n );\n\n const fileStat = await stat(record.absolutePath);\n const stream = createReadStream(record.absolutePath);\n const webStream = Readable.toWeb(stream) as ReadableStream;\n const disposition = rfc5987ContentDisposition(inline ? 'inline' : 'attachment', record.fileName);\n\n return new Response(webStream, {\n status: 200,\n headers: {\n 'Content-Type': shareResponseContentType(record.mimeType),\n 'Content-Disposition': disposition,\n 'Content-Length': String(fileStat.size),\n 'Cache-Control': 'private, no-store',\n 'X-Content-Type-Options': 'nosniff',\n 'X-Frame-Options': 'DENY',\n 'Referrer-Policy': 'no-referrer',\n },\n });\n } finally {\n releaseDownloadSlot(record.token);\n }\n}\n"],"mappings":"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"}
@@ -1,6 +1,7 @@
1
1
  import { init_public_url, validatePublicUrl } from "../../../config/public-url.js";
2
2
  import { resolveGatewayEffectiveHost } from "../../../config/gateway-bind.js";
3
3
  import { loadTunnelState } from "../../../tunnel/tunnel-state.js";
4
+ import { resolveReverseProxyPublicUrl } from "../../public-url.js";
4
5
  import { extractToken } from "../../auth.js";
5
6
  import { TUNNEL_CONSENT_REQUIRED_CODE, TunnelConsentError, assertTunnelMayStart, getTunnelConsentState } from "../../../tunnel/consent.js";
6
7
  import { getTunnelRegistrationSecretMeta, readTunnelRegistrationSecretFromConfigOnly, resolveTunnelBrokerUrl } from "../../../tunnel/env.js";
@@ -10,7 +11,6 @@ import { getTunnelService, hashGatewayToken } from "../../../tunnel/tunnel-servi
10
11
  import { configureTunnelFromGatewayConfig } from "../../../tunnel/gateway-lifecycle.js";
11
12
  import { applyTunnelConsentToConfig, setTunnelEnabledInConfig } from "../../../tunnel/tunnel-config.js";
12
13
  import { getClientIpFromHeaders } from "../../security/loopback.js";
13
- import { resolveReverseProxyPublicUrl } from "../../public-url.js";
14
14
  import { applyLanPairingGatewayPatch } from "../../../tunnel/enable-lan-pairing.js";
15
15
  import { consumeTunnelMutationLimit } from "../../../tunnel/tunnel-rate-limit.js";
16
16
  import "../../../tunnel/index.js";
@@ -120,6 +120,7 @@ function registerUpdateRoutes(authenticated, deps) {
120
120
  message: String(parsed.message ?? "Git checkout — use git pull instead.")
121
121
  }, 400);
122
122
  if (!result.ok) {
123
+ const installMessage = typeof parsed?.message === "string" ? parsed.message : typeof parsed?.stderrTail === "string" ? parsed.stderrTail : void 0;
123
124
  log.warn({
124
125
  channel,
125
126
  exitCode: result.exitCode,
@@ -128,7 +129,7 @@ function registerUpdateRoutes(authenticated, deps) {
128
129
  return c.json({
129
130
  ok: false,
130
131
  error: "update-failed",
131
- message: result.stderr?.trim() || result.reason || `Update exited with code ${result.exitCode ?? "unknown"}`,
132
+ message: installMessage || result.stderr?.trim() || result.reason || `Update exited with code ${result.exitCode ?? "unknown"}`,
132
133
  result: parsed
133
134
  });
134
135
  }
@@ -199,12 +200,13 @@ function registerUpdateRoutes(authenticated, deps) {
199
200
  return;
200
201
  }
201
202
  if (!result.ok) {
203
+ const installMessage = typeof parsed?.message === "string" ? parsed.message : typeof parsed?.stderrTail === "string" ? parsed.stderrTail : void 0;
202
204
  await stream.writeSSE({
203
205
  event: "result",
204
206
  data: JSON.stringify({
205
207
  ok: false,
206
208
  error: "update-failed",
207
- message: result.stderr?.trim() || result.reason || `Update exited with code ${result.exitCode ?? "unknown"}`,
209
+ message: installMessage || result.stderr?.trim() || result.reason || `Update exited with code ${result.exitCode ?? "unknown"}`,
208
210
  result: parsed,
209
211
  exitCode: result.exitCode,
210
212
  reason: result.reason
@@ -1 +1 @@
1
- {"version":3,"file":"update.js","names":[],"sources":["../../../../../src/gateway/hono/routes/update.ts"],"sourcesContent":["import type { Hono } from 'hono';\nimport { streamSSE } from 'hono/streaming';\n\nimport { loadConfig } from '../../../config/index.js';\nimport { acquireUpdateLock } from '../../../infra/update-lock.js';\nimport { detectInstallKind, resolvePackageRoot } from '../../../infra/update-check.js';\nimport {\n DEFAULT_PACKAGE_CHANNEL,\n normalizeUpdateChannel,\n type UpdateChannel,\n} from '../../../infra/update-channels.js';\nimport { runAutoUpdateCommand, runAutoUpdateCommandWithProgress } from '../../../infra/update-runner.js';\nimport { getUpdateAvailable, runGatewayUpdateCheck } from '../../../infra/update-startup.js';\nimport { PACKAGE_VERSION } from '../../../package-version.js';\nimport { createLogger } from '../../../utils/logger.js';\nimport type { AuthenticatedRouteDeps } from './deps.js';\n\nconst log = createLogger('GatewayUpdate');\n\nfunction parseUpdateCliJson(stdout: string): Record<string, unknown> | null {\n const t = stdout.trim();\n if (!t) return null;\n try {\n const parsed = JSON.parse(t) as unknown;\n return parsed && typeof parsed === 'object' && !Array.isArray(parsed)\n ? (parsed as Record<string, unknown>)\n : null;\n } catch {\n const lines = t.split('\\n').filter(Boolean);\n for (let i = lines.length - 1; i >= 0; i--) {\n const line = lines[i]!.trim();\n if (!line.startsWith('{')) continue;\n try {\n const parsed = JSON.parse(line) as unknown;\n if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {\n return parsed as Record<string, unknown>;\n }\n } catch {\n // try previous line\n }\n }\n }\n return null;\n}\n\ntype PreconditionOk = {\n ok: true;\n channel: UpdateChannel;\n root: string | null;\n};\n\nfunction isPreconditionFail(\n x: PreconditionOk | { ok: false; status: 400; body: Record<string, unknown> },\n): x is { ok: false; status: 400; body: Record<string, unknown> } {\n return !x.ok;\n}\n\nasync function npmUpdatePreconditions(\n service: AuthenticatedRouteDeps['service'],\n): Promise<PreconditionOk | { ok: false; status: 400; body: Record<string, unknown> }> {\n const config = loadConfig(service.getHealth().configPath);\n const channel = normalizeUpdateChannel(config.update?.channel) ?? DEFAULT_PACKAGE_CHANNEL;\n\n const root = await resolvePackageRoot();\n if (root) {\n const kind = await detectInstallKind(root);\n if (kind === 'git') {\n return {\n ok: false,\n status: 400,\n body: {\n ok: false,\n error: 'git-checkout',\n message:\n 'Running from a git checkout. Use `git pull` in the repo, or install from npm to use one-click update.',\n },\n };\n }\n }\n\n return { ok: true, channel, root };\n}\n\nexport function registerUpdateRoutes(authenticated: Hono, deps: AuthenticatedRouteDeps): void {\n const { strictRateLimitMiddleware, service } = deps;\n\n /**\n * GET /api/update/status\n */\n authenticated.get('/api/update/status', (c) => {\n const update = getUpdateAvailable();\n return c.json({\n ok: true,\n payload: {\n currentVersion: PACKAGE_VERSION,\n updateAvailable: update !== null,\n latestVersion: update?.latestVersion ?? null,\n channel: update?.channel ?? null,\n },\n });\n });\n\n /**\n * POST /api/update/check\n */\n authenticated.post('/api/update/check', strictRateLimitMiddleware, async (c) => {\n const config = loadConfig(service.getHealth().configPath);\n await runGatewayUpdateCheck({\n config,\n force: true,\n onUpdateAvailableChange: (update) => {\n service.emit('update.available', update);\n },\n });\n const result = getUpdateAvailable();\n return c.json({\n ok: true,\n payload: {\n currentVersion: PACKAGE_VERSION,\n updateAvailable: result !== null,\n latestVersion: result?.latestVersion ?? null,\n channel: result?.channel ?? null,\n },\n });\n });\n\n /**\n * POST /api/update/run — one-click npm install (OpenClaw-style). Rejects git checkouts.\n */\n authenticated.post('/api/update/run', strictRateLimitMiddleware, async (c) => {\n const pre = await npmUpdatePreconditions(service);\n if (isPreconditionFail(pre)) {\n return c.json(pre.body, pre.status);\n }\n\n const lock = await acquireUpdateLock('gateway');\n if (!lock) {\n return c.json(\n {\n ok: false,\n error: 'busy',\n message: 'Another update is already in progress.',\n },\n 409,\n );\n }\n\n const { channel, root } = pre;\n try {\n log.info({ channel }, 'Gateway: starting one-click npm update');\n const result = await runAutoUpdateCommand({ channel, root });\n const parsed = parseUpdateCliJson(result.stdout ?? '');\n\n if (result.ok && parsed?.status === 'skipped' && parsed?.reason === 'git-checkout') {\n return c.json(\n {\n ok: false,\n error: 'git-checkout',\n message: String(parsed.message ?? 'Git checkout — use git pull instead.'),\n },\n 400,\n );\n }\n\n if (!result.ok) {\n log.warn(\n { channel, exitCode: result.exitCode, reason: result.reason },\n 'Gateway: one-click npm update failed',\n );\n return c.json({\n ok: false,\n error: 'update-failed',\n message: result.stderr?.trim() || result.reason || `Update exited with code ${result.exitCode ?? 'unknown'}`,\n result: parsed,\n });\n }\n\n log.info({ channel }, 'Gateway: one-click npm update finished');\n return c.json({ ok: true, result: parsed });\n } catch (err) {\n log.error({ err, channel }, 'Gateway: one-click npm update threw');\n return c.json(\n {\n ok: false,\n error: 'internal',\n message: err instanceof Error ? err.message : String(err),\n },\n 500,\n );\n } finally {\n await lock.release();\n }\n });\n\n /**\n * POST /api/update/run/stream — SSE-streamed npm update with progress lines.\n */\n authenticated.post('/api/update/run/stream', strictRateLimitMiddleware, async (c) => {\n const pre = await npmUpdatePreconditions(service);\n if (isPreconditionFail(pre)) {\n return c.json(pre.body, pre.status);\n }\n\n const { channel, root } = pre;\n\n return streamSSE(c, async (stream) => {\n const lock = await acquireUpdateLock('gateway');\n if (!lock) {\n await stream.writeSSE({\n event: 'result',\n data: JSON.stringify({\n ok: false,\n error: 'busy',\n message: 'Another update is already in progress.',\n }),\n });\n return;\n }\n\n try {\n log.info({ channel }, 'Gateway: starting streamed one-click npm update');\n const result = await runAutoUpdateCommandWithProgress({\n channel,\n root,\n onProgress: async (line, source) => {\n await stream.writeSSE({\n event: 'progress',\n data: JSON.stringify({ line, source }),\n });\n },\n });\n\n const parsed = parseUpdateCliJson(result.stdout ?? '');\n\n if (result.ok && parsed?.status === 'skipped' && parsed?.reason === 'git-checkout') {\n await stream.writeSSE({\n event: 'result',\n data: JSON.stringify({\n ok: false,\n error: 'git-checkout',\n message: String(parsed.message ?? 'Git checkout — use git pull instead.'),\n }),\n });\n return;\n }\n\n if (!result.ok) {\n await stream.writeSSE({\n event: 'result',\n data: JSON.stringify({\n ok: false,\n error: 'update-failed',\n message:\n result.stderr?.trim() || result.reason || `Update exited with code ${result.exitCode ?? 'unknown'}`,\n result: parsed,\n exitCode: result.exitCode,\n reason: result.reason,\n }),\n });\n return;\n }\n\n await stream.writeSSE({\n event: 'result',\n data: JSON.stringify({ ok: true, result: parsed }),\n });\n } catch (err) {\n log.error({ err, channel }, 'Gateway: streamed npm update threw');\n await stream.writeSSE({\n event: 'result',\n data: JSON.stringify({\n ok: false,\n error: 'internal',\n message: err instanceof Error ? err.message : String(err),\n }),\n });\n } finally {\n await lock.release();\n }\n });\n });\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"update.js","names":[],"sources":["../../../../../src/gateway/hono/routes/update.ts"],"sourcesContent":["import type { Hono } from 'hono';\nimport { streamSSE } from 'hono/streaming';\n\nimport { loadConfig } from '../../../config/index.js';\nimport { acquireUpdateLock } from '../../../infra/update-lock.js';\nimport { detectInstallKind, resolvePackageRoot } from '../../../infra/update-check.js';\nimport {\n DEFAULT_PACKAGE_CHANNEL,\n normalizeUpdateChannel,\n type UpdateChannel,\n} from '../../../infra/update-channels.js';\nimport { runAutoUpdateCommand, runAutoUpdateCommandWithProgress } from '../../../infra/update-runner.js';\nimport { getUpdateAvailable, runGatewayUpdateCheck } from '../../../infra/update-startup.js';\nimport { PACKAGE_VERSION } from '../../../package-version.js';\nimport { createLogger } from '../../../utils/logger.js';\nimport type { AuthenticatedRouteDeps } from './deps.js';\n\nconst log = createLogger('GatewayUpdate');\n\nfunction parseUpdateCliJson(stdout: string): Record<string, unknown> | null {\n const t = stdout.trim();\n if (!t) return null;\n try {\n const parsed = JSON.parse(t) as unknown;\n return parsed && typeof parsed === 'object' && !Array.isArray(parsed)\n ? (parsed as Record<string, unknown>)\n : null;\n } catch {\n const lines = t.split('\\n').filter(Boolean);\n for (let i = lines.length - 1; i >= 0; i--) {\n const line = lines[i]!.trim();\n if (!line.startsWith('{')) continue;\n try {\n const parsed = JSON.parse(line) as unknown;\n if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {\n return parsed as Record<string, unknown>;\n }\n } catch {\n // try previous line\n }\n }\n }\n return null;\n}\n\ntype PreconditionOk = {\n ok: true;\n channel: UpdateChannel;\n root: string | null;\n};\n\nfunction isPreconditionFail(\n x: PreconditionOk | { ok: false; status: 400; body: Record<string, unknown> },\n): x is { ok: false; status: 400; body: Record<string, unknown> } {\n return !x.ok;\n}\n\nasync function npmUpdatePreconditions(\n service: AuthenticatedRouteDeps['service'],\n): Promise<PreconditionOk | { ok: false; status: 400; body: Record<string, unknown> }> {\n const config = loadConfig(service.getHealth().configPath);\n const channel = normalizeUpdateChannel(config.update?.channel) ?? DEFAULT_PACKAGE_CHANNEL;\n\n const root = await resolvePackageRoot();\n if (root) {\n const kind = await detectInstallKind(root);\n if (kind === 'git') {\n return {\n ok: false,\n status: 400,\n body: {\n ok: false,\n error: 'git-checkout',\n message:\n 'Running from a git checkout. Use `git pull` in the repo, or install from npm to use one-click update.',\n },\n };\n }\n }\n\n return { ok: true, channel, root };\n}\n\nexport function registerUpdateRoutes(authenticated: Hono, deps: AuthenticatedRouteDeps): void {\n const { strictRateLimitMiddleware, service } = deps;\n\n /**\n * GET /api/update/status\n */\n authenticated.get('/api/update/status', (c) => {\n const update = getUpdateAvailable();\n return c.json({\n ok: true,\n payload: {\n currentVersion: PACKAGE_VERSION,\n updateAvailable: update !== null,\n latestVersion: update?.latestVersion ?? null,\n channel: update?.channel ?? null,\n },\n });\n });\n\n /**\n * POST /api/update/check\n */\n authenticated.post('/api/update/check', strictRateLimitMiddleware, async (c) => {\n const config = loadConfig(service.getHealth().configPath);\n await runGatewayUpdateCheck({\n config,\n force: true,\n onUpdateAvailableChange: (update) => {\n service.emit('update.available', update);\n },\n });\n const result = getUpdateAvailable();\n return c.json({\n ok: true,\n payload: {\n currentVersion: PACKAGE_VERSION,\n updateAvailable: result !== null,\n latestVersion: result?.latestVersion ?? null,\n channel: result?.channel ?? null,\n },\n });\n });\n\n /**\n * POST /api/update/run — one-click npm install (OpenClaw-style). Rejects git checkouts.\n */\n authenticated.post('/api/update/run', strictRateLimitMiddleware, async (c) => {\n const pre = await npmUpdatePreconditions(service);\n if (isPreconditionFail(pre)) {\n return c.json(pre.body, pre.status);\n }\n\n const lock = await acquireUpdateLock('gateway');\n if (!lock) {\n return c.json(\n {\n ok: false,\n error: 'busy',\n message: 'Another update is already in progress.',\n },\n 409,\n );\n }\n\n const { channel, root } = pre;\n try {\n log.info({ channel }, 'Gateway: starting one-click npm update');\n const result = await runAutoUpdateCommand({ channel, root });\n const parsed = parseUpdateCliJson(result.stdout ?? '');\n\n if (result.ok && parsed?.status === 'skipped' && parsed?.reason === 'git-checkout') {\n return c.json(\n {\n ok: false,\n error: 'git-checkout',\n message: String(parsed.message ?? 'Git checkout — use git pull instead.'),\n },\n 400,\n );\n }\n\n if (!result.ok) {\n const installMessage =\n typeof parsed?.message === 'string'\n ? parsed.message\n : typeof parsed?.stderrTail === 'string'\n ? parsed.stderrTail\n : undefined;\n log.warn(\n { channel, exitCode: result.exitCode, reason: result.reason },\n 'Gateway: one-click npm update failed',\n );\n return c.json({\n ok: false,\n error: 'update-failed',\n message:\n installMessage ||\n result.stderr?.trim() ||\n result.reason ||\n `Update exited with code ${result.exitCode ?? 'unknown'}`,\n result: parsed,\n });\n }\n\n log.info({ channel }, 'Gateway: one-click npm update finished');\n return c.json({ ok: true, result: parsed });\n } catch (err) {\n log.error({ err, channel }, 'Gateway: one-click npm update threw');\n return c.json(\n {\n ok: false,\n error: 'internal',\n message: err instanceof Error ? err.message : String(err),\n },\n 500,\n );\n } finally {\n await lock.release();\n }\n });\n\n /**\n * POST /api/update/run/stream — SSE-streamed npm update with progress lines.\n */\n authenticated.post('/api/update/run/stream', strictRateLimitMiddleware, async (c) => {\n const pre = await npmUpdatePreconditions(service);\n if (isPreconditionFail(pre)) {\n return c.json(pre.body, pre.status);\n }\n\n const { channel, root } = pre;\n\n return streamSSE(c, async (stream) => {\n const lock = await acquireUpdateLock('gateway');\n if (!lock) {\n await stream.writeSSE({\n event: 'result',\n data: JSON.stringify({\n ok: false,\n error: 'busy',\n message: 'Another update is already in progress.',\n }),\n });\n return;\n }\n\n try {\n log.info({ channel }, 'Gateway: starting streamed one-click npm update');\n const result = await runAutoUpdateCommandWithProgress({\n channel,\n root,\n onProgress: async (line, source) => {\n await stream.writeSSE({\n event: 'progress',\n data: JSON.stringify({ line, source }),\n });\n },\n });\n\n const parsed = parseUpdateCliJson(result.stdout ?? '');\n\n if (result.ok && parsed?.status === 'skipped' && parsed?.reason === 'git-checkout') {\n await stream.writeSSE({\n event: 'result',\n data: JSON.stringify({\n ok: false,\n error: 'git-checkout',\n message: String(parsed.message ?? 'Git checkout — use git pull instead.'),\n }),\n });\n return;\n }\n\n if (!result.ok) {\n const installMessage =\n typeof parsed?.message === 'string'\n ? parsed.message\n : typeof parsed?.stderrTail === 'string'\n ? parsed.stderrTail\n : undefined;\n await stream.writeSSE({\n event: 'result',\n data: JSON.stringify({\n ok: false,\n error: 'update-failed',\n message:\n installMessage ||\n result.stderr?.trim() ||\n result.reason ||\n `Update exited with code ${result.exitCode ?? 'unknown'}`,\n result: parsed,\n exitCode: result.exitCode,\n reason: result.reason,\n }),\n });\n return;\n }\n\n await stream.writeSSE({\n event: 'result',\n data: JSON.stringify({ ok: true, result: parsed }),\n });\n } catch (err) {\n log.error({ err, channel }, 'Gateway: streamed npm update threw');\n await stream.writeSSE({\n event: 'result',\n data: JSON.stringify({\n ok: false,\n error: 'internal',\n message: err instanceof Error ? err.message : String(err),\n }),\n });\n } finally {\n await lock.release();\n }\n });\n });\n}\n"],"mappings":"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"}
@@ -1,16 +1,12 @@
1
- import { buildSessionKey, init_session_key, parseSessionKey } from "../../routing/session-key.js";
2
- import { getDefaultAgentId, init_resolve_route } from "../../routing/resolve-route.js";
3
1
  import { updateAsyncLogContext } from "../../utils/logger/context.js";
4
2
  import { createLogger } from "../../utils/logger/index.js";
5
3
  import { init_logger } from "../../utils/logger.js";
6
4
  import { MAX_WEBCHAT_ATTACHMENT_FILE_BYTES } from "../chat-limits.js";
5
+ import { resolveWebchatSessionKey } from "../resolve-webchat-session-key.js";
7
6
  import { stringifySSEData } from "./sse-json.js";
8
- import { randomUUID } from "node:crypto";
9
7
  import { streamSSE } from "hono/streaming";
10
8
  //#region src/gateway/hono/sse.ts
11
9
  init_logger();
12
- init_session_key();
13
- init_resolve_route();
14
10
  const log = createLogger("Hono:SSE");
15
11
  const activeConnections = /* @__PURE__ */ new Map();
16
12
  function isValidAgentRequest(body) {
@@ -53,24 +49,21 @@ function createAgentSSEHandler(config) {
53
49
  const { message, channel = "webchat", attachments, thinking } = body;
54
50
  const clientCreatedAtMs = typeof body.clientCreatedAtMs === "number" && Number.isFinite(body.clientCreatedAtMs) ? body.clientCreatedAtMs : void 0;
55
51
  const newSession = Boolean(body.newSession);
56
- let chatId = "default";
57
- if (newSession && channel === "webchat") chatId = `chat_${randomUUID()}`;
58
- else {
59
- const sk = typeof body.sessionKey === "string" && body.sessionKey.trim() ? body.sessionKey.trim() : "";
60
- const cid = typeof body.chatId === "string" && body.chatId.trim() ? body.chatId.trim() : "";
61
- const rawChatId = sk || cid || "default";
62
- if (rawChatId !== "default" && !/^[a-zA-Z0-9][a-zA-Z0-9._:@\-]{0,255}$/.test(rawChatId)) {
63
- log.warn({ rawChatId: rawChatId.slice(0, 64) }, "Rejected invalid chatId format");
64
- return c.json({
65
- ok: false,
66
- error: {
67
- code: "BAD_REQUEST",
68
- message: "Invalid session key format"
69
- }
70
- }, 400);
52
+ const cfg = service.currentConfig;
53
+ const resolved = resolveWebchatSessionKey({
54
+ cfg,
55
+ sessionKey: typeof body.sessionKey === "string" ? body.sessionKey : void 0,
56
+ chatId: typeof body.chatId === "string" ? body.chatId : void 0,
57
+ newSession
58
+ });
59
+ if (resolved.ok === false) return c.json({
60
+ ok: false,
61
+ error: {
62
+ code: "BAD_REQUEST",
63
+ message: resolved.error
71
64
  }
72
- chatId = rawChatId;
73
- }
65
+ }, 400);
66
+ const chatId = resolved.sessionKey;
74
67
  updateAsyncLogContext({ sessionId: String(chatId) });
75
68
  if (Array.isArray(attachments)) {
76
69
  const maxDataChars = maxBase64CharsForBinary(MAX_WEBCHAT_ATTACHMENT_FILE_BYTES);
@@ -92,17 +85,7 @@ function createAgentSSEHandler(config) {
92
85
  if (channel !== "webchat") if (raw.signal.aborted) clientAbort.abort();
93
86
  else raw.signal.addEventListener("abort", () => clientAbort.abort(), { once: true });
94
87
  if (!wantSSE) {
95
- let jsonSessionKey;
96
- if (channel === "webchat") {
97
- const cfg = service.currentConfig;
98
- jsonSessionKey = parseSessionKey(chatId) ? chatId : buildSessionKey({
99
- agentId: getDefaultAgentId(cfg),
100
- source: "webchat",
101
- accountId: "default",
102
- peerKind: "direct",
103
- peerId: chatId
104
- });
105
- }
88
+ const jsonSessionKey = channel === "webchat" ? chatId : void 0;
106
89
  const generator = service.runAgent(message, channel, chatId, attachments, thinking, {
107
90
  signal: clientAbort.signal,
108
91
  ...clientCreatedAtMs !== void 0 ? { clientCreatedAtMs } : {}