@xopcai/xopc 0.0.84 → 0.0.86

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 (418) hide show
  1. package/dist/browser-ext/manifest.json +1 -1
  2. package/dist/extensions/feishu/src/outbound/media-load.js +1 -1
  3. package/dist/extensions/feishu/src/plugin.d.ts +2 -0
  4. package/dist/extensions/feishu/src/plugin.js +10 -0
  5. package/dist/extensions/feishu/src/plugin.js.map +1 -1
  6. package/dist/extensions/feishu/src/workflow-progress.d.ts +27 -0
  7. package/dist/extensions/feishu/src/workflow-progress.js +99 -0
  8. package/dist/extensions/feishu/src/workflow-progress.js.map +1 -0
  9. package/dist/extensions/telegram/src/plugin.d.ts +2 -0
  10. package/dist/extensions/telegram/src/plugin.js +11 -1
  11. package/dist/extensions/telegram/src/plugin.js.map +1 -1
  12. package/dist/extensions/telegram/src/routing-integration.js +2 -2
  13. package/dist/extensions/telegram/src/workflow-progress.d.ts +24 -0
  14. package/dist/extensions/telegram/src/workflow-progress.js +73 -0
  15. package/dist/extensions/telegram/src/workflow-progress.js.map +1 -0
  16. package/dist/extensions/telegram/xopc.extension.json +1 -1
  17. package/dist/extensions/weixin/src/__tests__/workflow-progress.test.js +158 -0
  18. package/dist/extensions/weixin/src/__tests__/workflow-progress.test.js.map +1 -0
  19. package/dist/extensions/weixin/src/api/api.js +2 -2
  20. package/dist/extensions/weixin/src/auth/accounts.js +1 -1
  21. package/dist/extensions/weixin/src/cdn/upload.js +1 -1
  22. package/dist/extensions/weixin/src/media/data-url.js +1 -1
  23. package/dist/extensions/weixin/src/messaging/debug-mode.js +1 -1
  24. package/dist/extensions/weixin/src/messaging/inbound.js +1 -1
  25. package/dist/extensions/weixin/src/messaging/process-message.js +1 -1
  26. package/dist/extensions/weixin/src/plugin.d.ts +2 -0
  27. package/dist/extensions/weixin/src/plugin.js +11 -1
  28. package/dist/extensions/weixin/src/plugin.js.map +1 -1
  29. package/dist/extensions/weixin/src/storage/sync-buf.js +1 -1
  30. package/dist/extensions/weixin/src/workflow-progress.d.ts +26 -0
  31. package/dist/extensions/weixin/src/workflow-progress.js +99 -0
  32. package/dist/extensions/weixin/src/workflow-progress.js.map +1 -0
  33. package/dist/gateway/static/root/assets/agents-mS3_HpRI.js +222 -0
  34. package/dist/gateway/static/root/assets/apps-page-DrfytjOb.js +1 -0
  35. package/dist/gateway/static/root/assets/channels-settings-BG6b9KrW.js +1 -0
  36. package/dist/gateway/static/root/assets/channels-status-swr-Bs5kMCMI.js +8 -0
  37. package/dist/gateway/static/root/assets/createLucideIcon-DPHK1VkS.js +1 -0
  38. package/dist/gateway/static/root/assets/cron-api-BuVcZ5zR.js +1 -0
  39. package/dist/gateway/static/root/assets/cron-page-BMrloeFH.js +1 -0
  40. package/dist/gateway/static/root/assets/dist-BTWC-BTN.js +45 -0
  41. package/dist/gateway/static/root/assets/{dist-CqNMNhJM.js → dist-CKU1OOTf.js} +1 -1
  42. package/dist/gateway/static/root/assets/{extension-debug-page-gf2L0kY_.js → extension-debug-page-BdW_46sN.js} +1 -1
  43. package/dist/gateway/static/root/assets/extension-page-DW47KI82.js +1 -0
  44. package/dist/gateway/static/root/assets/extension-settings-page-B-W4x2xP.js +1 -0
  45. package/dist/gateway/static/root/assets/fetch-B2MYHbWg.js +1 -0
  46. package/dist/gateway/static/root/assets/{field-primitives-DTtlp-l8.js → field-primitives-DPG-oJmx.js} +1 -1
  47. package/dist/gateway/static/root/assets/heartbeat-config-api-C8dNts9i.js +1 -0
  48. package/dist/gateway/static/root/assets/index-BmVYculr.js +4700 -0
  49. package/dist/gateway/static/root/assets/index-ew_2L2We.css +1 -0
  50. package/dist/gateway/static/root/assets/logs-page-sTsVWz0X.js +1 -0
  51. package/dist/gateway/static/root/assets/sessions-page-FaG_Vlkb.js +1 -0
  52. package/dist/gateway/static/root/assets/settings-form-section-DuvRQW--.js +1 -0
  53. package/dist/gateway/static/root/assets/settings-page-Bet1OerL.js +3 -0
  54. package/dist/gateway/static/root/assets/share-preview-page-BtG2kLDh.js +2 -0
  55. package/dist/gateway/static/root/assets/skills-page-DhUO235y.js +2 -0
  56. package/dist/gateway/static/root/assets/theme-store-DryYl3qD.js +1 -0
  57. package/dist/gateway/static/root/assets/url-BwNL6Rgk.js +3 -0
  58. package/dist/gateway/static/root/assets/utils-BY7bU1DT.js +1 -0
  59. package/dist/gateway/static/root/assets/voice-api-key-field-CGEydndO.js +1 -0
  60. package/dist/gateway/static/root/index.html +7 -6
  61. package/dist/package.js +1 -1
  62. package/dist/src/agent/agent-manager.js +7 -7
  63. package/dist/src/agent/bootstrap/load-bootstrap-files.js +1 -1
  64. package/dist/src/agent/context/workspace-seed.js +3 -3
  65. package/dist/src/agent/embedded/map-stream-events.js +6 -0
  66. package/dist/src/agent/embedded/map-stream-events.js.map +1 -1
  67. package/dist/src/agent/embedded/subscribe-session.js +24 -0
  68. package/dist/src/agent/embedded/subscribe-session.js.map +1 -1
  69. package/dist/src/agent/embedded/types.d.ts +19 -0
  70. package/dist/src/agent/goals/goal-locale.js +2 -2
  71. package/dist/src/agent/goals/goal-run-store.js +4 -4
  72. package/dist/src/agent/goals/persistent-goal-service.js +1 -1
  73. package/dist/src/agent/goals/post-turn.js +2 -2
  74. package/dist/src/agent/image/load-image-media.js +2 -2
  75. package/dist/src/agent/ipc/bus.js +1 -1
  76. package/dist/src/agent/ipc/inbox.js +2 -2
  77. package/dist/src/agent/ipc/socket.js +1 -1
  78. package/dist/src/agent/memory/builtin-memory-store.js +1 -1
  79. package/dist/src/agent/memory/dreaming/deep-promotion.js +1 -1
  80. package/dist/src/agent/memory/dreaming/events.js +1 -1
  81. package/dist/src/agent/memory/dreaming/last-run.js +1 -1
  82. package/dist/src/agent/memory/dreaming/light-sweep.js +1 -1
  83. package/dist/src/agent/memory/dreaming/preview.js +1 -1
  84. package/dist/src/agent/memory/dreaming/rem-patterns.js +1 -1
  85. package/dist/src/agent/memory/dreaming/short-term-store.js +1 -1
  86. package/dist/src/agent/memory/dreaming/utils.js +1 -1
  87. package/dist/src/agent/memory/plugin-discovery.js +1 -1
  88. package/dist/src/agent/models/manager.js +1 -1
  89. package/dist/src/agent/prompt/service-prompt-builder.js +2 -2
  90. package/dist/src/agent/reply/post-compaction-context.js +1 -1
  91. package/dist/src/agent/reply/startup-context.d.ts +3 -0
  92. package/dist/src/agent/reply/startup-context.js +25 -2
  93. package/dist/src/agent/reply/startup-context.js.map +1 -1
  94. package/dist/src/agent/reply/workspace-boundary-read.js +1 -1
  95. package/dist/src/agent/sandbox/path-policy.js +2 -2
  96. package/dist/src/agent/service/build-direct-message-content.js +1 -1
  97. package/dist/src/agent/service.d.ts +1 -0
  98. package/dist/src/agent/service.js +10 -4
  99. package/dist/src/agent/service.js.map +1 -1
  100. package/dist/src/agent/session/session-inspector.js +1 -1
  101. package/dist/src/agent/skills/config.js +1 -1
  102. package/dist/src/agent/skills/hub-hash.js +2 -2
  103. package/dist/src/agent/skills/hub-lock.js +1 -1
  104. package/dist/src/agent/skills/hub-pull.js +3 -3
  105. package/dist/src/agent/skills/index.js +1 -1
  106. package/dist/src/agent/skills/managed-store.js +1 -1
  107. package/dist/src/agent/skills/scanner.js +1 -1
  108. package/dist/src/agent/skills/skill-manage-ops.js +1 -1
  109. package/dist/src/agent/skills/skill-manager.js +1 -1
  110. package/dist/src/agent/tools/create-share-tool.d.ts +27 -0
  111. package/dist/src/agent/tools/create-share-tool.js +237 -0
  112. package/dist/src/agent/tools/create-share-tool.js.map +1 -0
  113. package/dist/src/agent/tools/dreaming-tool.js +1 -1
  114. package/dist/src/agent/tools/factory.js +35 -1
  115. package/dist/src/agent/tools/factory.js.map +1 -1
  116. package/dist/src/agent/tools/image-generate-tool.js +1 -1
  117. package/dist/src/agent/tools/index.d.ts +2 -0
  118. package/dist/src/agent/tools/index.js +3 -1
  119. package/dist/src/agent/tools/send-media.js +1 -1
  120. package/dist/src/agent/tools/skill-manage-tool.js +1 -1
  121. package/dist/src/agent/tools/workflow-tool.d.ts +41 -0
  122. package/dist/src/agent/tools/workflow-tool.js +271 -0
  123. package/dist/src/agent/tools/workflow-tool.js.map +1 -0
  124. package/dist/src/agent/tools/write.js +1 -1
  125. package/dist/src/agent/workflow/builtins/audit-repo.d.ts +13 -0
  126. package/dist/src/agent/workflow/builtins/audit-repo.js +156 -0
  127. package/dist/src/agent/workflow/builtins/audit-repo.js.map +1 -0
  128. package/dist/src/agent/workflow/builtins/debug-incident.d.ts +13 -0
  129. package/dist/src/agent/workflow/builtins/debug-incident.js +155 -0
  130. package/dist/src/agent/workflow/builtins/debug-incident.js.map +1 -0
  131. package/dist/src/agent/workflow/builtins/index.d.ts +17 -0
  132. package/dist/src/agent/workflow/builtins/index.js +38 -0
  133. package/dist/src/agent/workflow/builtins/index.js.map +1 -0
  134. package/dist/src/agent/workflow/builtins/multi-perspective-review.d.ts +14 -0
  135. package/dist/src/agent/workflow/builtins/multi-perspective-review.js +149 -0
  136. package/dist/src/agent/workflow/builtins/multi-perspective-review.js.map +1 -0
  137. package/dist/src/agent/workflow/builtins/pr-review.d.ts +12 -0
  138. package/dist/src/agent/workflow/builtins/pr-review.js +156 -0
  139. package/dist/src/agent/workflow/builtins/pr-review.js.map +1 -0
  140. package/dist/src/agent/workflow/builtins/research.d.ts +13 -0
  141. package/dist/src/agent/workflow/builtins/research.js +160 -0
  142. package/dist/src/agent/workflow/builtins/research.js.map +1 -0
  143. package/dist/src/agent/workflow/catalog.d.ts +56 -0
  144. package/dist/src/agent/workflow/catalog.js +159 -0
  145. package/dist/src/agent/workflow/catalog.js.map +1 -0
  146. package/dist/src/agent/workflow/channel-capability.d.ts +76 -0
  147. package/dist/src/agent/workflow/channel-capability.js +1 -0
  148. package/dist/src/agent/workflow/index.d.ts +11 -0
  149. package/dist/src/agent/workflow/index.js +10 -0
  150. package/dist/src/agent/workflow/last-run-memory.d.ts +42 -0
  151. package/dist/src/agent/workflow/last-run-memory.js +60 -0
  152. package/dist/src/agent/workflow/last-run-memory.js.map +1 -0
  153. package/dist/src/agent/workflow/parser.d.ts +20 -0
  154. package/dist/src/agent/workflow/parser.js +146 -0
  155. package/dist/src/agent/workflow/parser.js.map +1 -0
  156. package/dist/src/agent/workflow/progress-broker.d.ts +80 -0
  157. package/dist/src/agent/workflow/progress-broker.js +263 -0
  158. package/dist/src/agent/workflow/progress-broker.js.map +1 -0
  159. package/dist/src/agent/workflow/runtime.d.ts +31 -0
  160. package/dist/src/agent/workflow/runtime.js +301 -0
  161. package/dist/src/agent/workflow/runtime.js.map +1 -0
  162. package/dist/src/agent/workflow/snapshot.d.ts +18 -0
  163. package/dist/src/agent/workflow/snapshot.js +144 -0
  164. package/dist/src/agent/workflow/snapshot.js.map +1 -0
  165. package/dist/src/agent/workflow/structured-output-tool.d.ts +33 -0
  166. package/dist/src/agent/workflow/structured-output-tool.js +58 -0
  167. package/dist/src/agent/workflow/structured-output-tool.js.map +1 -0
  168. package/dist/src/agent/workflow/subagent-runner.d.ts +42 -0
  169. package/dist/src/agent/workflow/subagent-runner.js +104 -0
  170. package/dist/src/agent/workflow/subagent-runner.js.map +1 -0
  171. package/dist/src/agent/workflow/types.d.ts +145 -0
  172. package/dist/src/agent/workflow/types.js +1 -0
  173. package/dist/src/auth/credentials.js +3 -3
  174. package/dist/src/auth/profiles/store.js +1 -1
  175. package/dist/src/auth/sync-provider-auth.js +1 -1
  176. package/dist/src/browser/cache-dir-policy.js +1 -1
  177. package/dist/src/browser/cdp-local-launcher.js +2 -2
  178. package/dist/src/browser/providers/browser-ext-install.js +4 -4
  179. package/dist/src/browser/providers/cloakbrowser.js +4 -4
  180. package/dist/src/browser/providers/playwright-doctor.js +1 -1
  181. package/dist/src/browser/stealth.js +1 -1
  182. package/dist/src/channels/attachments/inbound-persist.js +1 -1
  183. package/dist/src/channels/attachments/outbound-tts-persist.js +1 -1
  184. package/dist/src/channels/outbound/persist-store.js +1 -1
  185. package/dist/src/channels/pairing/allow-from-file.js +1 -1
  186. package/dist/src/channels/pairing/pairing-store.js +2 -2
  187. package/dist/src/chat-commands/builtins/config.js +2 -2
  188. package/dist/src/chat-commands/builtins/model.js +40 -23
  189. package/dist/src/chat-commands/builtins/model.js.map +1 -1
  190. package/dist/src/chat-commands/builtins/system.js +30 -15
  191. package/dist/src/chat-commands/builtins/system.js.map +1 -1
  192. package/dist/src/chat-commands/builtins/workflow.d.ts +18 -0
  193. package/dist/src/chat-commands/builtins/workflow.js +172 -0
  194. package/dist/src/chat-commands/builtins/workflow.js.map +1 -0
  195. package/dist/src/chat-commands/context.js +1 -1
  196. package/dist/src/chat-commands/format-output.d.ts +28 -0
  197. package/dist/src/chat-commands/format-output.js +45 -0
  198. package/dist/src/chat-commands/format-output.js.map +1 -0
  199. package/dist/src/chat-commands/index.d.ts +1 -0
  200. package/dist/src/chat-commands/index.js +3 -1
  201. package/dist/src/chat-commands/index.js.map +1 -1
  202. package/dist/src/cli/commands/config.js +2 -2
  203. package/dist/src/cli/commands/doctor/checks/config-health.js +1 -1
  204. package/dist/src/cli/commands/doctor/checks/provider-auth.js +1 -1
  205. package/dist/src/cli/commands/doctor/checks/session-integrity.js +1 -1
  206. package/dist/src/cli/commands/doctor/checks/state-integrity.js +1 -1
  207. package/dist/src/cli/commands/doctor/checks/workspace-status.js +1 -1
  208. package/dist/src/cli/commands/extension-dev.js +1 -1
  209. package/dist/src/cli/commands/extension-marketplace.js +1 -1
  210. package/dist/src/cli/commands/extension-pack.js +1 -1
  211. package/dist/src/cli/commands/gateway/lifecycle.js +10 -4
  212. package/dist/src/cli/commands/gateway/lifecycle.js.map +1 -1
  213. package/dist/src/cli/commands/gateway/shared.js +1 -1
  214. package/dist/src/cli/commands/image.js +1 -1
  215. package/dist/src/cli/commands/init.js +4 -4
  216. package/dist/src/cli/commands/onboard.js +2 -2
  217. package/dist/src/cli/commands/tunnel.js +2 -2
  218. package/dist/src/cli/utils/gateway-client.js +1 -1
  219. package/dist/src/cli/utils/init-workspace-core.js +2 -2
  220. package/dist/src/config/agent-profile.js +1 -1
  221. package/dist/src/config/gateway-bind.js +1 -1
  222. package/dist/src/config/index.js +5 -5
  223. package/dist/src/config/loader.js +2 -2
  224. package/dist/src/config/models-json.js +2 -2
  225. package/dist/src/config/paths-state.js +1 -1
  226. package/dist/src/config/profile.js +2 -2
  227. package/dist/src/config/public-url.d.ts +28 -0
  228. package/dist/src/config/public-url.js +103 -0
  229. package/dist/src/config/public-url.js.map +1 -0
  230. package/dist/src/config/schema.d.ts +82 -0
  231. package/dist/src/config/schema.js +130 -1
  232. package/dist/src/config/schema.js.map +1 -1
  233. package/dist/src/config/workspace-path.js +1 -1
  234. package/dist/src/cron/executor.js +2 -2
  235. package/dist/src/cron/persistence.js +1 -1
  236. package/dist/src/cron/run-log-store.js +1 -1
  237. package/dist/src/daemon/constants.js +1 -1
  238. package/dist/src/daemon/install-plan.js +3 -3
  239. package/dist/src/daemon/install-plan.js.map +1 -1
  240. package/dist/src/daemon/launchd.js +2 -2
  241. package/dist/src/daemon/schtasks.js +38 -1
  242. package/dist/src/daemon/schtasks.js.map +1 -1
  243. package/dist/src/daemon/systemd.js +2 -2
  244. package/dist/src/extensions/bundle-mcp.js +1 -1
  245. package/dist/src/extensions/discover-extensions.js +1 -1
  246. package/dist/src/extensions/health.js +1 -1
  247. package/dist/src/extensions/loader.js +1 -1
  248. package/dist/src/extensions/lockfile.js +2 -2
  249. package/dist/src/gateway/agents-admin.js +2 -2
  250. package/dist/src/gateway/file-path-classifier.js +2 -2
  251. package/dist/src/gateway/heartbeat/service.js +1 -1
  252. package/dist/src/gateway/hono/app.js +33 -2
  253. package/dist/src/gateway/hono/app.js.map +1 -1
  254. package/dist/src/gateway/hono/lib/config-payload.js +1 -1
  255. package/dist/src/gateway/hono/lib/extension-store.js +2 -2
  256. package/dist/src/gateway/hono/lib/static-ui.js +2 -2
  257. package/dist/src/gateway/hono/oauth.js +1 -1
  258. package/dist/src/gateway/hono/routes/agents.js +1 -1
  259. package/dist/src/gateway/hono/routes/auth-registry-extensions.js +1 -1
  260. package/dist/src/gateway/hono/routes/config-patch/misc.js +1 -1
  261. package/dist/src/gateway/hono/routes/dreaming.js +1 -1
  262. package/dist/src/gateway/hono/routes/host-fs.js +2 -2
  263. package/dist/src/gateway/hono/routes/lazy-bundles.js +8 -0
  264. package/dist/src/gateway/hono/routes/lazy-bundles.js.map +1 -1
  265. package/dist/src/gateway/hono/routes/models.js +1 -1
  266. package/dist/src/gateway/hono/routes/shares.js +631 -34
  267. package/dist/src/gateway/hono/routes/shares.js.map +1 -1
  268. package/dist/src/gateway/hono/routes/site-shares.d.ts +3 -0
  269. package/dist/src/gateway/hono/routes/site-shares.js +228 -0
  270. package/dist/src/gateway/hono/routes/site-shares.js.map +1 -0
  271. package/dist/src/gateway/hono/routes/tunnel.js +97 -8
  272. package/dist/src/gateway/hono/routes/tunnel.js.map +1 -1
  273. package/dist/src/gateway/hono/routes/workspace.js +5 -5
  274. package/dist/src/gateway/hono/sse.js +2 -2
  275. package/dist/src/gateway/host.d.ts +3 -1
  276. package/dist/src/gateway/host.js +3 -1
  277. package/dist/src/gateway/host.js.map +1 -1
  278. package/dist/src/gateway/lock.js +3 -3
  279. package/dist/src/gateway/ports.d.ts +6 -0
  280. package/dist/src/gateway/ports.js +38 -2
  281. package/dist/src/gateway/ports.js.map +1 -1
  282. package/dist/src/gateway/public-url.d.ts +8 -0
  283. package/dist/src/gateway/public-url.js +10 -0
  284. package/dist/src/gateway/public-url.js.map +1 -0
  285. package/dist/src/gateway/security/origin-check.d.ts +9 -1
  286. package/dist/src/gateway/security/origin-check.js +4 -0
  287. package/dist/src/gateway/security/origin-check.js.map +1 -1
  288. package/dist/src/gateway/server.js +15 -0
  289. package/dist/src/gateway/server.js.map +1 -1
  290. package/dist/src/gateway/service/agent-runner.js +2 -2
  291. package/dist/src/gateway/service/marketplace-service.js +2 -2
  292. package/dist/src/gateway/service/run-gateway-agent.js +2 -2
  293. package/dist/src/gateway/service.js +3 -2
  294. package/dist/src/gateway/service.js.map +1 -1
  295. package/dist/src/gateway/workspace-fs-file-list.js +1 -1
  296. package/dist/src/heartbeat/index.js +1 -1
  297. package/dist/src/i18n/goals-bundle.js +1 -1
  298. package/dist/src/i18n/index.d.ts +1 -0
  299. package/dist/src/i18n/index.js +2 -1
  300. package/dist/src/i18n/locales/share-tool.en.js +15 -0
  301. package/dist/src/i18n/locales/share-tool.en.js.map +1 -0
  302. package/dist/src/i18n/locales/share-tool.zh.js +15 -0
  303. package/dist/src/i18n/locales/share-tool.zh.js.map +1 -0
  304. package/dist/src/i18n/share-tool-bundle.d.ts +20 -0
  305. package/dist/src/i18n/share-tool-bundle.js +56 -0
  306. package/dist/src/i18n/share-tool-bundle.js.map +1 -0
  307. package/dist/src/infra/gateway-processes.js +1 -0
  308. package/dist/src/infra/gateway-processes.js.map +1 -1
  309. package/dist/src/infra/restart.js +2 -2
  310. package/dist/src/infra/update-check.js +1 -1
  311. package/dist/src/infra/update-lock.js +3 -3
  312. package/dist/src/infra/update-runner.js +1 -1
  313. package/dist/src/infra/update-startup.js +2 -2
  314. package/dist/src/infra/write-file-atomic.js +2 -2
  315. package/dist/src/providers/auth-runtime/auth-profile-store.js +1 -1
  316. package/dist/src/providers/index.js +2 -2
  317. package/dist/src/providers/model-registry.js +1 -1
  318. package/dist/src/session/config-store.js +2 -2
  319. package/dist/src/session/parity/jsonl-transcript-io.js +2 -2
  320. package/dist/src/session/parity/sessions-json-file.js +1 -1
  321. package/dist/src/session/parity/transcript-file-lock.js +2 -2
  322. package/dist/src/session/parity/transcript-paths.js +1 -1
  323. package/dist/src/session/search-index-cache.js +1 -1
  324. package/dist/src/session/search-index.js +1 -1
  325. package/dist/src/session/session-title.js +3 -2
  326. package/dist/src/session/session-title.js.map +1 -1
  327. package/dist/src/session/store.js +5 -5
  328. package/dist/src/share/share-auto.d.ts +74 -0
  329. package/dist/src/share/share-auto.js +247 -0
  330. package/dist/src/share/share-auto.js.map +1 -0
  331. package/dist/src/share/share-config.js +63 -4
  332. package/dist/src/share/share-config.js.map +1 -1
  333. package/dist/src/share/share-landing.d.ts +28 -2
  334. package/dist/src/share/share-landing.js +155 -34
  335. package/dist/src/share/share-landing.js.map +1 -1
  336. package/dist/src/share/share-store.d.ts +48 -4
  337. package/dist/src/share/share-store.js +322 -51
  338. package/dist/src/share/share-store.js.map +1 -1
  339. package/dist/src/share/share-thumbnail.d.ts +35 -0
  340. package/dist/src/share/share-thumbnail.js +277 -0
  341. package/dist/src/share/share-thumbnail.js.map +1 -0
  342. package/dist/src/share/share-types.d.ts +68 -10
  343. package/dist/src/share/share-types.js +18 -1
  344. package/dist/src/share/share-types.js.map +1 -1
  345. package/dist/src/share/share-url.js +1 -1
  346. package/dist/src/share/share-zip.d.ts +35 -0
  347. package/dist/src/share/share-zip.js +303 -0
  348. package/dist/src/share/share-zip.js.map +1 -0
  349. package/dist/src/share/site-proxy.d.ts +35 -0
  350. package/dist/src/share/site-proxy.js +234 -0
  351. package/dist/src/share/site-proxy.js.map +1 -0
  352. package/dist/src/share/site-share-config.d.ts +11 -0
  353. package/dist/src/share/site-share-config.js +103 -0
  354. package/dist/src/share/site-share-config.js.map +1 -0
  355. package/dist/src/share/site-share-router.d.ts +23 -0
  356. package/dist/src/share/site-share-router.js +147 -0
  357. package/dist/src/share/site-share-router.js.map +1 -0
  358. package/dist/src/share/site-share-store.d.ts +53 -0
  359. package/dist/src/share/site-share-store.js +400 -0
  360. package/dist/src/share/site-share-store.js.map +1 -0
  361. package/dist/src/share/site-share-types.d.ts +103 -0
  362. package/dist/src/share/site-share-types.js +41 -0
  363. package/dist/src/share/site-share-types.js.map +1 -0
  364. package/dist/src/share/site-static-serve.d.ts +10 -0
  365. package/dist/src/share/site-static-serve.js +145 -0
  366. package/dist/src/share/site-static-serve.js.map +1 -0
  367. package/dist/src/tui/clipboard-image.js +3 -3
  368. package/dist/src/tui/theme-manager.js +1 -1
  369. package/dist/src/tui/tui-commands.js +18 -0
  370. package/dist/src/tui/tui-commands.js.map +1 -1
  371. package/dist/src/tui/tui-keybindings-file.js +1 -1
  372. package/dist/src/tui/tui-scoped-models.js +2 -2
  373. package/dist/src/tui/tui-settings.js +1 -1
  374. package/dist/src/tui/tui-workflow-slash.d.ts +32 -0
  375. package/dist/src/tui/tui-workflow-slash.js +63 -0
  376. package/dist/src/tui/tui-workflow-slash.js.map +1 -0
  377. package/dist/src/tui/tui.js +2 -2
  378. package/dist/src/tunnel/enable-lan-pairing.js +1 -1
  379. package/dist/src/tunnel/frpc-binary.js +3 -3
  380. package/dist/src/tunnel/frpc-config.js +1 -1
  381. package/dist/src/tunnel/frpc-extract.js +1 -1
  382. package/dist/src/tunnel/index.js +2 -2
  383. package/dist/src/tunnel/pair-context.d.ts +7 -1
  384. package/dist/src/tunnel/pair-context.js +25 -9
  385. package/dist/src/tunnel/pair-context.js.map +1 -1
  386. package/dist/src/tunnel/pair-url.d.ts +14 -1
  387. package/dist/src/tunnel/pair-url.js +14 -1
  388. package/dist/src/tunnel/pair-url.js.map +1 -1
  389. package/dist/src/tunnel/tunnel-service.js +2 -2
  390. package/dist/src/tunnel/tunnel-state.js +1 -1
  391. package/dist/src/utils/logger/audit.js +1 -1
  392. package/dist/src/utils/logger/log-store.js +1 -1
  393. package/dist/src/utils/logger/rotation.js +1 -1
  394. package/dist/src/voice/tts/audio.js +1 -1
  395. package/dist/src/voice/tts/providers/edge-speech.js +2 -2
  396. package/package.json +3 -2
  397. package/dist/gateway/static/root/assets/agents-tR-nNP04.js +0 -222
  398. package/dist/gateway/static/root/assets/apps-page-BDw6SP-d.js +0 -1
  399. package/dist/gateway/static/root/assets/button-KafIU8dx.js +0 -1
  400. package/dist/gateway/static/root/assets/channels-settings-DEFd-jj1.js +0 -1
  401. package/dist/gateway/static/root/assets/channels-status-swr-DI5FHdGe.js +0 -8
  402. package/dist/gateway/static/root/assets/cron-api-BSqY8LwW.js +0 -1
  403. package/dist/gateway/static/root/assets/cron-page-D7lVDjcR.js +0 -1
  404. package/dist/gateway/static/root/assets/dist-C57OMHW8.js +0 -48
  405. package/dist/gateway/static/root/assets/extension-page-CQo2Xsmg.js +0 -1
  406. package/dist/gateway/static/root/assets/extension-settings-page-CZf0WoZg.js +0 -1
  407. package/dist/gateway/static/root/assets/fetch-2iRFmd3n.js +0 -3
  408. package/dist/gateway/static/root/assets/heartbeat-config-api-B0drdQEJ.js +0 -1
  409. package/dist/gateway/static/root/assets/index-0Gt3TG4j.js +0 -4693
  410. package/dist/gateway/static/root/assets/index-BuFldCsB.css +0 -1
  411. package/dist/gateway/static/root/assets/logs-page-DMuORLfC.js +0 -1
  412. package/dist/gateway/static/root/assets/sessions-page-_UO8g6NN.js +0 -1
  413. package/dist/gateway/static/root/assets/settings-form-section-DkmHkknc.js +0 -1
  414. package/dist/gateway/static/root/assets/settings-page-Cz8FoW_A.js +0 -3
  415. package/dist/gateway/static/root/assets/skills-page-HrUOxF7H.js +0 -2
  416. package/dist/gateway/static/root/assets/theme-store-D01dJt95.js +0 -1
  417. package/dist/gateway/static/root/assets/utils-BFwcR6pL.js +0 -1
  418. package/dist/gateway/static/root/assets/voice-api-key-field-JF8-aqc5.js +0 -1
