@songsid/agend 2.2.0-beta.1 → 2.2.0-beta.11

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 (401) hide show
  1. package/README.md +1 -1
  2. package/README.zh-TW.md +1 -1
  3. package/dist/agent-cli-instructions.md +16 -9
  4. package/dist/agent-cli.js +15 -7
  5. package/dist/agent-cli.js.map +1 -1
  6. package/dist/agent-endpoint.js +24 -9
  7. package/dist/agent-endpoint.js.map +1 -1
  8. package/dist/agent-whats-new.d.ts +47 -0
  9. package/dist/agent-whats-new.js +147 -0
  10. package/dist/agent-whats-new.js.map +1 -0
  11. package/dist/auth-api.d.ts +5 -3
  12. package/dist/auth-api.js +19 -3
  13. package/dist/auth-api.js.map +1 -1
  14. package/dist/backend/antigravity.d.ts +8 -0
  15. package/dist/backend/antigravity.js +32 -9
  16. package/dist/backend/antigravity.js.map +1 -1
  17. package/dist/backend/claude-carry.d.ts +22 -0
  18. package/dist/backend/claude-carry.js +170 -0
  19. package/dist/backend/claude-carry.js.map +1 -0
  20. package/dist/backend/claude-code.d.ts +65 -2
  21. package/dist/backend/claude-code.js +226 -33
  22. package/dist/backend/claude-code.js.map +1 -1
  23. package/dist/backend/codex-metadata.d.ts +33 -0
  24. package/dist/backend/codex-metadata.js +122 -18
  25. package/dist/backend/codex-metadata.js.map +1 -1
  26. package/dist/backend/codex.d.ts +3 -0
  27. package/dist/backend/codex.js +17 -1
  28. package/dist/backend/codex.js.map +1 -1
  29. package/dist/backend/credential-profile.d.ts +40 -3
  30. package/dist/backend/credential-profile.js +113 -5
  31. package/dist/backend/credential-profile.js.map +1 -1
  32. package/dist/backend/effort-metadata.d.ts +37 -1
  33. package/dist/backend/effort-metadata.js +86 -15
  34. package/dist/backend/effort-metadata.js.map +1 -1
  35. package/dist/backend/grok.d.ts +4 -0
  36. package/dist/backend/grok.js +14 -0
  37. package/dist/backend/grok.js.map +1 -1
  38. package/dist/backend/kiro-engine-ledger.d.ts +5 -0
  39. package/dist/backend/kiro-engine-ledger.js +20 -0
  40. package/dist/backend/kiro-engine-ledger.js.map +1 -1
  41. package/dist/backend/kiro-identity.d.ts +6 -0
  42. package/dist/backend/kiro-identity.js +19 -5
  43. package/dist/backend/kiro-identity.js.map +1 -1
  44. package/dist/backend/kiro.d.ts +39 -3
  45. package/dist/backend/kiro.js +143 -19
  46. package/dist/backend/kiro.js.map +1 -1
  47. package/dist/backend/launch-effort.d.ts +61 -0
  48. package/dist/backend/launch-effort.js +170 -0
  49. package/dist/backend/launch-effort.js.map +1 -0
  50. package/dist/backend/muse.d.ts +84 -0
  51. package/dist/backend/muse.js +449 -53
  52. package/dist/backend/muse.js.map +1 -1
  53. package/dist/backend/types.d.ts +47 -4
  54. package/dist/backend/types.js +12 -1
  55. package/dist/backend/types.js.map +1 -1
  56. package/dist/backend-command.d.ts +91 -0
  57. package/dist/backend-command.js +122 -0
  58. package/dist/backend-command.js.map +1 -0
  59. package/dist/background-work-ended.d.ts +18 -0
  60. package/dist/background-work-ended.js +30 -0
  61. package/dist/background-work-ended.js.map +1 -0
  62. package/dist/cache-service.d.ts +2 -0
  63. package/dist/cache-service.js +2 -1
  64. package/dist/cache-service.js.map +1 -1
  65. package/dist/channel/adapters/discord.d.ts +36 -1
  66. package/dist/channel/adapters/discord.js +126 -4
  67. package/dist/channel/adapters/discord.js.map +1 -1
  68. package/dist/channel/adapters/telegram.d.ts +42 -2
  69. package/dist/channel/adapters/telegram.js +142 -23
  70. package/dist/channel/adapters/telegram.js.map +1 -1
  71. package/dist/channel/mcp-server.js +9 -1
  72. package/dist/channel/mcp-server.js.map +1 -1
  73. package/dist/channel/mcp-tools.d.ts +11 -0
  74. package/dist/channel/mcp-tools.js +26 -13
  75. package/dist/channel/mcp-tools.js.map +1 -1
  76. package/dist/channel/message-edit-errors.d.ts +5 -0
  77. package/dist/channel/message-edit-errors.js +24 -0
  78. package/dist/channel/message-edit-errors.js.map +1 -0
  79. package/dist/channel/telegram-markdown.d.ts +6 -0
  80. package/dist/channel/telegram-markdown.js +104 -0
  81. package/dist/channel/telegram-markdown.js.map +1 -0
  82. package/dist/channel/tool-router.d.ts +2 -0
  83. package/dist/channel/tool-router.js +10 -4
  84. package/dist/channel/tool-router.js.map +1 -1
  85. package/dist/channel/types.d.ts +55 -3
  86. package/dist/cli.js +167 -39
  87. package/dist/cli.js.map +1 -1
  88. package/dist/command-table.d.ts +2 -2
  89. package/dist/command-table.js +6 -3
  90. package/dist/command-table.js.map +1 -1
  91. package/dist/config-validator.js +59 -0
  92. package/dist/config-validator.js.map +1 -1
  93. package/dist/cross-instance-envelope.d.ts +15 -2
  94. package/dist/cross-instance-envelope.js +29 -5
  95. package/dist/cross-instance-envelope.js.map +1 -1
  96. package/dist/daemon.d.ts +156 -1
  97. package/dist/daemon.js +773 -89
  98. package/dist/daemon.js.map +1 -1
  99. package/dist/delivery-outbox.d.ts +70 -8
  100. package/dist/delivery-outbox.js +315 -24
  101. package/dist/delivery-outbox.js.map +1 -1
  102. package/dist/delivery-reconciliation.d.ts +18 -1
  103. package/dist/delivery-reconciliation.js +67 -2
  104. package/dist/delivery-reconciliation.js.map +1 -1
  105. package/dist/fleet-context.d.ts +10 -1
  106. package/dist/fleet-manager.d.ts +404 -13
  107. package/dist/fleet-manager.js +2039 -210
  108. package/dist/fleet-manager.js.map +1 -1
  109. package/dist/general-knowledge/skills/backend-providers/SKILL.md +1 -1
  110. package/dist/general-knowledge/skills/credential-profiles/SKILL.md +92 -21
  111. package/dist/general-knowledge/skills/cross-instance-messaging/SKILL.md +27 -3
  112. package/dist/general-knowledge/skills/delegation-playbook/SKILL.md +8 -8
  113. package/dist/general-knowledge/skills/fleet-config/SKILL.md +5 -5
  114. package/dist/general-knowledge/skills/fleet-health/SKILL.md +4 -4
  115. package/dist/general-knowledge/skills/fleet-restart/SKILL.md +13 -13
  116. package/dist/general-knowledge/skills/instance-lifecycle/SKILL.md +3 -2
  117. package/dist/general-knowledge/skills/kiro-engine-migration/SKILL.md +6 -0
  118. package/dist/general-knowledge/skills/model-discovery/SKILL.md +12 -9
  119. package/dist/general-knowledge/skills/multi-channel/SKILL.md +8 -2
  120. package/dist/general-knowledge/skills/scheduling/SKILL.md +5 -3
  121. package/dist/general-knowledge/skills/session-management/SKILL.md +6 -5
  122. package/dist/general-knowledge/skills/tui-effort/SKILL.md +2 -2
  123. package/dist/general-knowledge/skills/worker-collaboration/SKILL.md +8 -3
  124. package/dist/general-knowledge/steering/core-rules.md +8 -7
  125. package/dist/host-memory.d.ts +10 -0
  126. package/dist/host-memory.js +33 -1
  127. package/dist/host-memory.js.map +1 -1
  128. package/dist/human-delivery-receipts.d.ts +83 -0
  129. package/dist/human-delivery-receipts.js +194 -0
  130. package/dist/human-delivery-receipts.js.map +1 -0
  131. package/dist/instance-avatar.d.ts +36 -0
  132. package/dist/instance-avatar.js +57 -0
  133. package/dist/instance-avatar.js.map +1 -0
  134. package/dist/instance-config-impact.js +6 -0
  135. package/dist/instance-config-impact.js.map +1 -1
  136. package/dist/instance-lifecycle.d.ts +29 -6
  137. package/dist/instance-lifecycle.js +79 -28
  138. package/dist/instance-lifecycle.js.map +1 -1
  139. package/dist/instructions.d.ts +3 -1
  140. package/dist/instructions.js +11 -8
  141. package/dist/instructions.js.map +1 -1
  142. package/dist/kiro-log-gate.d.ts +42 -0
  143. package/dist/kiro-log-gate.js +39 -0
  144. package/dist/kiro-log-gate.js.map +1 -0
  145. package/dist/locale.js +226 -44
  146. package/dist/locale.js.map +1 -1
  147. package/dist/log-service.d.ts +99 -0
  148. package/dist/log-service.js +243 -0
  149. package/dist/log-service.js.map +1 -0
  150. package/dist/log-steps.d.ts +169 -0
  151. package/dist/log-steps.js +858 -0
  152. package/dist/log-steps.js.map +1 -0
  153. package/dist/login-controller.d.ts +20 -3
  154. package/dist/login-controller.js +71 -19
  155. package/dist/login-controller.js.map +1 -1
  156. package/dist/login-flows.d.ts +5 -2
  157. package/dist/login-flows.js +5 -4
  158. package/dist/login-flows.js.map +1 -1
  159. package/dist/memory-pressure.d.ts +13 -1
  160. package/dist/memory-pressure.js +50 -10
  161. package/dist/memory-pressure.js.map +1 -1
  162. package/dist/needs-you-hub.js +2 -0
  163. package/dist/needs-you-hub.js.map +1 -1
  164. package/dist/needs-you-live.d.ts +6 -0
  165. package/dist/needs-you-live.js +23 -3
  166. package/dist/needs-you-live.js.map +1 -1
  167. package/dist/next-steps.js +1 -1
  168. package/dist/next-steps.js.map +1 -1
  169. package/dist/outbound-handlers.d.ts +100 -0
  170. package/dist/outbound-handlers.js +284 -97
  171. package/dist/outbound-handlers.js.map +1 -1
  172. package/dist/outbound-schemas.d.ts +38 -15
  173. package/dist/outbound-schemas.js +36 -8
  174. package/dist/outbound-schemas.js.map +1 -1
  175. package/dist/public-link-progress.d.ts +1 -1
  176. package/dist/public-link-progress.js +8 -2
  177. package/dist/public-link-progress.js.map +1 -1
  178. package/dist/public-preview.d.ts +101 -0
  179. package/dist/public-preview.js +236 -0
  180. package/dist/public-preview.js.map +1 -0
  181. package/dist/public-web-gateway.js +14 -4
  182. package/dist/public-web-gateway.js.map +1 -1
  183. package/dist/public-web-link.d.ts +24 -1
  184. package/dist/public-web-link.js +63 -9
  185. package/dist/public-web-link.js.map +1 -1
  186. package/dist/reply-buttons-controller.d.ts +89 -5
  187. package/dist/reply-buttons-controller.js +273 -8
  188. package/dist/reply-buttons-controller.js.map +1 -1
  189. package/dist/reply-buttons.d.ts +48 -3
  190. package/dist/reply-buttons.js +105 -10
  191. package/dist/reply-buttons.js.map +1 -1
  192. package/dist/reply-polls.d.ts +85 -0
  193. package/dist/reply-polls.js +154 -0
  194. package/dist/reply-polls.js.map +1 -0
  195. package/dist/restart-guard.d.ts +6 -3
  196. package/dist/restart-guard.js +6 -3
  197. package/dist/restart-guard.js.map +1 -1
  198. package/dist/restart-progress.d.ts +33 -0
  199. package/dist/restart-progress.js +30 -0
  200. package/dist/restart-progress.js.map +1 -1
  201. package/dist/service-activation.d.ts +15 -7
  202. package/dist/service-activation.js +46 -29
  203. package/dist/service-activation.js.map +1 -1
  204. package/dist/service-installer.d.ts +31 -0
  205. package/dist/service-installer.js +41 -2
  206. package/dist/service-installer.js.map +1 -1
  207. package/dist/settings-api.js +85 -25
  208. package/dist/settings-api.js.map +1 -1
  209. package/dist/settings-change.js +72 -1
  210. package/dist/settings-change.js.map +1 -1
  211. package/dist/settings-effect.js +28 -4
  212. package/dist/settings-effect.js.map +1 -1
  213. package/dist/settings-http-confirmation.js +7 -0
  214. package/dist/settings-http-confirmation.js.map +1 -1
  215. package/dist/setup-form.d.ts +1 -1
  216. package/dist/setup-form.js +1 -1
  217. package/dist/spawn-gate.js +5 -1
  218. package/dist/spawn-gate.js.map +1 -1
  219. package/dist/spawn-hold-reply.d.ts +12 -0
  220. package/dist/spawn-hold-reply.js +39 -0
  221. package/dist/spawn-hold-reply.js.map +1 -0
  222. package/dist/status-emojis.d.ts +10 -3
  223. package/dist/status-emojis.js +11 -2
  224. package/dist/status-emojis.js.map +1 -1
  225. package/dist/systemd-peer-bus.d.ts +95 -0
  226. package/dist/systemd-peer-bus.js +430 -0
  227. package/dist/systemd-peer-bus.js.map +1 -0
  228. package/dist/systemd-runtime.d.ts +27 -1
  229. package/dist/systemd-runtime.js +69 -7
  230. package/dist/systemd-runtime.js.map +1 -1
  231. package/dist/tips.js +4 -4
  232. package/dist/tips.js.map +1 -1
  233. package/dist/tool-permissions.js +3 -3
  234. package/dist/tool-permissions.js.map +1 -1
  235. package/dist/tool-progress.js +3 -2
  236. package/dist/tool-progress.js.map +1 -1
  237. package/dist/topic-commands.d.ts +42 -2
  238. package/dist/topic-commands.js +101 -11
  239. package/dist/topic-commands.js.map +1 -1
  240. package/dist/transcript-delta-reader.d.ts +3 -3
  241. package/dist/transcript-delta-reader.js +8 -8
  242. package/dist/transcript-delta-reader.js.map +1 -1
  243. package/dist/transcript-jsonl.d.ts +80 -3
  244. package/dist/transcript-jsonl.js +147 -10
  245. package/dist/transcript-jsonl.js.map +1 -1
  246. package/dist/transcript-monitor.d.ts +19 -0
  247. package/dist/transcript-monitor.js +87 -15
  248. package/dist/transcript-monitor.js.map +1 -1
  249. package/dist/transcript-sources.d.ts +78 -0
  250. package/dist/transcript-sources.js +375 -64
  251. package/dist/transcript-sources.js.map +1 -1
  252. package/dist/transcript-turns.d.ts +15 -2
  253. package/dist/transcript-turns.js +15 -2
  254. package/dist/transcript-turns.js.map +1 -1
  255. package/dist/tunnel/cloudflared.d.ts +7 -15
  256. package/dist/tunnel/cloudflared.js +36 -1
  257. package/dist/tunnel/cloudflared.js.map +1 -1
  258. package/dist/tunnel/purpose-lane.d.ts +5 -1
  259. package/dist/tunnel/purpose-lane.js +14 -7
  260. package/dist/tunnel/purpose-lane.js.map +1 -1
  261. package/dist/tunnel/types.d.ts +5 -1
  262. package/dist/tunnel/types.js.map +1 -1
  263. package/dist/turn-resume.d.ts +2 -0
  264. package/dist/turn-resume.js +1 -1
  265. package/dist/turn-resume.js.map +1 -1
  266. package/dist/types.d.ts +25 -0
  267. package/dist/ui/chat-commands.js +1 -0
  268. package/dist/ui/chat-emojis.js +33 -0
  269. package/dist/ui/chat-mentions.js +63 -0
  270. package/dist/ui/chat-render.js +61 -4
  271. package/dist/ui/chat-store.js +37 -3
  272. package/dist/ui/chat-strings.js +40 -20
  273. package/dist/ui/chat-thread.js +111 -12
  274. package/dist/ui/chat-time.js +80 -0
  275. package/dist/ui/panel-chat.js +41 -10
  276. package/dist/ui/panel-details.js +2 -0
  277. package/dist/ui/panel-fleet.js +199 -57
  278. package/dist/ui/panel-log.js +428 -0
  279. package/dist/ui/panel-needs.js +6 -2
  280. package/dist/ui/panel-settings.js +184 -32
  281. package/dist/ui/preview-panel.js +55 -15
  282. package/dist/ui/preview-status.js +28 -0
  283. package/dist/ui/preview.js +44 -3
  284. package/dist/ui/settings-dialogs.js +7 -2
  285. package/dist/ui/settings-model.js +2 -1
  286. package/dist/ui/settings-strings.js +56 -4
  287. package/dist/ui/settings-token.js +1 -1
  288. package/dist/ui/shared/app-i18n.js +43 -17
  289. package/dist/ui/shared/app-prefs.js +250 -0
  290. package/dist/ui/shared/app-route.js +7 -2
  291. package/dist/ui/shared/app-shell.js +165 -8
  292. package/dist/ui/shared/app-store.js +5 -1
  293. package/dist/ui/shared/app-stream.js +26 -5
  294. package/dist/ui/shared/app.css +163 -22
  295. package/dist/ui/shared/app.js +11 -1
  296. package/dist/ui/shared/fonts/README.md +1 -1
  297. package/dist/ui/shared/header-tools.js +55 -5
  298. package/dist/ui/shared/instance-nav.js +241 -45
  299. package/dist/ui/shared/instance-switch.js +4 -4
  300. package/dist/ui/shared/panel-view.js +6 -0
  301. package/dist/ui/shared/signin.js +7 -7
  302. package/dist/ui/shared/theme.js +39 -7
  303. package/dist/ui/shared/tokens.css +1 -1
  304. package/dist/ui/shared/ui-icons.js +1 -0
  305. package/dist/ui/shared/usage-dialog.js +23 -5
  306. package/dist/ui/shared/view-strings.js +32 -4
  307. package/dist/ui/signin.html +1 -1
  308. package/dist/unrecognised-prompt.d.ts +23 -0
  309. package/dist/unrecognised-prompt.js +74 -0
  310. package/dist/unrecognised-prompt.js.map +1 -0
  311. package/dist/update-check.d.ts +31 -0
  312. package/dist/update-check.js +47 -0
  313. package/dist/update-check.js.map +1 -1
  314. package/dist/update-install.d.ts +28 -2
  315. package/dist/update-install.js +22 -4
  316. package/dist/update-install.js.map +1 -1
  317. package/dist/update-launch.d.ts +46 -1
  318. package/dist/update-launch.js +146 -3
  319. package/dist/update-launch.js.map +1 -1
  320. package/dist/update-marker.d.ts +51 -0
  321. package/dist/update-marker.js +61 -0
  322. package/dist/update-marker.js.map +1 -1
  323. package/dist/update-preflight.d.ts +100 -0
  324. package/dist/update-preflight.js +264 -0
  325. package/dist/update-preflight.js.map +1 -0
  326. package/dist/updater-caller.d.ts +31 -0
  327. package/dist/updater-caller.js +109 -0
  328. package/dist/updater-caller.js.map +1 -0
  329. package/dist/usage/i18n-keys.d.ts +1 -1
  330. package/dist/usage/i18n-keys.js +1 -1
  331. package/dist/usage/i18n-keys.js.map +1 -1
  332. package/dist/usage/providers.d.ts +34 -5
  333. package/dist/usage/providers.js +160 -27
  334. package/dist/usage/providers.js.map +1 -1
  335. package/dist/usage/statusline-usage.d.ts +6 -2
  336. package/dist/usage/statusline-usage.js +10 -4
  337. package/dist/usage/statusline-usage.js.map +1 -1
  338. package/dist/view-api.d.ts +1 -1
  339. package/dist/view-api.js +38 -8
  340. package/dist/view-api.js.map +1 -1
  341. package/dist/wake-coordinator.d.ts +11 -0
  342. package/dist/wake-coordinator.js +38 -4
  343. package/dist/wake-coordinator.js.map +1 -1
  344. package/dist/web-api.d.ts +89 -4
  345. package/dist/web-api.js +551 -45
  346. package/dist/web-api.js.map +1 -1
  347. package/dist/web-auth.d.ts +2 -2
  348. package/dist/web-auth.js +9 -4
  349. package/dist/web-auth.js.map +1 -1
  350. package/dist/web-chat-history.d.ts +12 -1
  351. package/dist/web-chat-history.js +29 -7
  352. package/dist/web-chat-history.js.map +1 -1
  353. package/dist/web-commands.d.ts +27 -4
  354. package/dist/web-commands.js +19 -4
  355. package/dist/web-commands.js.map +1 -1
  356. package/dist/web-host-guard.d.ts +3 -11
  357. package/dist/web-host-guard.js +3 -12
  358. package/dist/web-host-guard.js.map +1 -1
  359. package/dist/web-images.d.ts +47 -0
  360. package/dist/web-images.js +169 -0
  361. package/dist/web-images.js.map +1 -0
  362. package/dist/web-login.d.ts +14 -0
  363. package/dist/web-login.js +4 -2
  364. package/dist/web-login.js.map +1 -1
  365. package/dist/web-mentions.d.ts +44 -0
  366. package/dist/web-mentions.js +75 -0
  367. package/dist/web-mentions.js.map +1 -0
  368. package/dist/web-prefs.d.ts +81 -0
  369. package/dist/web-prefs.js +297 -0
  370. package/dist/web-prefs.js.map +1 -0
  371. package/dist/web-preview.d.ts +14 -1
  372. package/dist/web-preview.js +35 -13
  373. package/dist/web-preview.js.map +1 -1
  374. package/dist/web-profile.d.ts +32 -0
  375. package/dist/web-profile.js +132 -0
  376. package/dist/web-profile.js.map +1 -0
  377. package/dist/web-reply-files.d.ts +45 -0
  378. package/dist/web-reply-files.js +223 -0
  379. package/dist/web-reply-files.js.map +1 -0
  380. package/dist/web-schedules.d.ts +12 -0
  381. package/dist/web-schedules.js +34 -0
  382. package/dist/web-schedules.js.map +1 -0
  383. package/dist/web-server-drain.d.ts +10 -0
  384. package/dist/web-server-drain.js +50 -0
  385. package/dist/web-server-drain.js.map +1 -0
  386. package/dist/web-session.d.ts +50 -1
  387. package/dist/web-session.js +63 -6
  388. package/dist/web-session.js.map +1 -1
  389. package/dist/web-settings-patch.d.ts +23 -0
  390. package/dist/web-settings-patch.js +77 -0
  391. package/dist/web-settings-patch.js.map +1 -0
  392. package/dist/web-shell-routes.d.ts +5 -1
  393. package/dist/web-shell-routes.js +10 -1
  394. package/dist/web-shell-routes.js.map +1 -1
  395. package/dist/web-upload.d.ts +17 -4
  396. package/dist/web-upload.js +64 -13
  397. package/dist/web-upload.js.map +1 -1
  398. package/dist/workflow-templates/default.md +1 -1
  399. package/package.json +1 -1
  400. package/templates/launchd.plist.ejs +3 -1
  401. package/templates/systemd.service.ejs +2 -1