@@ -1,14 +1,19 @@
1
1
  import { resolveGatewayEffectiveHost } from "../../../config/gateway-bind.js";
2
+ import { SHARE_CONFIG_DEFAULTS } from "../../../share/share-types.js";
3
+ import { resolveSiteShareConfig } from "../../../share/site-share-config.js";
4
+ import { logShareAudit } from "../../../share/share-audit.js";
5
+ import { getShareStore, shareResponseContentType } from "../../../share/share-store.js";
6
+ import { getSiteShareStore } from "../../../share/site-share-store.js";
7
+ import { resolveShareUrl } from "../../../share/share-url.js";
8
+ import { audienceDefaults, cleanupStagedSite, decideShareKind, forgetStagedSite, makeDescription, makeTitle, probeShareTarget, rememberStagedSite, stageSingleHtmlAsSite } from "../../../share/share-auto.js";
9
+ import { deleteThumbnail, placeholderSvg, readThumbnail, scheduleThumbnail, thumbnailContentType, thumbnailExists } from "../../../share/share-thumbnail.js";
2
10
  import { extractToken } from "../../auth.js";
3
11
  import { getClientIpFromHeaders } from "../../security/loopback.js";
4
- import { logShareAudit } from "../../../share/share-audit.js";
5
- import { SHARE_CONFIG_DEFAULTS } from "../../../share/share-types.js";
6
- import { renderShareExpiredPage, renderShareLandingPage } from "../../../share/share-landing.js";
12
+ import { renderFolderLandingPage, renderShareExpiredPage, renderShareLandingPage } from "../../../share/share-landing.js";
7
13
  import { consumeSharePublicLimit } from "../../../share/share-rate-limit.js";
8
- import { getShareStore } from "../../../share/share-store.js";
9
- import { resolveShareUrl } from "../../../share/share-url.js";
10
- import { createHash } from "node:crypto";
14
+ import { createZipStream, planDirectoryFiles } from "../../../share/share-zip.js";
11
15
  import { createReadStream } from "node:fs";
16
+ import { createHash } from "node:crypto";
12
17
  import { stat } from "node:fs/promises";
13
18
  import { Readable } from "node:stream";
14
19
  //#region src/gateway/hono/routes/shares.ts
@@ -19,6 +24,18 @@ function getShareUrlContext(service) {
19
24
  gatewayPort: gateway.port ?? 18790
20
25
  };
21
26
  }
27
+ function thumbnailRenderContext(service) {
28
+ const cfg = {
29
+ ...SHARE_CONFIG_DEFAULTS,
30
+ ...resolveShareConfig(service)
31
+ };
32
+ const port = service.currentConfig.gateway.port ?? 18790;
33
+ const internalBaseUrl = cfg.thumbnail.internalGatewayUrl ?? `http://127.0.0.1:${port}`;
34
+ return {
35
+ config: cfg.thumbnail,
36
+ internalBaseUrl
37
+ };
38
+ }
22
39
  function resolveShareConfig(service) {
23
40
  const raw = service.currentConfig.gateway?.share;
24
41
  if (!raw || typeof raw !== "object") return {};
@@ -29,6 +46,7 @@ function hashGatewayToken(token) {
29
46
  }
30
47
  const MAX_CONCURRENT_DOWNLOADS_PER_TOKEN = 5;
31
48
  const activeDownloads = /* @__PURE__ */ new Map();
49
+ const activeZipStreams = /* @__PURE__ */ new Map();
32
50
  function acquireDownloadSlot(token) {
33
51
  const current = activeDownloads.get(token) ?? 0;
34
52
  if (current >= MAX_CONCURRENT_DOWNLOADS_PER_TOKEN) return false;
@@ -40,9 +58,40 @@ function releaseDownloadSlot(token) {
40
58
  if (current <= 1) activeDownloads.delete(token);
41
59
  else activeDownloads.set(token, current - 1);
42
60
  }
61
+ function acquireZipSlot(token, limit) {
62
+ const current = activeZipStreams.get(token) ?? 0;
63
+ if (current >= limit) return false;
64
+ activeZipStreams.set(token, current + 1);
65
+ return true;
66
+ }
67
+ function releaseZipSlot(token) {
68
+ const current = activeZipStreams.get(token) ?? 0;
69
+ if (current <= 1) activeZipStreams.delete(token);
70
+ else activeZipStreams.set(token, current - 1);
71
+ }
72
+ /**
73
+ * Whether the browser can render this MIME natively (when served with
74
+ * `Content-Disposition: inline`). Honours the per-deployment whitelist so
75
+ * admins can block specific types.
76
+ */
77
+ function isPreviewableInline(mime, whitelist) {
78
+ return whitelist.includes(mime);
79
+ }
80
+ /**
81
+ * Whether the type benefits from being rendered through the SPA preview page
82
+ * (e.g. markdown — the browser would otherwise show raw source). Independent
83
+ * of the inline whitelist: SPA preview pulls the bytes via the share API and
84
+ * renders client-side, so admins control reach via TTL/maxViews, not MIME.
85
+ */
86
+ function isRichSpaPreviewable(mime) {
87
+ return mime === "text/markdown" || mime === "application/vnd.openxmlformats-officedocument.wordprocessingml.document" || mime === "application/vnd.openxmlformats-officedocument.presentationml.presentation";
88
+ }
89
+ function rfc5987ContentDisposition(disposition, fileName) {
90
+ return `${disposition}; filename="${fileName.replace(/[^\x20-\x7e]/g, "_").replace(/"/g, "")}"; filename*=UTF-8''${encodeURIComponent(fileName)}`;
91
+ }
43
92
  function registerSharePublicRoutes(app, service) {
44
93
  const store = getShareStore(resolveShareConfig(service));
45
- /** Landing page — does NOT consume viewCount (prevents link unfurl from wasting views). */
94
+ /** Landing page — does NOT consume downloadCount. */
46
95
  app.get("/s/:token", async (c) => {
47
96
  const clientIp = getClientIpFromHeaders({ get: (n) => c.req.header(n) ?? void 0 });
48
97
  const rateResult = consumeSharePublicLimit(clientIp);
@@ -63,17 +112,29 @@ function registerSharePublicRoutes(app, service) {
63
112
  }, `Share access denied: ${validation.reason}`);
64
113
  return c.html(renderShareExpiredPage(validation.reason), 410);
65
114
  }
66
- if (c.req.query("dl") === "1") return handleDownload(c, store, record, clientIp);
115
+ if (record.kind === "directory") return renderDirectoryLanding(c, store, service, record, token);
116
+ if (c.req.query("dl") === "1") return handleFileDownload(c, store, record, clientIp);
67
117
  if (c.req.query("inline") === "1") {
68
118
  if ({
69
119
  ...SHARE_CONFIG_DEFAULTS,
70
120
  ...resolveShareConfig(service)
71
- }.inlinePreviewMimes.includes(record.mimeType)) return handleDownload(c, store, record, clientIp, true);
121
+ }.inlinePreviewMimes.includes(record.mimeType)) return handleFileDownload(c, store, record, clientIp, true);
72
122
  }
73
123
  const downloadPath = `/s/${token}/download`;
74
- return c.html(renderShareLandingPage(record, downloadPath));
124
+ const cfg = {
125
+ ...SHARE_CONFIG_DEFAULTS,
126
+ ...resolveShareConfig(service)
127
+ };
128
+ const previewable = isPreviewableInline(record.mimeType, cfg.inlinePreviewMimes);
129
+ const richPreviewable = isRichSpaPreviewable(record.mimeType);
130
+ const og = buildLandingOg(service, record);
131
+ return c.html(renderShareLandingPage(record, downloadPath, {
132
+ inlineUrl: previewable ? `/s/${token}?inline=1` : null,
133
+ previewUrl: richPreviewable ? `/#/share/${encodeURIComponent(token)}` : null,
134
+ og
135
+ }));
75
136
  });