@@ -0,0 +1,44 @@
1
+ /**
2
+ * #1682: the names the web chat shows for Discord mentions (`<@id>`, `<@&id>`, `<#id>`) — design: the comment on #1682.
3
+ *
4
+ * Asked by id, a few at a time (GET /ui/mentions?u=…&r=…&c=…: the ids a page found in what it rendered), answered
5
+ * without any Discord request: the fleet's own bots (their instance's label — fleet.yaml and the View profiles in
6
+ * profiles.db), the people seen on inbound messages (a bounded LRU), and what the Discord adapters already hold in
7
+ * their caches. A page never gets a directory: only the names of the ids it asked for.
8
+ */
9
+ export type MentionKind = "u" | "r" | "c";
10
+ export interface MentionQuery {
11
+ u: string[];
12
+ r: string[];
13
+ c: string[];
14
+ }
15
+ export type MentionNames = {
16
+ u: Record<string, string>;
17
+ r: Record<string, string>;
18
+ c: Record<string, string>;
19
+ };
20
+ /** At most this many ids in one request (all kinds together). */
21
+ export declare const MAX_MENTION_IDS = 100;
22
+ /** People remembered from inbound messages. */
23
+ export declare const MAX_SEEN_USERS = 1000;
24
+ /** `?u=1,2&r=3&c=4` → the ids, or why not: digits only (a Discord id), at most MAX_MENTION_IDS, no other parameter. */
25
+ export declare function parseMentionQuery(params: URLSearchParams): {
26
+ ok: true;
27
+ query: MentionQuery;
28
+ } | {
29
+ ok: false;
30
+ error: string;
31
+ };
32
+ /** The people seen on inbound messages: id → the name they wrote under, most recent last; the oldest go first. */
33
+ export declare class SeenUsers {
34
+ private readonly max;
35
+ private readonly names;
36
+ constructor(max?: number);
37
+ record(id: string, name: string): void;
38
+ get(id: string): string | undefined;
39
+ get size(): number;
40
+ }
41
+ /** One source of names: what it knows for the asked ids (leaving out what it does not). */
42
+ export type MentionSource = (query: MentionQuery) => Partial<MentionNames>;
43
+ /** The names for a query from the sources, in order: the first that knows an id names it. Own keys only. */
44
+ export declare function resolveMentions(query: MentionQuery, sources: MentionSource[]): MentionNames;
@@ -0,0 +1,75 @@
1
+ /** At most this many ids in one request (all kinds together). */
2
+ export const MAX_MENTION_IDS = 100;
3
+ /** People remembered from inbound messages. */
4
+ export const MAX_SEEN_USERS = 1000;
5
+ const SNOWFLAKE = /^\d{15,25}$/;
6
+ /** `?u=1,2&r=3&c=4` → the ids, or why not: digits only (a Discord id), at most MAX_MENTION_IDS, no other parameter. */
7
+ export function parseMentionQuery(params) {
8
+ const query = { u: [], r: [], c: [] };
9
+ let total = 0;
10
+ for (const key of params.keys())
11
+ if (key !== "u" && key !== "r" && key !== "c")
12
+ return { ok: false, error: `unknown parameter: ${key.slice(0, 16)}` };
13
+ for (const kind of ["u", "r", "c"]) {
14
+ for (const raw of params.getAll(kind)) {
15
+ for (const id of raw.split(",")) {
16
+ if (!id)
17
+ continue;
18
+ if (!SNOWFLAKE.test(id))
19
+ return { ok: false, error: "ids are Discord ids (digits)" };
20
+ if (!query[kind].includes(id)) {
21
+ query[kind].push(id);
22
+ total++;
23
+ }
24
+ }
25
+ }
26
+ }
27
+ if (total > MAX_MENTION_IDS)
28
+ return { ok: false, error: `at most ${MAX_MENTION_IDS} ids` };
29
+ return { ok: true, query };
30
+ }
31
+ /** The people seen on inbound messages: id → the name they wrote under, most recent last; the oldest go first. */
32
+ export class SeenUsers {
33
+ max;
34
+ names = new Map();
35
+ constructor(max = MAX_SEEN_USERS) {
36
+ this.max = max;
37
+ }
38
+ record(id, name) {
39
+ if (!SNOWFLAKE.test(id) || typeof name !== "string" || !name.trim())
40
+ return;
41
+ this.names.delete(id);
42
+ this.names.set(id, name.trim().slice(0, 100));
43
+ while (this.names.size > this.max)
44
+ this.names.delete(this.names.keys().next().value);
45
+ }
46
+ get(id) { return this.names.get(id); }
47
+ get size() { return this.names.size; }
48
+ }
49
+ /** The names for a query from the sources, in order: the first that knows an id names it. Own keys only. */
50
+ export function resolveMentions(query, sources) {
51
+ const out = { u: Object.create(null), r: Object.create(null), c: Object.create(null) };
52
+ for (const source of sources) {
53
+ let found;
54
+ try {
55
+ found = source(query);
56
+ }
57
+ catch {
58
+ continue;
59
+ }
60
+ for (const kind of ["u", "r", "c"]) {
61
+ const got = found[kind];
62
+ if (!got)
63
+ continue;
64
+ for (const id of query[kind]) {
65
+ if (Object.hasOwn(out[kind], id) || !Object.hasOwn(got, id))
66
+ continue;
67
+ const name = got[id];
68
+ if (typeof name === "string" && name.trim())
69
+ out[kind][id] = name.trim().slice(0, 100);
70
+ }
71
+ }
72
+ }
73
+ return { u: { ...out.u }, r: { ...out.r }, c: { ...out.c } };
74
+ }
75
+ //# sourceMappingURL=web-mentions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"web-mentions.js","sourceRoot":"","sources":["../src/web-mentions.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,81 @@
1
+ export declare const WEB_PREFS_FILE = "web-prefs.json";
2
+ export declare const OPERATOR = "operator";
3
+ /** #1664: at most this many favorites. */
4
+ export declare const MAX_FAVORITES = 50;
5
+ export declare const WEB_PREF_KEYS: readonly string[];
6
+ export declare function isWebPrefKey(key: string): boolean;
7
+ export interface WebPrefsView {
8
+ /** Every key that is set, and its value (a key not set has its default: the page's own). */
9
+ readonly values: Record<string, unknown>;
10
+ readonly rev: number;
11
+ /** The file could not be read: these are the defaults, and writes are refused until it is fixed. */
12
+ readonly unreadable?: true;
13
+ }
14
+ export type WebPrefsWrite = {
15
+ ok: true;
16
+ view: WebPrefsView;
17
+ changed: string[];
18
+ } | {
19
+ ok: false;
20
+ status: 400 | 503;
21
+ error: string;
22
+ };
23
+ /**
24
+ * A request's `set`: own keys only, each known, each valid — or the first reason it is not. All or nothing.
25
+ * `exists`: whether an instance of that name is in the fleet now (#1664: a favorite must be one).
26
+ */
27
+ export declare function parseWebPrefsPatch(set: unknown, exists?: (name: string) => boolean): {
28
+ ok: true;
29
+ patch: Map<string, unknown>;
30
+ } | {
31
+ ok: false;
32
+ error: string;
33
+ };
34
+ export declare class WebPrefsStore {
35
+ private readonly dir;
36
+ private readonly opts;
37
+ private state;
38
+ /**
39
+ * `exists` (#1664): whether an instance of that name is in the fleet now — a favorite that is not is never served,
40
+ * and is never accepted.
41
+ */
42
+ constructor(dir: string, opts?: {
43
+ now?: () => number;
44
+ write?: (path: string, data: string) => void;
45
+ exists?: (name: string) => boolean;
46
+ });
47
+ /** parseWebPrefsPatch against this fleet. */
48
+ parsePatch(set: unknown): ReturnType<typeof parseWebPrefsPatch>;
49
+ get path(): string;
50
+ private load;
51
+ read(identity?: string): WebPrefsView;
52
+ rev(): number;
53
+ /** #1664: an instance was removed — drop it from every favorites list (a write, and a new rev, only if one had it). */
54
+ forgetInstance(name: string): void;
55
+ private viewOf;
56
+ /**
57
+ * Set (or, with null, clear) the patch's keys. `onlyIfUnset`: a device's first upload of what it had locally — only
58
+ * the keys the host does not have yet are taken, so the first device to arrive wins and the host wins from then on.
59
+ */
60
+ write(identity: string, patch: Map<string, unknown>, opts?: {
61
+ onlyIfUnset?: boolean;
62
+ }): WebPrefsWrite;
63
+ /** #1827: a web person's profile as kept (checked again where it is used); null while the file cannot be read. */
64
+ profile(identity: string): WebProfile | null;
65
+ /** #1827: set (or, with null, clear) a person's own name — already validated by the caller (web-profile.ts). */
66
+ setProfileName(identity: string, name: string | null): {
67
+ ok: true;
68
+ } | {
69
+ ok: false;
70
+ status: 400 | 503;
71
+ error: string;
72
+ };
73
+ /** #1827: their account's display name at a sign-in (a change is a write and a new rev; the same, nothing). */
74
+ setPlatformName(identity: string, name: string): void;
75
+ }
76
+ export interface WebProfile {
77
+ /** The name they chose (as stored); absent: not set — their platform name is used. */
78
+ readonly name?: string;
79
+ /** Their account's display name at their last sign-in with a code. */
80
+ readonly platformName?: string;
81
+ }
@@ -0,0 +1,297 @@
1
+ /**
2
+ * #1665: the web preferences that follow the person from device to device (theme, language, the sidebar's arrangement,
3
+ * the HTML-preview opt-in), kept on the host — design: the comment on #1665.
4
+ *
5
+ * - The tour and the first-run card stay per device (the user's call on #1665): they are not keys here.
6
+ * - One bucket per web identity. There is one today: every web session is the operator's (SessionRecord has no
7
+ * owner), so the file is keyed by identity and holds "operator" only; a per-owner key can come later unmigrated.
8
+ * - A fixed registry of keys, each with its own validator. Anything else is refused — "__proto__" and "constructor"
9
+ * are just unknown keys: every lookup is a Map, never an object's prototype chain.
10
+ * - The file (<home>/web-prefs.json, 0600) is replaced atomically. One that cannot be read or parsed is never
11
+ * overwritten: reads give the defaults, writes are refused, until a person looks at it.
12
+ * - `rev` rises with every change; the status frame carries it, so another device re-reads at its next poll.
13
+ */
14
+ import { readFileSync } from "node:fs";
15
+ import { join } from "node:path";
16
+ import { atomicWriteFileSync } from "./atomic-write.js";
17
+ export const WEB_PREFS_FILE = "web-prefs.json";
18
+ export const OPERATOR = "operator";
19
+ const bad = (invalid) => ({ invalid });
20
+ const isInvalid = (v) => !!v && typeof v === "object" && Object.hasOwn(v, "invalid") && Object.keys(v).length === 1;
21
+ const plain = (v) => !!v && typeof v === "object" && !Array.isArray(v) && Object.getPrototypeOf(v) === Object.prototype;
22
+ const shortText = (v, max) => typeof v === "string" && v.length <= max;
23
+ const name = (v) => typeof v === "string" && v.length >= 1 && v.length <= 128 && !/[\u0000-\u001f\u007f]/.test(v);
24
+ /** Only the listed fields, each present: an extra field is a value this version does not understand. */
25
+ function exactly(v, fields) {
26
+ const keys = Object.keys(v);
27
+ return keys.length === fields.length && fields.every((f) => Object.hasOwn(v, f));
28
+ }
29
+ function uniqueStrings(v, max, each, what) {
30
+ if (!Array.isArray(v) || v.length > max)
31
+ return bad(`${what}: a list of at most ${max}`);
32
+ if (!v.every(each))
33
+ return bad(`${what}: each a name of 1–128 characters`);
34
+ if (new Set(v).size !== v.length)
35
+ return bad(`${what}: no repeats`);
36
+ return v;
37
+ }
38
+ /** #1827: a web person's profile keys (internal: not settable through /ui/prefs, not in its view). */
39
+ const PROFILE_NAME = "profile.name";
40
+ const PROFILE_PLATFORM_NAME = "profile.platformName";
41
+ const PROFILE_KEYS = new Set([PROFILE_NAME, PROFILE_PLATFORM_NAME]);
42
+ /** #1664: at most this many favorites. */
43
+ export const MAX_FAVORITES = 50;
44
+ const FAVORITES = "nav.favorites";
45
+ /** The status chips of the sidebar filter (src/ui/shared/instance-nav.js STATUS_FACETS). */
46
+ const STATUS_FACETS = new Set(["working", "needs", "idle", "stopped", "crashed"]);
47
+ /** Each key's validator: the value to store, or why it is refused. `null` (clear) never reaches one. */
48
+ const REGISTRY = new Map([
49
+ ["theme", (v) => (v === "light" || v === "dark" || v === "system" ? v : bad("theme: light, dark or system"))],
50
+ ["lang", (v) => (v === "en" || v === "zh-TW" ? v : bad("lang: en or zh-TW"))],
51
+ // The sidebar's drag order: the rows instance-nav.js keeps (group rows have no group_name).
52
+ ["nav.order", (v) => {
53
+ if (!Array.isArray(v) || v.length > 1000)
54
+ return bad("nav.order: a list of at most 1000 rows");
55
+ for (const row of v) {
56
+ if (!plain(row) || !exactly(row, ["item_type", "item_name", "sort_index", "group_name"]))
57
+ return bad("nav.order: rows of item_type, item_name, sort_index, group_name");
58
+ const groupRow = row.item_type === "group";
59
+ if (!groupRow && row.item_type !== "instance")
60
+ return bad("nav.order: item_type group or instance");
61
+ if (!name(row.item_name))
62
+ return bad("nav.order: item_name of 1–128 characters");
63
+ if (!Number.isSafeInteger(row.sort_index) || row.sort_index < 0)
64
+ return bad("nav.order: sort_index a whole number from 0");
65
+ if (groupRow ? row.group_name !== null : !name(row.group_name))
66
+ return bad("nav.order: group_name null for a group, the group's name for an instance");
67
+ }
68
+ return v;
69
+ }],
70
+ ["nav.collapsed", (v) => uniqueStrings(v, 200, name, "nav.collapsed")],
71
+ ["nav.filter", (v) => {
72
+ if (!plain(v) || !exactly(v, ["q", "status", "cli"]))
73
+ return bad("nav.filter: { q, status, cli }");
74
+ if (!shortText(v.q, 200))
75
+ return bad("nav.filter: q at most 200 characters");
76
+ const status = uniqueStrings(v.status, STATUS_FACETS.size, (s) => typeof s === "string" && STATUS_FACETS.has(s), "nav.filter.status");
77
+ if (isInvalid(status))
78
+ return status;
79
+ const cli = uniqueStrings(v.cli, 32, (s) => typeof s === "string" && /^[a-z0-9][a-z0-9_-]{0,31}$/.test(s), "nav.filter.cli");
80
+ if (isInvalid(cli))
81
+ return cli;
82
+ return { q: v.q, status, cli };
83
+ }],
84
+ // #1664: the starred instances, in the order they were starred (or moved). Whether each one exists is checked where
85
+ // the fleet is known (parseWebPrefsPatch's `exists`); a deleted one is dropped on read and when it is removed.
86
+ [FAVORITES, (v) => uniqueStrings(v, MAX_FAVORITES, name, FAVORITES)],
87
+ // HTML previews allowed. Who may write it at all is the route's business (a signed-in session, never the public link).
88
+ ["preview.optIn", (v) => (typeof v === "boolean" ? v : bad("preview.optIn: true or false"))],
89
+ // #1722: which name leads where the web shows an instance — its nickname (display name) or its real instance name.
90
+ ["names.primary", (v) => (v === "nickname" || v === "instance" ? v : bad("names.primary: nickname or instance"))],
91
+ // #1827: a web person's own profile — the name they chose, and their account's display name at their last sign-in.
92
+ // Kept here so it follows the person (their bucket), but never a /ui/prefs key: read and written only through
93
+ // profile() / setProfileName() / setPlatformName(), and checked in full where it is used (web-profile.ts). The file's
94
+ // own check is only its shape.
95
+ [PROFILE_NAME, (v) => (typeof v === "string" && v.length >= 1 && v.length <= 256 ? v : bad("profile.name"))],
96
+ [PROFILE_PLATFORM_NAME, (v) => (typeof v === "string" && v.length >= 1 && v.length <= 256 ? v : bad("profile.platformName"))],
97
+ ]);
98
+ export const WEB_PREF_KEYS = [...REGISTRY.keys()].filter((k) => !PROFILE_KEYS.has(k));
99
+ export function isWebPrefKey(key) { return REGISTRY.has(key) && !PROFILE_KEYS.has(key); }
100
+ /**
101
+ * A request's `set`: own keys only, each known, each valid — or the first reason it is not. All or nothing.
102
+ * `exists`: whether an instance of that name is in the fleet now (#1664: a favorite must be one).
103
+ */
104
+ export function parseWebPrefsPatch(set, exists) {
105
+ if (!plain(set))
106
+ return { ok: false, error: "set: an object of preference keys" };
107
+ const patch = new Map();
108
+ const keys = Object.keys(set);
109
+ if (!keys.length)
110
+ return { ok: false, error: "set: at least one preference" };
111
+ for (const key of keys) {
112
+ const validate = PROFILE_KEYS.has(key) ? undefined : REGISTRY.get(key);
113
+ if (!validate)
114
+ return { ok: false, error: `unknown_pref: ${key.slice(0, 64)}` };
115
+ const raw = set[key];
116
+ if (raw === null) {
117
+ patch.set(key, null);
118
+ continue;
119
+ }
120
+ const value = validate(raw);
121
+ if (isInvalid(value))
122
+ return { ok: false, error: value.invalid };
123
+ if (key === FAVORITES) {
124
+ const unknown = value.find((n) => !exists || !exists(n));
125
+ if (unknown !== undefined)
126
+ return { ok: false, error: `unknown_instance: ${unknown.slice(0, 64)}` };
127
+ }
128
+ patch.set(key, value);
129
+ }
130
+ return { ok: true, patch };
131
+ }
132
+ export class WebPrefsStore {
133
+ dir;
134
+ opts;
135
+ state = null;
136
+ /**
137
+ * `exists` (#1664): whether an instance of that name is in the fleet now — a favorite that is not is never served,
138
+ * and is never accepted.
139
+ */
140
+ constructor(dir, opts = {}) {
141
+ this.dir = dir;
142
+ this.opts = opts;
143
+ }
144
+ /** parseWebPrefsPatch against this fleet. */
145
+ parsePatch(set) { return parseWebPrefsPatch(set, this.opts.exists); }
146
+ get path() { return join(this.dir, WEB_PREFS_FILE); }
147
+ load() {
148
+ // A store that could not read its file tries again next time (a permission put back, a disk that recovered); one
149
+ // that did keeps what it read.
150
+ if (this.state && this.state !== "unreadable")
151
+ return this.state;
152
+ const empty = { buckets: new Map(), rev: 0 };
153
+ // Only a file that is definitely not there (ENOENT) is a new, empty store. Anything else — a read refused (EACCES),
154
+ // an I/O error, no place for the file at all, content of another shape — is "unreadable": the defaults are served
155
+ // and nothing is written, so the file is never replaced and a page or status frame never fails because of it.
156
+ try {
157
+ let raw;
158
+ try {
159
+ raw = readFileSync(this.path, "utf8");
160
+ }
161
+ catch (err) {
162
+ if (err?.code === "ENOENT")
163
+ return (this.state = empty);
164
+ return (this.state = "unreadable");
165
+ }
166
+ const doc = JSON.parse(raw);
167
+ if (!plain(doc) || doc.version !== 1 || !plain(doc.identities) || !Number.isSafeInteger(doc.rev))
168
+ return (this.state = "unreadable");
169
+ const buckets = new Map();
170
+ for (const id of Object.keys(doc.identities)) {
171
+ const raw = doc.identities[id];
172
+ if (!plain(raw) || !plain(raw.values) || !plain(raw.updated))
173
+ return (this.state = "unreadable");
174
+ const bucket = { values: new Map(), updated: new Map() };
175
+ // A key this version no longer knows, or a value it would refuse now, is left out (never served).
176
+ for (const key of Object.keys(raw.values)) {
177
+ const validate = REGISTRY.get(key);
178
+ if (!validate)
179
+ continue;
180
+ const value = validate(raw.values[key]);
181
+ if (isInvalid(value))
182
+ continue;
183
+ bucket.values.set(key, value);
184
+ const at = raw.updated[key];
185
+ bucket.updated.set(key, typeof at === "number" && Number.isFinite(at) ? at : 0);
186
+ }
187
+ buckets.set(id, bucket);
188
+ }
189
+ return (this.state = { buckets, rev: doc.rev });
190
+ }
191
+ catch {
192
+ return (this.state = "unreadable");
193
+ }
194
+ }
195
+ read(identity = OPERATOR) {
196
+ const state = this.load();
197
+ if (state === "unreadable")
198
+ return { values: {}, rev: 0, unreadable: true };
199
+ return this.viewOf(state, identity);
200
+ }
201
+ rev() { const s = this.load(); return s === "unreadable" ? 0 : s.rev; }
202
+ /** #1664: an instance was removed — drop it from every favorites list (a write, and a new rev, only if one had it). */
203
+ forgetInstance(name) {
204
+ const state = this.load();
205
+ if (state === "unreadable")
206
+ return;
207
+ for (const [identity, bucket] of state.buckets) {
208
+ const list = bucket.values.get(FAVORITES);
209
+ if (list && list.includes(name))
210
+ this.write(identity, new Map([[FAVORITES, list.filter((n) => n !== name)]]));
211
+ }
212
+ }
213
+ viewOf(state, identity) {
214
+ const values = Object.create(null);
215
+ const bucket = state.buckets.get(identity);
216
+ if (bucket)
217
+ for (const [k, v] of bucket.values)
218
+ if (!PROFILE_KEYS.has(k))
219
+ values[k] = v;
220
+ // #1664: a favorite whose instance was deleted is dropped (paused or stopped ones are still instances: kept).
221
+ if (Object.hasOwn(values, FAVORITES)) {
222
+ const exists = this.opts.exists;
223
+ values[FAVORITES] = values[FAVORITES].filter((n) => !!exists && exists(n));
224
+ }
225
+ return { values: { ...values }, rev: state.rev };
226
+ }
227
+ /**
228
+ * Set (or, with null, clear) the patch's keys. `onlyIfUnset`: a device's first upload of what it had locally — only
229
+ * the keys the host does not have yet are taken, so the first device to arrive wins and the host wins from then on.
230
+ */
231
+ write(identity, patch, opts = {}) {
232
+ const state = this.load();
233
+ if (state === "unreadable")
234
+ return { ok: false, status: 503, error: "prefs_unreadable" };
235
+ const prior = state.buckets.get(identity);
236
+ const next = { values: new Map(prior?.values), updated: new Map(prior?.updated) };
237
+ const at = (this.opts.now ?? Date.now)();
238
+ const changed = [];
239
+ for (const [key, value] of patch) {
240
+ if (opts.onlyIfUnset && next.values.has(key))
241
+ continue;
242
+ if (value === null) {
243
+ if (!next.values.has(key))
244
+ continue;
245
+ next.values.delete(key);
246
+ next.updated.delete(key);
247
+ }
248
+ else {
249
+ if (next.values.has(key) && JSON.stringify(next.values.get(key)) === JSON.stringify(value))
250
+ continue;
251
+ next.values.set(key, value);
252
+ next.updated.set(key, at);
253
+ }
254
+ changed.push(key);
255
+ }
256
+ if (!changed.length)
257
+ return { ok: true, view: this.viewOf(state, identity), changed };
258
+ const buckets = new Map(state.buckets);
259
+ buckets.set(identity, next);
260
+ const candidate = { buckets, rev: state.rev + 1 };
261
+ (this.opts.write ?? ((path, data) => atomicWriteFileSync(path, data, { mode: 0o600 })))(this.path, serialize(candidate));
262
+ // Only once it is on disk: a failed write leaves memory as the file is.
263
+ this.state = candidate;
264
+ return { ok: true, view: this.viewOf(candidate, identity), changed };
265
+ }
266
+ /** #1827: a web person's profile as kept (checked again where it is used); null while the file cannot be read. */
267
+ profile(identity) {
268
+ const state = this.load();
269
+ if (state === "unreadable")
270
+ return null;
271
+ const values = state.buckets.get(identity)?.values;
272
+ const name = values?.get(PROFILE_NAME);
273
+ const platformName = values?.get(PROFILE_PLATFORM_NAME);
274
+ return { ...(name !== undefined ? { name } : {}), ...(platformName !== undefined ? { platformName } : {}) };
275
+ }
276
+ /** #1827: set (or, with null, clear) a person's own name — already validated by the caller (web-profile.ts). */
277
+ setProfileName(identity, name) {
278
+ const w = this.write(identity, new Map([[PROFILE_NAME, name]]));
279
+ return w.ok ? { ok: true } : w;
280
+ }
281
+ /** #1827: their account's display name at a sign-in (a change is a write and a new rev; the same, nothing). */
282
+ setPlatformName(identity, name) {
283
+ const value = name.slice(0, 256);
284
+ if (value)
285
+ this.write(identity, new Map([[PROFILE_PLATFORM_NAME, value]]));
286
+ }
287
+ }
288
+ function serialize(state) {
289
+ const identities = {};
290
+ for (const [id, b] of state.buckets) {
291
+ identities[id] = { values: Object.fromEntries(b.values), updated: Object.fromEntries(b.updated) };
292
+ }
293
+ // Object.fromEntries with a "__proto__" entry defines an own property, but no such key ever gets in: ids are ours, keys
294
+ // are the registry's.
295
+ return JSON.stringify({ version: 1, rev: state.rev, identities }, null, 2) + "\n";
296
+ }
297
+ //# sourceMappingURL=web-prefs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"web-prefs.js","sourceRoot":"","sources":["../src/web-prefs.ts"],"names":[],"mappings":"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"}
@@ -40,7 +40,7 @@ export interface PreviewAvailability {
40
40
  * previewAvailability; `publicLink` and `notOffered` from the app shell, which asks it for neither the public link nor
41
41
  * a page that is not the full app. One list: the page's dictionary is checked against it.
42
42
  */
43
- export declare const PREVIEW_OFF_CODES: readonly ["fleetOff", "unchecked", "noPort", "notListed", "needOrigin", "needHttps", "sameOrigin", "publicLink", "notOffered"];
43
+ export declare const PREVIEW_OFF_CODES: readonly ["fleetOff", "unchecked", "noPort", "notListed", "needOrigin", "needHttps", "sameOrigin", "publicLink", "publicLinkStarting", "publicLinkPreviewFailed", "notOffered"];
44
44
  export type PreviewOffCode = typeof PREVIEW_OFF_CODES[number];
45
45
  /** scheme://host[:port] from a Host header the host guard already accepted; a default port is dropped. */
46
46
  export declare function dashboardOriginFor(hostHeader: string | undefined, secure: boolean): string | null;
@@ -78,9 +78,22 @@ export interface PreviewListener {
78
78
  * it does not answer to → empty 403. No X-Frame-Options (it must be framable by the dashboard), no cookie or
79
79
  * header parsing beyond Host, nothing logged per request. Built once: the shim and its headers never change.
80
80
  */
81
+ /**
82
+ * The /frame response for one set of dashboard origins (design §5.1): the shim and its exact headers. Shared by the
83
+ * local preview listener and the public link's own preview gateway (#1723), so both serve the very same frame.
84
+ */
85
+ export declare function previewFrameResponse(bootId: string, origins: readonly string[]): {
86
+ body: Buffer;
87
+ headers: Record<string, string>;
88
+ };
81
89
  export declare function createPreviewListener(opts: {
82
90
  settings: PreviewSettings;
83
91
  healthPort: number;
84
92
  config: HostGuardConfig | null | undefined;
85
93
  bootId?: string;
94
+ /**
95
+ * #1723: dashboard origins that may frame the preview for now — the open public link's, when a web.preview_origin is
96
+ * configured (it is used instead of a preview tunnel). Asked per request; never cached past the answer.
97
+ */
98
+ extraOrigins?(): readonly string[];
86
99
  }): PreviewListener;
@@ -67,7 +67,7 @@ export function previewOriginProblem(value, config) {
67
67
  * previewAvailability; `publicLink` and `notOffered` from the app shell, which asks it for neither the public link nor
68
68
  * a page that is not the full app. One list: the page's dictionary is checked against it.
69
69
  */
70
- export const PREVIEW_OFF_CODES = ["fleetOff", "unchecked", "noPort", "notListed", "needOrigin", "needHttps", "sameOrigin", "publicLink", "notOffered"];
70
+ export const PREVIEW_OFF_CODES = ["fleetOff", "unchecked", "noPort", "notListed", "needOrigin", "needHttps", "sameOrigin", "publicLink", "publicLinkStarting", "publicLinkPreviewFailed", "notOffered"];
71
71
  const DEFAULT_PORT = { "http:": "80", "https:": "443" };
72
72
  /** scheme://host[:port] from a Host header the host guard already accepted; a default port is dropped. */
73
73
  export function dashboardOriginFor(hostHeader, secure) {
@@ -203,19 +203,40 @@ export function buildShim(bootId, origins) {
203
203
  * it does not answer to → empty 403. No X-Frame-Options (it must be framable by the dashboard), no cookie or
204
204
  * header parsing beyond Host, nothing logged per request. Built once: the shim and its headers never change.
205
205
  */
206
+ /**
207
+ * The /frame response for one set of dashboard origins (design §5.1): the shim and its exact headers. Shared by the
208
+ * local preview listener and the public link's own preview gateway (#1723), so both serve the very same frame.
209
+ */
210
+ export function previewFrameResponse(bootId, origins) {
211
+ const body = Buffer.from(buildShim(bootId, origins), "utf8");
212
+ return {
213
+ body,
214
+ headers: {
215
+ "Content-Type": "text/html; charset=utf-8",
216
+ "Content-Security-Policy": previewFrameCsp(origins),
217
+ "Permissions-Policy": PERMISSIONS_POLICY,
218
+ "Referrer-Policy": "no-referrer",
219
+ "Cross-Origin-Resource-Policy": "same-origin",
220
+ "X-Content-Type-Options": "nosniff",
221
+ "Cache-Control": "no-store",
222
+ "Content-Length": String(body.length),
223
+ },
224
+ };
225
+ }
206
226
  export function createPreviewListener(opts) {
207
227
  const bootId = opts.bootId ?? randomBytes(16).toString("hex");
208
228
  const origins = dashboardOrigins(opts.settings, opts.healthPort, opts.config);
209
- const body = Buffer.from(buildShim(bootId, origins), "utf8");
210
- const headers = {
211
- "Content-Type": "text/html; charset=utf-8",
212
- "Content-Security-Policy": previewFrameCsp(origins),
213
- "Permissions-Policy": PERMISSIONS_POLICY,
214
- "Referrer-Policy": "no-referrer",
215
- "Cross-Origin-Resource-Policy": "same-origin",
216
- "X-Content-Type-Options": "nosniff",
217
- "Cache-Control": "no-store",
218
- "Content-Length": String(body.length),
229
+ const { body, headers } = previewFrameResponse(bootId, origins);
230
+ let widened = null;
231
+ const frameFor = () => {
232
+ const extra = (opts.extraOrigins?.() ?? []).filter(o => !origins.includes(o));
233
+ if (extra.length === 0)
234
+ return { body, headers };
235
+ const all = [...origins, ...extra];
236
+ const key = all.join(" ");
237
+ if (widened?.key !== key)
238
+ widened = { key, ...previewFrameResponse(bootId, all) };
239
+ return widened;
219
240
  };
220
241
  const hosts = new Set(LOOPBACK_HOST_NAMES);
221
242
  if (opts.settings.origin) {
@@ -234,8 +255,9 @@ export function createPreviewListener(opts) {
234
255
  return empty(res, 403);
235
256
  if (req.method !== "GET" || req.url !== "/frame")
236
257
  return empty(res, 404);
237
- res.writeHead(200, headers);
238
- res.end(body);
258
+ const frame = frameFor();
259
+ res.writeHead(200, frame.headers);
260
+ res.end(frame.body);
239
261
  });
240
262
  const settings = { ...opts.settings };
241
263
  // A listener on port 0 learns its real port when it binds: that is the one a /ui load must be offered.