76
- /** Actual file download — consumes viewCount. */
137
+ /** Single-file download — POST so unfurl/scrapers don't consume views. */
77
138
  app.post("/s/:token/download", async (c) => {
78
139
  const clientIp = getClientIpFromHeaders({ get: (n) => c.req.header(n) ?? void 0 });
79
140
  const rateResult = consumeSharePublicLimit(clientIp);
@@ -94,9 +155,80 @@ function registerSharePublicRoutes(app, service) {
94
155
  }, `Share download denied: ${validation.reason}`);
95
156
  return c.html(renderShareExpiredPage(validation.reason), 410);
96
157
  }
97
- return handleDownload(c, store, record, clientIp);
158
+ if (record.kind === "directory") return c.html(renderShareExpiredPage("not_found"), 404);
159
+ return handleFileDownload(c, store, record, clientIp);
160
+ });
161
+ /** Directory child file (GET — preview counts as download per product). */
162
+ app.get("/s/:token/file", async (c) => {
163
+ const clientIp = getClientIpFromHeaders({ get: (n) => c.req.header(n) ?? void 0 });
164
+ const rateResult = consumeSharePublicLimit(clientIp);
165
+ if (!rateResult.allowed) {
166
+ c.header("Retry-After", String(Math.ceil(rateResult.retryAfterMs / 1e3)));
167
+ return c.text("Too many requests", 429);
168
+ }
169
+ const token = c.req.param("token");
170
+ const record = store.getByToken(token);
171
+ if (!record) return c.html(renderShareExpiredPage("not_found"), 404);
172
+ if (record.kind !== "directory") return c.html(renderShareExpiredPage("not_found"), 404);
173
+ const validation = store.validateAccess(record);
174
+ if (!validation.valid) return c.html(renderShareExpiredPage(validation.reason), 410);
175
+ return handleDirectoryFile(c, store, service, record, c.req.query("path") ?? "", clientIp, c.req.query("inline") === "1" || c.req.query("dl") !== "1");
176
+ });
177
+ /** Directory JSON listing. */
178
+ app.get("/s/:token/tree", async (c) => {
179
+ const rateResult = consumeSharePublicLimit(getClientIpFromHeaders({ get: (n) => c.req.header(n) ?? void 0 }));
180
+ if (!rateResult.allowed) {
181
+ c.header("Retry-After", String(Math.ceil(rateResult.retryAfterMs / 1e3)));
182
+ return c.text("Too many requests", 429);
183
+ }
184
+ const token = c.req.param("token");
185
+ const record = store.getByToken(token);
186
+ if (!record) return c.json({
187
+ ok: false,
188
+ error: { message: "not_found" }
189
+ }, 404);
190
+ if (record.kind !== "directory") return c.json({
191
+ ok: false,
192
+ error: { message: "not_directory" }
193
+ }, 400);
194
+ const validation = store.validateAccess(record);
195
+ if (!validation.valid) return c.json({
196
+ ok: false,
197
+ error: { message: validation.reason }
198
+ }, 410);
199
+ const path = c.req.query("path") ?? "";
200
+ if ((c.req.header("accept") ?? "").includes("text/html")) return renderDirectoryLanding(c, store, service, record, token, path);
201
+ try {
202
+ const listing = await store.listDirectory(record, path);
203
+ return c.json({
204
+ ok: true,
205
+ payload: listing
206
+ });
207
+ } catch (err) {
208
+ const message = err instanceof Error ? err.message : String(err);
209
+ return c.json({
210
+ ok: false,
211
+ error: { message }
212
+ }, 400);
213
+ }
214
+ });
215
+ /** Folder ZIP download (whole share or sub-path). */
216
+ app.get("/s/:token/zip", async (c) => {
217
+ const clientIp = getClientIpFromHeaders({ get: (n) => c.req.header(n) ?? void 0 });
218
+ const rateResult = consumeSharePublicLimit(clientIp);
219
+ if (!rateResult.allowed) {
220
+ c.header("Retry-After", String(Math.ceil(rateResult.retryAfterMs / 1e3)));
221
+ return c.text("Too many requests", 429);
222
+ }
223
+ const token = c.req.param("token");
224
+ const record = store.getByToken(token);
225
+ if (!record) return c.html(renderShareExpiredPage("not_found"), 404);
226
+ if (record.kind !== "directory") return c.html(renderShareExpiredPage("not_found"), 404);
227
+ const validation = store.validateAccess(record);
228
+ if (!validation.valid) return c.html(renderShareExpiredPage(validation.reason), 410);
229
+ return handleDirectoryZip(c, store, service, record, c.req.query("path") ?? "", clientIp);
98
230
  });
99
- /** File metadata (for link preview cards). */
231
+ /** Metadata (for link preview cards / unfurl). Does NOT consume views. */
100
232
  app.get("/s/:token/meta", async (c) => {
101
233
  const rateResult = consumeSharePublicLimit(getClientIpFromHeaders({ get: (n) => c.req.header(n) ?? void 0 }));
102
234
  if (!rateResult.allowed) {
@@ -107,18 +239,20 @@ function registerSharePublicRoutes(app, service) {
107
239
  const record = store.getByToken(token);
108
240
  if (!record) return c.json({ valid: false }, 404);
109
241
  const validation = store.validateAccess(record);
110
- const remainingViews = record.maxViews !== null ? Math.max(0, record.maxViews - record.viewCount) : null;
242
+ const remainingViews = record.maxViews !== null ? Math.max(0, record.maxViews - record.downloadCount) : null;
111
243
  return c.json({
244
+ kind: record.kind,
112
245
  fileName: record.fileName,
113
246
  fileSize: record.fileSize,
114
247
  mimeType: record.mimeType,
115
248
  description: record.description ?? null,
116
249
  expiresAt: record.expiresAt,
117
250
  remainingViews,
118
- valid: validation.valid
251
+ valid: validation.valid,
252
+ directory: record.directory ?? null
119
253
  });
120
254
  });
121
- /** HEAD check (Hono uses .on() for HEAD method). */
255
+ /** HEAD check. */
122
256
  app.on("HEAD", "/s/:token", async (c) => {
123
257
  const token = c.req.param("token");
124
258
  const record = store.getByToken(token);
@@ -126,11 +260,100 @@ function registerSharePublicRoutes(app, service) {
126
260
  const validation = store.validateAccess(record);
127
261
  return c.body(null, validation.valid ? 200 : 410);
128
262
  });
263
+ /** Thumbnail (jpeg/png/svg) — does NOT consume views. */
264
+ app.get("/s/:token/thumbnail", async (c) => {
265
+ const rateResult = consumeSharePublicLimit(getClientIpFromHeaders({ get: (n) => c.req.header(n) ?? void 0 }));
266
+ if (!rateResult.allowed) {
267
+ c.header("Retry-After", String(Math.ceil(rateResult.retryAfterMs / 1e3)));
268
+ return c.text("Too many requests", 429);
269
+ }
270
+ const token = c.req.param("token");
271
+ const record = store.getByToken(token);
272
+ if (!record) return c.body(null, 404);
273
+ if (!store.validateAccess(record).valid) return c.body(null, 410);
274
+ const cached = await readThumbnail(token);
275
+ if (cached) return new Response(cached, {
276
+ status: 200,
277
+ headers: {
278
+ "Content-Type": thumbnailContentType(cached),
279
+ "Cache-Control": "public, max-age=300",
280
+ "X-Content-Type-Options": "nosniff"
281
+ }
282
+ });
283
+ scheduleThumbnail({
284
+ scope: "file",
285
+ token,
286
+ recordId: record.id
287
+ }, thumbnailRenderContext(service));
288
+ const placeholder = placeholderSvg(record.fileName, record.kind === "directory" ? "folder" : "file");
289
+ return new Response(placeholder, {
290
+ status: 200,
291
+ headers: {
292
+ "Content-Type": "image/svg+xml; charset=utf-8",
293
+ "Cache-Control": "public, max-age=10",
294
+ "X-Content-Type-Options": "nosniff"
295
+ }
296
+ });
297
+ });
298
+ app.on("HEAD", "/s/:token/thumbnail", async (c) => {
299
+ const token = c.req.param("token");
300
+ const record = store.getByToken(token);
301
+ if (!record) return c.body(null, 404);
302
+ if (!store.validateAccess(record).valid) return c.body(null, 410);
303
+ const ready = await thumbnailExists(token);
304
+ return c.body(null, ready ? 200 : 202);
305
+ });
306
+ /** Site-share thumbnail. Shape mirrors /s/:token/thumbnail. */
307
+ app.get("/site/:token/thumbnail", async (c) => {
308
+ const rateResult = consumeSharePublicLimit(getClientIpFromHeaders({ get: (n) => c.req.header(n) ?? void 0 }));
309
+ if (!rateResult.allowed) {
310
+ c.header("Retry-After", String(Math.ceil(rateResult.retryAfterMs / 1e3)));
311
+ return c.text("Too many requests", 429);
312
+ }
313
+ const token = c.req.param("token");
314
+ const siteStore = getSiteShareStore(resolveSiteShareConfig(service));
315
+ const record = siteStore.getByToken(token);
316
+ if (!record) return c.body(null, 404);
317
+ if (!siteStore.validateAccess(record).valid) return c.body(null, 410);
318
+ const cached = await readThumbnail(token);
319
+ if (cached) return new Response(cached, {
320
+ status: 200,
321
+ headers: {
322
+ "Content-Type": thumbnailContentType(cached),
323
+ "Cache-Control": "public, max-age=300",
324
+ "X-Content-Type-Options": "nosniff"
325
+ }
326
+ });
327
+ scheduleThumbnail({
328
+ scope: "site",
329
+ token,
330
+ recordId: record.id
331
+ }, thumbnailRenderContext(service));
332
+ const placeholder = placeholderSvg(record.description ?? "site share", "html");
333
+ return new Response(placeholder, {
334
+ status: 200,
335
+ headers: {
336
+ "Content-Type": "image/svg+xml; charset=utf-8",
337
+ "Cache-Control": "public, max-age=10",
338
+ "X-Content-Type-Options": "nosniff"
339
+ }
340
+ });
341
+ });
342
+ app.on("HEAD", "/site/:token/thumbnail", async (c) => {
343
+ const ready = await thumbnailExists(c.req.param("token"));
344
+ return c.body(null, ready ? 200 : 202);
345
+ });
346
+ store.setCleanupHook((rec) => {
347
+ deleteThumbnail(rec.token);
348
+ });
129
349
  }
130
350
  function registerShareRoutes(authenticated, deps) {
131
351
  const { service } = deps;
132
352
  const store = getShareStore(resolveShareConfig(service));
133
- /** Create a share. */
353
+ getSiteShareStore(resolveSiteShareConfig(service)).setCleanupHook((rec) => {
354
+ const dir = forgetStagedSite(rec.id);
355
+ if (dir) cleanupStagedSite(dir);
356
+ });
134
357
  authenticated.post("/api/shares", async (c) => {
135
358
  const gatewayToken = extractToken({ authorization: c.req.header("authorization") ?? void 0 });
136
359
  if (!gatewayToken) return c.json({
@@ -161,6 +384,12 @@ function registerShareRoutes(authenticated, deps) {
161
384
  const ttlMs = typeof body.ttlMs === "number" ? body.ttlMs : void 0;
162
385
  const maxViews = body.maxViews === null ? null : typeof body.maxViews === "number" ? body.maxViews : void 0;
163
386
  const description = typeof body.description === "string" ? body.description.trim() || void 0 : void 0;
387
+ const kind = body.kind === "directory" || body.kind === "file" ? body.kind : void 0;
388
+ const directoryMode = body.directoryMode === "zip-only" ? "zip-only" : body.directoryMode === "browse" ? "browse" : void 0;
389
+ const followSymlinks = body.followSymlinks === true;
390
+ const maxFileCount = typeof body.maxFileCount === "number" ? body.maxFileCount : void 0;
391
+ const maxFolderSize = typeof body.maxFolderSize === "number" ? body.maxFolderSize : void 0;
392
+ const maxDepth = typeof body.maxDepth === "number" ? body.maxDepth : void 0;
164
393
  try {
165
394
  store.updateConfig(resolveShareConfig(service));
166
395
  const record = await store.create({
@@ -171,7 +400,13 @@ function registerShareRoutes(authenticated, deps) {
171
400
  sessionKey,
172
401
  agentId,
173
402
  workspaceRoot,
174
- gatewayTokenHash: hashGatewayToken(gatewayToken)
403
+ gatewayTokenHash: hashGatewayToken(gatewayToken),
404
+ kind,
405
+ directoryMode,
406
+ followSymlinks,
407
+ maxFileCount,
408
+ maxFolderSize,
409
+ maxDepth
175
410
  });
176
411
  const urlCtx = getShareUrlContext(service);
177
412
  const resolved = resolveShareUrl(record.token, urlCtx);
@@ -180,6 +415,7 @@ function registerShareRoutes(authenticated, deps) {
180
415
  payload: {
181
416
  id: record.id,
182
417
  token: record.token,
418
+ kind: record.kind,
183
419
  shareUrl: resolved.shareUrl,
184
420
  lanUrl: resolved.lanUrl,
185
421
  reachability: resolved.reachability,
@@ -187,7 +423,8 @@ function registerShareRoutes(authenticated, deps) {
187
423
  expiresAt: record.expiresAt,
188
424
  maxViews: record.maxViews,
189
425
  fileName: record.fileName,
190
- fileSize: record.fileSize
426
+ fileSize: record.fileSize,
427
+ directory: record.directory ?? null
191
428
  }
192
429
  }, 201);
193
430
  } catch (err) {
@@ -198,7 +435,172 @@ function registerShareRoutes(authenticated, deps) {
198
435
  }, 400);
199
436
  }
200
437
  });
201
- /** List all shares. */
438
+ /**
439
+ * Smart share — picks file vs site, fills sensible defaults, returns the
440
+ * payload the mobile share-sheet needs (title, description, thumbnailUrl,
441
+ * reachability) in a single round-trip.
442
+ */
443
+ authenticated.post("/api/shares/auto", async (c) => {
444
+ const gatewayToken = extractToken({ authorization: c.req.header("authorization") ?? void 0 });
445
+ if (!gatewayToken) return c.json({
446
+ ok: false,
447
+ error: { message: "Token required" }
448
+ }, 401);
449
+ let body;
450
+ try {
451
+ body = await c.req.json();
452
+ } catch {
453
+ return c.json({
454
+ ok: false,
455
+ error: { message: "Invalid JSON" }
456
+ }, 400);
457
+ }
458
+ const path = typeof body.path === "string" ? body.path.trim() : "";
459
+ if (!path) return c.json({
460
+ ok: false,
461
+ error: { message: "Missing path" }
462
+ }, 400);
463
+ const sessionKey = typeof body.sessionKey === "string" ? body.sessionKey.trim() : void 0;
464
+ const agentId = typeof body.agentId === "string" ? body.agentId.trim() : void 0;
465
+ const mode = typeof body.mode === "string" && [
466
+ "auto",
467
+ "force-file",
468
+ "force-site",
469
+ "force-zip"
470
+ ].includes(body.mode) ? body.mode : "auto";
471
+ const audience = body.audience === "friend" || body.audience === "colleague" || body.audience === "public" ? body.audience : void 0;
472
+ const title = typeof body.title === "string" ? body.title : void 0;
473
+ const description = typeof body.description === "string" ? body.description : void 0;
474
+ const ttlOverride = typeof body.ttlMs === "number" ? body.ttlMs : void 0;
475
+ const maxViewsOverride = body.maxViews === null ? null : typeof body.maxViews === "number" ? body.maxViews : void 0;
476
+ const wantThumbnail = body.thumbnail !== false;
477
+ const workspaceRoot = await resolveWorkspaceRootForShare(service, sessionKey, agentId);
478
+ if (!workspaceRoot) return c.json({
479
+ ok: false,
480
+ error: { message: "Workspace not configured" }
481
+ }, 400);
482
+ let probe;
483
+ try {
484
+ probe = await probeShareTarget(workspaceRoot, path);
485
+ } catch (err) {
486
+ const message = err instanceof Error ? err.message : String(err);
487
+ return c.json({
488
+ ok: false,
489
+ error: { message }
490
+ }, 400);
491
+ }
492
+ let decision;
493
+ try {
494
+ decision = decideShareKind(probe, mode);
495
+ } catch (err) {
496
+ const message = err instanceof Error ? err.message : String(err);
497
+ return c.json({
498
+ ok: false,
499
+ error: { message }
500
+ }, 400);
501
+ }
502
+ const defaults = audienceDefaults(audience);
503
+ const ttlMs = ttlOverride ?? defaults.ttlMs;
504
+ const maxViews = maxViewsOverride !== void 0 ? maxViewsOverride : defaults.maxViews;
505
+ const tokenHash = hashGatewayToken(gatewayToken);
506
+ const urlCtx = getShareUrlContext(service);
507
+ try {
508
+ if (decision.kind === "site") {
509
+ const siteStore = getSiteShareStore(resolveSiteShareConfig(service));
510
+ let sitePath = path;
511
+ let stagedDir = null;
512
+ if (probe.kind === "file") {
513
+ const staged = await stageSingleHtmlAsSite(workspaceRoot, probe.absolutePath);
514
+ sitePath = staged.relativePath;
515
+ stagedDir = staged.stagingDir;
516
+ }
517
+ const siteRec = await siteStore.create({
518
+ kind: "static",
519
+ path: sitePath,
520
+ ttlMs,
521
+ description,
522
+ subdomain: typeof body.subdomain === "string" ? body.subdomain : void 0,
523
+ spaFallback: true,
524
+ rewriteMode: "html-css",
525
+ sessionKey,
526
+ agentId,
527
+ workspaceRoot,
528
+ gatewayTokenHash: tokenHash
529
+ });
530
+ if (stagedDir) rememberStagedSite(siteRec.id, stagedDir);
531
+ const cfg = siteStore.getConfig();
532
+ const gatewayPort = service.currentConfig.gateway.port ?? 18790;
533
+ const gatewayHost = resolveGatewayEffectiveHost(service.currentConfig);
534
+ const subdomainLabel = siteRec.subdomain ?? siteRec.token;
535
+ const tunnelUp = !!(await import("../../../tunnel/tunnel-state.js")).loadTunnelState();
536
+ const shareUrl = tunnelUp ? `https://${subdomainLabel}.${cfg.publicHostSuffix}/` : `http://${gatewayHost}:${gatewayPort}/site/${siteRec.token}/`;
537
+ const reachability = tunnelUp ? "public" : gatewayHost === "127.0.0.1" || gatewayHost === "localhost" || gatewayHost === "::1" ? "local-only" : "lan";
538
+ const thumbnailUrl = wantThumbnail ? `${tunnelUp ? `https://${subdomainLabel}.${cfg.publicHostSuffix}` : `http://${gatewayHost}:${gatewayPort}`}/site/${siteRec.token}/thumbnail` : "";
539
+ if (wantThumbnail) {
540
+ scheduleThumbnail({
541
+ scope: "site",
542
+ token: siteRec.token,
543
+ recordId: siteRec.id
544
+ }, thumbnailRenderContext(service));
545
+ siteStore.setThumbnailStatus(siteRec.id, "pending");
546
+ }
547
+ const titleOut = makeTitle(probe.kind === "directory" ? path.split("/").pop() || path : path, title);
548
+ return c.json({
549
+ ok: true,
550
+ payload: {
551
+ share: {
552
+ id: siteRec.id,
553
+ kind: "site",
554
+ title: titleOut,
555
+ description: makeDescription({
556
+ audience,
557
+ expiresAt: siteRec.expiresAt,
558
+ override: description
559
+ }),
560
+ shareUrl,
561
+ lanUrl: null,
562
+ reachability,
563
+ reachabilityHint: reachability === "public" ? null : reachability === "lan" ? "当前局域网内可访问,开启远程隧道后对外网可达" : "当前仅本机可访问,开启远程隧道后对外可达",
564
+ expiresAt: siteRec.expiresAt,
565
+ maxViews: null
566
+ },
567
+ thumbnail: {
568
+ url: thumbnailUrl,
569
+ status: wantThumbnail ? "pending" : "unavailable",
570
+ width: SHARE_CONFIG_DEFAULTS.thumbnail.viewportWidth,
571
+ height: SHARE_CONFIG_DEFAULTS.thumbnail.viewportHeight
572
+ },
573
+ routing: {
574
+ reason: decision.reason,
575
+ hint: decision.hint
576
+ }
577
+ }
578
+ }, 201);
579
+ }
580
+ return await createFileShareResponse({
581
+ c,
582
+ service,
583
+ store,
584
+ probe,
585
+ decision,
586
+ ttlMs,
587
+ maxViews,
588
+ title,
589
+ description,
590
+ audience,
591
+ workspaceRoot,
592
+ tokenHash,
593
+ urlCtx,
594
+ wantThumbnail
595
+ });
596
+ } catch (err) {
597
+ const message = err instanceof Error ? err.message : String(err);
598
+ return c.json({
599
+ ok: false,
600
+ error: { message }
601
+ }, 400);
602
+ }
603
+ });
202
604
  authenticated.get("/api/shares", async (c) => {
203
605
  store.updateConfig(resolveShareConfig(service));
204
606
  const shares = store.getAllShares();
@@ -209,6 +611,7 @@ function registerShareRoutes(authenticated, deps) {
209
611
  const expired = now >= new Date(r.expiresAt).getTime();
210
612
  return {
211
613
  id: r.id,
614
+ kind: r.kind,
212
615
  fileName: r.fileName,
213
616
  workspaceRelativePath: r.workspaceRelativePath,
214
617
  shareUrl: resolved.shareUrl,
@@ -216,13 +619,14 @@ function registerShareRoutes(authenticated, deps) {
216
619
  reachability: resolved.reachability,
217
620
  createdAt: r.createdAt,
218
621
  expiresAt: r.expiresAt,
219
- viewCount: r.viewCount,
622
+ downloadCount: r.downloadCount,
220
623
  maxViews: r.maxViews,
221
624
  revoked: r.revoked,
222
625
  expired,
223
626
  description: r.description ?? null,
224
627
  fileSize: r.fileSize,
225
- mimeType: r.mimeType
628
+ mimeType: r.mimeType,
629
+ directory: r.directory ?? null
226
630
  };
227
631
  });
228
632
  return c.json({
@@ -230,7 +634,6 @@ function registerShareRoutes(authenticated, deps) {
230
634
  payload: { shares: items }
231
635
  });
232
636
  });
233
- /** Get single share details. */
234
637
  authenticated.get("/api/shares/:id", async (c) => {
235
638
  const id = c.req.param("id");
236
639
  const record = store.getById(id);
@@ -253,7 +656,6 @@ function registerShareRoutes(authenticated, deps) {
253
656
  }
254
657
  });
255
658
  });
256
- /** Revoke a share. */
257
659
  authenticated.delete("/api/shares/:id", async (c) => {
258
660
  const id = c.req.param("id");
259
661
  if (!store.revoke(id)) return c.json({
@@ -262,7 +664,6 @@ function registerShareRoutes(authenticated, deps) {
262
664
  }, 404);
263
665
  return c.json({ ok: true });
264
666
  });
265
- /** Batch revoke. */
266
667
  authenticated.delete("/api/shares", async (c) => {
267
668
  let body = {};
268
669
  try {
@@ -286,7 +687,6 @@ function registerShareRoutes(authenticated, deps) {
286
687
  payload: { revokedCount: count }
287
688
  });
288
689
  });
289
- /** Update a share (extend TTL or change maxViews). */
290
690
  authenticated.patch("/api/shares/:id", async (c) => {
291
691
  const id = c.req.param("id");
292
692
  let body;
@@ -319,6 +719,204 @@ function registerShareRoutes(authenticated, deps) {
319
719
  });
320
720
  });
321
721
  }
722
+ async function renderDirectoryLanding(c, store, service, record, token, subPath = "") {
723
+ store.updateConfig(resolveShareConfig(service));
724
+ const urls = {
725
+ tree: (p) => p ? `/s/${token}/tree?path=${encodeURIComponent(p)}` : `/s/${token}`,
726
+ file: (p) => `/s/${token}/file?path=${encodeURIComponent(p)}`,
727
+ zip: (p) => p ? `/s/${token}/zip?path=${encodeURIComponent(p)}` : `/s/${token}/zip`
728
+ };
729
+ let listing = null;
730
+ if (record.directory?.mode !== "zip-only") try {
731
+ listing = await store.listDirectory(record, subPath);
732
+ } catch (err) {
733
+ logShareAudit("share.access_denied", {
734
+ shareId: record.id,
735
+ tokenPrefix: record.token.slice(0, 8),
736
+ subPath,
737
+ reason: String(err)
738
+ }, `Directory listing failed`);
739
+ return c.html(renderShareExpiredPage("not_found"), 404);
740
+ }
741
+ const og = buildLandingOg(service, record);
742
+ return c.html(renderFolderLandingPage(record, listing, urls, { og }));
743
+ }
744
+ function buildLandingOg(service, record) {
745
+ const resolved = resolveShareUrl(record.token, getShareUrlContext(service));
746
+ if (resolved.reachability !== "public") return void 0;
747
+ return {
748
+ absoluteShareUrl: resolved.shareUrl,
749
+ absoluteThumbnailUrl: `${resolved.shareUrl}/thumbnail`,
750
+ title: record.fileName,
751
+ description: record.description ?? void 0
752
+ };
753
+ }
754
+ async function handleDirectoryFile(c, store, service, record, relPath, clientIp, inline) {
755
+ if (!acquireDownloadSlot(record.token)) return new Response("Too many concurrent downloads for this share", { status: 429 });
756
+ try {
757
+ const resolved = await store.resolveDirectoryChild(record, relPath);
758
+ if (resolved.ok !== true) {
759
+ logShareAudit("share.access_denied", {
760
+ shareId: record.id,
761
+ tokenPrefix: record.token.slice(0, 8),
762
+ reason: resolved.reason,
763
+ clientIp,
764
+ relPath
765
+ }, `Directory child resolution failed: ${resolved.reason}`);
766
+ return c.html(renderShareExpiredPage("not_found"), 404);
767
+ }
768
+ const integrity = await store.validateFileIntegrity(record);
769
+ if (!integrity.valid) return c.html(renderShareExpiredPage(integrity.reason ?? "file_deleted"), 410);
770
+ const fileStat = await stat(resolved.absolutePath);
771
+ if (!fileStat.isFile()) return c.html(renderShareExpiredPage("not_found"), 404);
772
+ store.updateConfig(resolveShareConfig(service));
773
+ const cfg = store.getConfig();
774
+ if (fileStat.size > cfg.maxFileSize) return c.html(renderShareExpiredPage("not_found"), 410);
775
+ const cfgPreview = {
776
+ ...SHARE_CONFIG_DEFAULTS,
777
+ ...resolveShareConfig(service)
778
+ };
779
+ const baseName = relPath.split("/").pop() || record.fileName;
780
+ const { resolveMimeType } = await import("../../../share/share-store.js");
781
+ const mime = resolveMimeType(baseName);
782
+ const useInline = inline && cfgPreview.inlinePreviewMimes.includes(mime);
783
+ store.incrementDownloadCount(record.id);
784
+ logShareAudit("share.access", {
785
+ shareId: record.id,
786
+ tokenPrefix: record.token.slice(0, 8),
787
+ clientIp,
788
+ relPath,
789
+ mode: useInline ? "inline" : "attachment"
790
+ }, `Directory file served: ${baseName}`);
791
+ const stream = createReadStream(resolved.absolutePath);
792
+ const webStream = Readable.toWeb(stream);
793
+ const disposition = rfc5987ContentDisposition(useInline ? "inline" : "attachment", baseName);
794
+ return new Response(webStream, {
795
+ status: 200,
796
+ headers: {
797
+ "Content-Type": shareResponseContentType(mime),
798
+ "Content-Disposition": disposition,
799
+ "Content-Length": String(fileStat.size),
800
+ "Cache-Control": "private, no-store",
801
+ "X-Content-Type-Options": "nosniff",
802
+ "X-Frame-Options": "DENY",
803
+ "Referrer-Policy": "no-referrer"
804
+ }
805
+ });
806
+ } finally {
807
+ releaseDownloadSlot(record.token);
808
+ }
809
+ }
810
+ async function handleDirectoryZip(c, store, service, record, subPath, clientIp) {
811
+ store.updateConfig(resolveShareConfig(service));
812
+ const cfg = store.getConfig();
813
+ const zipLimit = cfg.directory.zipConcurrency;
814
+ if (!acquireZipSlot(record.token, zipLimit)) return new Response("Too many concurrent ZIP streams for this share", { status: 429 });
815
+ try {
816
+ if ((await store.resolveDirectoryChild(record, subPath)).ok !== true) return c.html(renderShareExpiredPage("not_found"), 404);
817
+ const integrity = await store.validateFileIntegrity(record);
818
+ if (!integrity.valid) return c.html(renderShareExpiredPage(integrity.reason ?? "file_deleted"), 410);
819
+ const files = await planDirectoryFiles(record, {
820
+ rootRelativePath: subPath,
821
+ maxFileCount: cfg.directory.maxFileCount,
822
+ maxFolderSize: cfg.directory.maxFolderSize,
823
+ followSymlinks: record.directory?.followSymlinks ?? false,
824
+ maxDepth: record.directory?.maxDepth ?? cfg.directory.maxDepth
825
+ });
826
+ store.incrementDownloadCount(record.id);
827
+ logShareAudit("share.access", {
828
+ shareId: record.id,
829
+ tokenPrefix: record.token.slice(0, 8),
830
+ clientIp,
831
+ mode: "zip",
832
+ subPath,
833
+ fileCount: files.length
834
+ }, `Directory zip served: ${record.fileName}${subPath ? "/" + subPath : ""}`);
835
+ const zipName = subPath ? `${record.fileName}-${subPath.replace(/[\\/]/g, "_")}.zip` : `${record.fileName}.zip`;
836
+ const stream = createZipStream({ files });
837
+ const webStream = Readable.toWeb(stream);
838
+ return new Response(webStream, {
839
+ status: 200,
840
+ headers: {
841
+ "Content-Type": "application/zip",
842
+ "Content-Disposition": rfc5987ContentDisposition("attachment", zipName),
843
+ "Cache-Control": "private, no-store",
844
+ "X-Content-Type-Options": "nosniff",
845
+ "X-Frame-Options": "DENY",
846
+ "Referrer-Policy": "no-referrer"
847
+ }
848
+ });
849
+ } catch (err) {
850
+ const message = err instanceof Error ? err.message : String(err);
851
+ return new Response(`zip build failed: ${message}`, { status: 500 });
852
+ } finally {
853
+ releaseZipSlot(record.token);
854
+ }
855
+ }
856
+ async function createFileShareResponse(args) {
857
+ const { c, service, store, probe, decision, ttlMs, maxViews, title, description, audience, workspaceRoot, tokenHash, urlCtx, wantThumbnail } = args;
858
+ store.updateConfig(resolveShareConfig(service));
859
+ const record = await store.create({
860
+ path: relPathFromAbs(workspaceRoot, probe.absolutePath),
861
+ workspaceRoot,
862
+ gatewayTokenHash: tokenHash,
863
+ ttlMs,
864
+ maxViews: maxViews === void 0 ? void 0 : maxViews,
865
+ description,
866
+ kind: probe.kind === "directory" ? "directory" : "file",
867
+ directoryMode: decision.kind === "zip" ? "zip-only" : probe.kind === "directory" ? "browse" : void 0
868
+ });
869
+ const resolved = resolveShareUrl(record.token, urlCtx);
870
+ const titleOut = makeTitle(record.fileName, title);
871
+ const descOut = makeDescription({
872
+ audience,
873
+ expiresAt: record.expiresAt,
874
+ override: description
875
+ });
876
+ const thumbnailUrl = wantThumbnail ? `${resolved.shareUrl}/thumbnail` : "";
877
+ if (wantThumbnail) {
878
+ scheduleThumbnail({
879
+ scope: "file",
880
+ token: record.token,
881
+ recordId: record.id
882
+ }, thumbnailRenderContext(service));
883
+ store.setThumbnailStatus(record.id, "pending");
884
+ }
885
+ return c.json({
886
+ ok: true,
887
+ payload: {
888
+ share: {
889
+ id: record.id,
890
+ kind: decision.kind,
891
+ title: titleOut,
892
+ description: descOut,
893
+ shareUrl: resolved.shareUrl,
894
+ lanUrl: resolved.lanUrl,
895
+ reachability: resolved.reachability,
896
+ reachabilityHint: resolved.reachabilityHint,
897
+ expiresAt: record.expiresAt,
898
+ maxViews: record.maxViews
899
+ },
900
+ thumbnail: {
901
+ url: thumbnailUrl,
902
+ status: wantThumbnail ? "pending" : "unavailable",
903
+ width: SHARE_CONFIG_DEFAULTS.thumbnail.viewportWidth,
904
+ height: SHARE_CONFIG_DEFAULTS.thumbnail.viewportHeight
905
+ },
906
+ routing: {
907
+ reason: decision.reason,
908
+ hint: decision.hint
909
+ }
910
+ }
911
+ }, 201);
912
+ }
913
+ function relPathFromAbs(workspaceRoot, abs) {
914
+ const root = workspaceRoot.replace(/[\\/]+$/, "");
915
+ if (abs === root) return "";
916
+ if (abs.startsWith(`${root}/`)) return abs.slice(root.length + 1);
917
+ if (abs.startsWith(`${root}\\`)) return abs.slice(root.length + 1).replace(/\\/g, "/");
918
+ return abs.split(/[\\/]/).pop() ?? abs;
919
+ }
322
920
  async function resolveWorkspaceRootForShare(service, sessionKey, agentId) {
323
921
  const cfg = service.currentConfig;
324
922
  if (sessionKey) try {
@@ -331,8 +929,8 @@ async function resolveWorkspaceRootForShare(service, sessionKey, agentId) {
331
929
  if (root) return root;
332
930
  return resolveAgentWorkspaceDir(cfg, resolveDefaultAgentId(cfg));
333
931
  }
334
- async function handleDownload(c, store, record, clientIp, inline = false) {
335
- if (!acquireDownloadSlot(record.token)) return c.body("Too many concurrent downloads for this share", 429);
932
+ async function handleFileDownload(c, store, record, clientIp, inline = false) {
933
+ if (!acquireDownloadSlot(record.token)) return new Response("Too many concurrent downloads for this share", { status: 429 });
336
934
  try {
337
935
  const integrity = await store.validateFileIntegrity(record);
338
936
  if (!integrity.valid) {
@@ -342,27 +940,26 @@ async function handleDownload(c, store, record, clientIp, inline = false) {
342
940
  reason: integrity.reason,
343
941
  clientIp
344
942
  }, `Share file integrity check failed: ${integrity.reason}`);
345
- const { renderShareExpiredPage: render } = await import("../../../share/share-landing.js");
346
- return new Response(render(integrity.reason), {
943
+ return new Response(renderShareExpiredPage(integrity.reason ?? "file_deleted"), {
347
944
  status: 410,
348
945
  headers: { "Content-Type": "text/html; charset=utf-8" }
349
946
  });
350
947
  }
351
- store.incrementViewCount(record.id);
948
+ store.incrementDownloadCount(record.id);
352
949
  logShareAudit("share.access", {
353
950
  shareId: record.id,
354
951
  tokenPrefix: record.token.slice(0, 8),
355
952
  clientIp,
356
- viewCount: record.viewCount
953
+ downloadCount: record.downloadCount
357
954
  }, `Share downloaded: ${record.fileName}`);
358
955
  const fileStat = await stat(record.absolutePath);
359
956
  const stream = createReadStream(record.absolutePath);
360
957
  const webStream = Readable.toWeb(stream);
361
- const disposition = inline ? `inline; filename="${encodeURIComponent(record.fileName)}"` : `attachment; filename="${encodeURIComponent(record.fileName)}"`;
958
+ const disposition = rfc5987ContentDisposition(inline ? "inline" : "attachment", record.fileName);
362
959
  return new Response(webStream, {
363
960
  status: 200,
364
961
  headers: {
365
- "Content-Type": record.mimeType,
962
+ "Content-Type": shareResponseContentType(record.mimeType),
366
963
  "Content-Disposition": disposition,
367
964
  "Content-Length": String(fileStat.size),
368
965
  "Cache-Control": "private, no-store",