@songsid/agend 2.2.1 → 2.2.6

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 (455) hide show
  1. package/dist/agent-cli-instructions.md +5 -2
  2. package/dist/agent-cli.js +284 -2
  3. package/dist/agent-cli.js.map +1 -1
  4. package/dist/agent-endpoint.d.ts +2 -0
  5. package/dist/agent-endpoint.js +29 -0
  6. package/dist/agent-endpoint.js.map +1 -1
  7. package/dist/agent-file-read.d.ts +38 -0
  8. package/dist/agent-file-read.js +317 -0
  9. package/dist/agent-file-read.js.map +1 -0
  10. package/dist/agent-whats-new.d.ts +11 -0
  11. package/dist/agent-whats-new.js +34 -2
  12. package/dist/agent-whats-new.js.map +1 -1
  13. package/dist/atomic-write.js +9 -1
  14. package/dist/atomic-write.js.map +1 -1
  15. package/dist/auth-api.d.ts +30 -1
  16. package/dist/auth-api.js +61 -12
  17. package/dist/auth-api.js.map +1 -1
  18. package/dist/authority/authority-state.d.ts +38 -0
  19. package/dist/authority/authority-state.js +114 -0
  20. package/dist/authority/authority-state.js.map +1 -0
  21. package/dist/authority/session-authorizations.d.ts +43 -0
  22. package/dist/authority/session-authorizations.js +102 -0
  23. package/dist/authority/session-authorizations.js.map +1 -0
  24. package/dist/authority/web-authority.d.ts +120 -0
  25. package/dist/authority/web-authority.js +157 -0
  26. package/dist/authority/web-authority.js.map +1 -0
  27. package/dist/backend/antigravity.js +3 -3
  28. package/dist/backend/antigravity.js.map +1 -1
  29. package/dist/backend/claude-code.d.ts +2 -2
  30. package/dist/backend/claude-code.js +10 -5
  31. package/dist/backend/claude-code.js.map +1 -1
  32. package/dist/backend/launch-effort.js +1 -1
  33. package/dist/backend/launch-effort.js.map +1 -1
  34. package/dist/backend/muse.js +10 -2
  35. package/dist/backend/muse.js.map +1 -1
  36. package/dist/backend/types.d.ts +14 -0
  37. package/dist/backend/types.js +25 -0
  38. package/dist/backend/types.js.map +1 -1
  39. package/dist/backend-availability.d.ts +120 -0
  40. package/dist/backend-availability.js +396 -0
  41. package/dist/backend-availability.js.map +1 -0
  42. package/dist/backend-command.d.ts +21 -3
  43. package/dist/backend-command.js +23 -4
  44. package/dist/backend-command.js.map +1 -1
  45. package/dist/bundled-skills.d.ts +14 -0
  46. package/dist/bundled-skills.js +29 -0
  47. package/dist/bundled-skills.js.map +1 -0
  48. package/dist/cache-analysis.d.ts +47 -1
  49. package/dist/cache-analysis.js +68 -8
  50. package/dist/cache-analysis.js.map +1 -1
  51. package/dist/cache-ledger.d.ts +16 -3
  52. package/dist/cache-ledger.js +33 -13
  53. package/dist/cache-ledger.js.map +1 -1
  54. package/dist/cache-report-tool.d.ts +17 -0
  55. package/dist/cache-report-tool.js +40 -0
  56. package/dist/cache-report-tool.js.map +1 -0
  57. package/dist/cache-service.d.ts +70 -3
  58. package/dist/cache-service.js +178 -17
  59. package/dist/cache-service.js.map +1 -1
  60. package/dist/channel/adapters/discord-client-retirement.d.ts +5 -0
  61. package/dist/channel/adapters/discord-client-retirement.js +57 -0
  62. package/dist/channel/adapters/discord-client-retirement.js.map +1 -0
  63. package/dist/channel/adapters/discord.d.ts +11 -0
  64. package/dist/channel/adapters/discord.js +66 -6
  65. package/dist/channel/adapters/discord.js.map +1 -1
  66. package/dist/channel/adapters/telegram.d.ts +8 -1
  67. package/dist/channel/adapters/telegram.js +74 -4
  68. package/dist/channel/adapters/telegram.js.map +1 -1
  69. package/dist/channel/mcp-tools.js +48 -2
  70. package/dist/channel/mcp-tools.js.map +1 -1
  71. package/dist/channel/types.d.ts +21 -0
  72. package/dist/classic-channel-manager.d.ts +10 -5
  73. package/dist/classic-channel-manager.js +12 -9
  74. package/dist/classic-channel-manager.js.map +1 -1
  75. package/dist/cli.js +142 -110
  76. package/dist/cli.js.map +1 -1
  77. package/dist/config-validator.js +119 -4
  78. package/dist/config-validator.js.map +1 -1
  79. package/dist/daemon.d.ts +8 -0
  80. package/dist/daemon.js +298 -22
  81. package/dist/daemon.js.map +1 -1
  82. package/dist/decision-list.d.ts +25 -0
  83. package/dist/decision-list.js +30 -0
  84. package/dist/decision-list.js.map +1 -0
  85. package/dist/delivery-outbox.d.ts +10 -0
  86. package/dist/delivery-outbox.js +12 -1
  87. package/dist/delivery-outbox.js.map +1 -1
  88. package/dist/fleet-context.d.ts +15 -0
  89. package/dist/fleet-manager.d.ts +514 -12
  90. package/dist/fleet-manager.js +1885 -163
  91. package/dist/fleet-manager.js.map +1 -1
  92. package/dist/fleet-process-handlers.d.ts +18 -0
  93. package/dist/fleet-process-handlers.js +69 -0
  94. package/dist/fleet-process-handlers.js.map +1 -0
  95. package/dist/fleet-traffic.d.ts +74 -0
  96. package/dist/fleet-traffic.js +196 -0
  97. package/dist/fleet-traffic.js.map +1 -0
  98. package/dist/general-knowledge/skills/credential-profiles/SKILL.md +20 -5
  99. package/dist/general-knowledge/skills/cross-instance-messaging/SKILL.md +6 -1
  100. package/dist/general-knowledge/skills/multi-channel/SKILL.md +3 -2
  101. package/dist/general-knowledge/skills/persona-emoji/SKILL.md +1 -1
  102. package/dist/general-knowledge/skills/web-appearance/SKILL.md +492 -0
  103. package/dist/general-knowledge/skills/web-dashboard/SKILL.md +482 -0
  104. package/dist/general-knowledge/skills/worker-collaboration/SKILL.md +1 -1
  105. package/dist/human-delivery-receipts.d.ts +24 -1
  106. package/dist/human-delivery-receipts.js +64 -11
  107. package/dist/human-delivery-receipts.js.map +1 -1
  108. package/dist/instance-avatar.js +3 -0
  109. package/dist/instance-avatar.js.map +1 -1
  110. package/dist/instance-config-impact.js +6 -1
  111. package/dist/instance-config-impact.js.map +1 -1
  112. package/dist/instance-lifecycle.d.ts +2 -0
  113. package/dist/instance-lifecycle.js +20 -10
  114. package/dist/instance-lifecycle.js.map +1 -1
  115. package/dist/instance-names.d.ts +4 -0
  116. package/dist/instance-names.js +12 -0
  117. package/dist/instance-names.js.map +1 -0
  118. package/dist/instructions.d.ts +12 -1
  119. package/dist/instructions.js +13 -2
  120. package/dist/instructions.js.map +1 -1
  121. package/dist/locale.js +118 -10
  122. package/dist/locale.js.map +1 -1
  123. package/dist/login-controller.js +35 -2
  124. package/dist/login-controller.js.map +1 -1
  125. package/dist/ls-attach-resolve.d.ts +95 -0
  126. package/dist/ls-attach-resolve.js +174 -0
  127. package/dist/ls-attach-resolve.js.map +1 -0
  128. package/dist/memory-hogs.d.ts +27 -0
  129. package/dist/memory-hogs.js +124 -0
  130. package/dist/memory-hogs.js.map +1 -0
  131. package/dist/needs-you-hub.d.ts +4 -0
  132. package/dist/needs-you-hub.js +31 -6
  133. package/dist/needs-you-hub.js.map +1 -1
  134. package/dist/needs-you.d.ts +1 -13
  135. package/dist/needs-you.js +3 -37
  136. package/dist/needs-you.js.map +1 -1
  137. package/dist/outbound-handlers.d.ts +15 -0
  138. package/dist/outbound-handlers.js +152 -11
  139. package/dist/outbound-handlers.js.map +1 -1
  140. package/dist/outbound-schemas.d.ts +228 -4
  141. package/dist/outbound-schemas.js +147 -1
  142. package/dist/outbound-schemas.js.map +1 -1
  143. package/dist/public-web-gateway.js +21 -1
  144. package/dist/public-web-gateway.js.map +1 -1
  145. package/dist/public-web-link.d.ts +9 -0
  146. package/dist/public-web-link.js +8 -0
  147. package/dist/public-web-link.js.map +1 -1
  148. package/dist/quickstart-api.d.ts +10 -1
  149. package/dist/quickstart-api.js +12 -4
  150. package/dist/quickstart-api.js.map +1 -1
  151. package/dist/reply-buttons-controller.d.ts +4 -0
  152. package/dist/reply-buttons-controller.js +2 -2
  153. package/dist/reply-buttons-controller.js.map +1 -1
  154. package/dist/repo-worktree-guard.d.ts +36 -0
  155. package/dist/repo-worktree-guard.js +143 -0
  156. package/dist/repo-worktree-guard.js.map +1 -0
  157. package/dist/restart-progress.d.ts +65 -1
  158. package/dist/restart-progress.js +236 -10
  159. package/dist/restart-progress.js.map +1 -1
  160. package/dist/rollout-index.d.ts +5 -0
  161. package/dist/rollout-index.js +26 -5
  162. package/dist/rollout-index.js.map +1 -1
  163. package/dist/scheduler/scheduler.d.ts +70 -1
  164. package/dist/scheduler/scheduler.js +256 -17
  165. package/dist/scheduler/scheduler.js.map +1 -1
  166. package/dist/service-installer.d.ts +2 -1
  167. package/dist/service-installer.js +18 -3
  168. package/dist/service-installer.js.map +1 -1
  169. package/dist/settings-api.d.ts +23 -0
  170. package/dist/settings-api.js +233 -2
  171. package/dist/settings-api.js.map +1 -1
  172. package/dist/settings-change.js +112 -0
  173. package/dist/settings-change.js.map +1 -1
  174. package/dist/settings-effect.js +32 -2
  175. package/dist/settings-effect.js.map +1 -1
  176. package/dist/settings-http-confirmation.js +5 -0
  177. package/dist/settings-http-confirmation.js.map +1 -1
  178. package/dist/tmux-manager.d.ts +5 -0
  179. package/dist/tmux-manager.js +16 -0
  180. package/dist/tmux-manager.js.map +1 -1
  181. package/dist/tool-permissions.d.ts +4 -6
  182. package/dist/tool-permissions.js +100 -2
  183. package/dist/tool-permissions.js.map +1 -1
  184. package/dist/topic-commands.d.ts +17 -7
  185. package/dist/topic-commands.js +39 -21
  186. package/dist/topic-commands.js.map +1 -1
  187. package/dist/topic-link.d.ts +128 -0
  188. package/dist/topic-link.js +269 -0
  189. package/dist/topic-link.js.map +1 -0
  190. package/dist/tunnel/cloudflared.js +37 -2
  191. package/dist/tunnel/cloudflared.js.map +1 -1
  192. package/dist/tunnel/types.d.ts +3 -1
  193. package/dist/tunnel/types.js.map +1 -1
  194. package/dist/types.d.ts +61 -1
  195. package/dist/ui/app-effects.js +245 -0
  196. package/dist/ui/chat-ambient.js +307 -0
  197. package/dist/ui/chat-effect-posters.js +42 -0
  198. package/dist/ui/chat-effects.js +160 -0
  199. package/dist/ui/chat-live-replies.js +58 -0
  200. package/dist/ui/chat-render.js +25 -7
  201. package/dist/ui/chat-store.js +41 -13
  202. package/dist/ui/chat-strings.js +86 -2
  203. package/dist/ui/chat-thread.js +204 -37
  204. package/dist/ui/chat-tour.js +1 -1
  205. package/dist/ui/effect-trial-controller.js +124 -0
  206. package/dist/ui/effect-trial-offer.js +31 -0
  207. package/dist/ui/effect-trial-panel.js +40 -0
  208. package/dist/ui/fleet-cache.js +23 -1
  209. package/dist/ui/fleet-canvas-layout.js +111 -0
  210. package/dist/ui/fleet-canvas.js +525 -0
  211. package/dist/ui/fleet-org.js +27 -12
  212. package/dist/ui/fleet-traffic.js +343 -0
  213. package/dist/ui/fleet-usage.js +401 -0
  214. package/dist/ui/image-lightbox.js +1 -1
  215. package/dist/ui/panel-chat.js +252 -58
  216. package/dist/ui/panel-details.js +20 -6
  217. package/dist/ui/panel-fleet.js +45 -19
  218. package/dist/ui/panel-log.js +6 -6
  219. package/dist/ui/panel-needs.js +3 -3
  220. package/dist/ui/panel-settings.js +58 -13
  221. package/dist/ui/preview-panel.js +25 -9
  222. package/dist/ui/preview.js +3 -2
  223. package/dist/ui/settings-appearance.js +349 -0
  224. package/dist/ui/settings-catalog.js +92 -0
  225. package/dist/ui/settings-dialogs.js +40 -12
  226. package/dist/ui/settings-effects.js +202 -0
  227. package/dist/ui/settings-guests.js +117 -0
  228. package/dist/ui/settings-learn.js +25 -0
  229. package/dist/ui/settings-model.js +15 -6
  230. package/dist/ui/settings-mods.js +271 -0
  231. package/dist/ui/settings-owner-effects.js +80 -0
  232. package/dist/ui/settings-strings.js +335 -10
  233. package/dist/ui/settings-web-authority.js +91 -0
  234. package/dist/ui/shared/agend-auth.js +28 -4
  235. package/dist/ui/shared/agent-widget.js +248 -0
  236. package/dist/ui/shared/app-i18n.js +6 -2
  237. package/dist/ui/shared/app-prefs.js +34 -13
  238. package/dist/ui/shared/app-route.js +42 -2
  239. package/dist/ui/shared/app-session.js +3 -1
  240. package/dist/ui/shared/app-shell.js +119 -41
  241. package/dist/ui/shared/app-store.js +28 -1
  242. package/dist/ui/shared/app-stream.js +14 -7
  243. package/dist/ui/shared/app-themes.js +206 -0
  244. package/dist/ui/shared/app.css +566 -5
  245. package/dist/ui/shared/app.js +23 -10
  246. package/dist/ui/shared/card-strings.js +32 -0
  247. package/dist/ui/shared/effect-app-runtime.js +297 -0
  248. package/dist/ui/shared/effect-baseline.js +64 -0
  249. package/dist/ui/shared/effect-chat-runtime.js +167 -0
  250. package/dist/ui/shared/effect-cursor.js +125 -0
  251. package/dist/ui/shared/effect-frame.js +146 -0
  252. package/dist/ui/shared/effect-geometry.js +137 -0
  253. package/dist/ui/shared/effect-host-style.js +10 -0
  254. package/dist/ui/shared/effect-owner-regions.js +74 -0
  255. package/dist/ui/shared/effect-owner-source.js +122 -0
  256. package/dist/ui/shared/effect-policy.js +151 -0
  257. package/dist/ui/shared/effect-poster-reader.js +98 -0
  258. package/dist/ui/shared/effect-posters.js +117 -0
  259. package/dist/ui/shared/effect-profiler.js +184 -0
  260. package/dist/ui/shared/effect-profiles.js +65 -0
  261. package/dist/ui/shared/effect-registry.js +543 -0
  262. package/dist/ui/shared/effect-runtime-loader.js +234 -0
  263. package/dist/ui/shared/effect-safety.js +153 -0
  264. package/dist/ui/shared/effect-stock-snapshot.js +43 -0
  265. package/dist/ui/shared/effect-stock-trust.js +217 -0
  266. package/dist/ui/shared/effect-trial-shadow.js +62 -0
  267. package/dist/ui/shared/effect-trial-source.js +103 -0
  268. package/dist/ui/shared/effect-trial-stage.js +170 -0
  269. package/dist/ui/shared/effect-trial.css +40 -0
  270. package/dist/ui/shared/fx-motion.js +67 -0
  271. package/dist/ui/shared/header-tools.js +1 -1
  272. package/dist/ui/shared/identity-card.js +273 -0
  273. package/dist/ui/shared/identity-color.js +261 -0
  274. package/dist/ui/shared/instance-nav.js +215 -41
  275. package/dist/ui/shared/owner-css.js +250 -0
  276. package/dist/ui/shared/owner-effect-prefs.js +117 -0
  277. package/dist/ui/shared/panel-view.js +103 -10
  278. package/dist/ui/shared/signin.js +2 -0
  279. package/dist/ui/shared/tokens.css +9 -3
  280. package/dist/ui/shared/ui-dialog.js +1 -1
  281. package/dist/ui/shared/ui-icons.js +3 -0
  282. package/dist/ui/shared/ui-menu.js +1 -1
  283. package/dist/ui/shared/ui-toast.js +1 -1
  284. package/dist/ui/shared/usage-dialog.js +22 -2
  285. package/dist/ui/shared/view-strings.js +98 -2
  286. package/dist/ui/shared/web-authorize.js +151 -0
  287. package/dist/ui/signin.html +1 -0
  288. package/dist/ui/web-terminal/terminal.css +4 -4
  289. package/dist/usage/providers.d.ts +28 -0
  290. package/dist/usage/providers.js +126 -36
  291. package/dist/usage/providers.js.map +1 -1
  292. package/dist/usage-report.d.ts +158 -0
  293. package/dist/usage-report.js +226 -0
  294. package/dist/usage-report.js.map +1 -0
  295. package/dist/usage-rollup.d.ts +51 -0
  296. package/dist/usage-rollup.js +168 -0
  297. package/dist/usage-rollup.js.map +1 -0
  298. package/dist/view-api.d.ts +68 -0
  299. package/dist/view-api.js +249 -8
  300. package/dist/view-api.js.map +1 -1
  301. package/dist/web-ambient.d.ts +72 -0
  302. package/dist/web-ambient.js +161 -0
  303. package/dist/web-ambient.js.map +1 -0
  304. package/dist/web-api.d.ts +117 -7
  305. package/dist/web-api.js +1283 -70
  306. package/dist/web-api.js.map +1 -1
  307. package/dist/web-appearance-limits.d.ts +82 -0
  308. package/dist/web-appearance-limits.js +83 -0
  309. package/dist/web-appearance-limits.js.map +1 -0
  310. package/dist/web-appearance-policy.d.ts +64 -0
  311. package/dist/web-appearance-policy.js +87 -0
  312. package/dist/web-appearance-policy.js.map +1 -0
  313. package/dist/web-appearance-tools.d.ts +53 -0
  314. package/dist/web-appearance-tools.js +124 -0
  315. package/dist/web-appearance-tools.js.map +1 -0
  316. package/dist/web-appearance-view.d.ts +124 -0
  317. package/dist/web-appearance-view.js +278 -0
  318. package/dist/web-appearance-view.js.map +1 -0
  319. package/dist/web-appearance.d.ts +93 -0
  320. package/dist/web-appearance.js +373 -0
  321. package/dist/web-appearance.js.map +1 -0
  322. package/dist/web-asset-parts.d.ts +93 -0
  323. package/dist/web-asset-parts.js +133 -0
  324. package/dist/web-asset-parts.js.map +1 -0
  325. package/dist/web-auth.js +20 -2
  326. package/dist/web-auth.js.map +1 -1
  327. package/dist/web-card.d.ts +110 -0
  328. package/dist/web-card.js +266 -0
  329. package/dist/web-card.js.map +1 -0
  330. package/dist/web-channel-echo.d.ts +7 -1
  331. package/dist/web-channel-echo.js +6 -2
  332. package/dist/web-channel-echo.js.map +1 -1
  333. package/dist/web-chat-history.d.ts +63 -0
  334. package/dist/web-chat-history.js +109 -6
  335. package/dist/web-chat-history.js.map +1 -1
  336. package/dist/web-commands.d.ts +1 -0
  337. package/dist/web-effect-bootstrap.d.ts +12 -0
  338. package/dist/web-effect-bootstrap.js +49 -0
  339. package/dist/web-effect-bootstrap.js.map +1 -0
  340. package/dist/web-effect-bundle.d.ts +28 -0
  341. package/dist/web-effect-bundle.js +171 -0
  342. package/dist/web-effect-bundle.js.map +1 -0
  343. package/dist/web-effect-canonical.d.ts +8 -0
  344. package/dist/web-effect-canonical.js +27 -0
  345. package/dist/web-effect-canonical.js.map +1 -0
  346. package/dist/web-effect-library.d.ts +114 -0
  347. package/dist/web-effect-library.js +530 -0
  348. package/dist/web-effect-library.js.map +1 -0
  349. package/dist/web-effect-playback.d.ts +30 -0
  350. package/dist/web-effect-playback.js +71 -0
  351. package/dist/web-effect-playback.js.map +1 -0
  352. package/dist/web-effect-poster.d.ts +8 -0
  353. package/dist/web-effect-poster.js +52 -0
  354. package/dist/web-effect-poster.js.map +1 -0
  355. package/dist/web-effect-profiles.d.ts +118 -0
  356. package/dist/web-effect-profiles.js +180 -0
  357. package/dist/web-effect-profiles.js.map +1 -0
  358. package/dist/web-effect-service.d.ts +93 -0
  359. package/dist/web-effect-service.js +335 -0
  360. package/dist/web-effect-service.js.map +1 -0
  361. package/dist/web-effect-tools.d.ts +48 -0
  362. package/dist/web-effect-tools.js +129 -0
  363. package/dist/web-effect-tools.js.map +1 -0
  364. package/dist/web-effect-trials.d.ts +86 -0
  365. package/dist/web-effect-trials.js +204 -0
  366. package/dist/web-effect-trials.js.map +1 -0
  367. package/dist/web-effect-upload.d.ts +27 -0
  368. package/dist/web-effect-upload.js +185 -0
  369. package/dist/web-effect-upload.js.map +1 -0
  370. package/dist/web-effect-worker.d.ts +8 -0
  371. package/dist/web-effect-worker.js +170 -0
  372. package/dist/web-effect-worker.js.map +1 -0
  373. package/dist/web-effect.d.ts +103 -0
  374. package/dist/web-effect.js +153 -0
  375. package/dist/web-effect.js.map +1 -0
  376. package/dist/web-guest-access.d.ts +125 -0
  377. package/dist/web-guest-access.js +234 -0
  378. package/dist/web-guest-access.js.map +1 -0
  379. package/dist/web-guest-rooms.d.ts +82 -0
  380. package/dist/web-guest-rooms.js +135 -0
  381. package/dist/web-guest-rooms.js.map +1 -0
  382. package/dist/web-identity-color.d.ts +87 -0
  383. package/dist/web-identity-color.js +209 -0
  384. package/dist/web-identity-color.js.map +1 -0
  385. package/dist/web-identity-palette.d.ts +113 -0
  386. package/dist/web-identity-palette.js +84 -0
  387. package/dist/web-identity-palette.js.map +1 -0
  388. package/dist/web-image-meta.d.ts +38 -0
  389. package/dist/web-image-meta.js +262 -0
  390. package/dist/web-image-meta.js.map +1 -0
  391. package/dist/web-login.d.ts +47 -0
  392. package/dist/web-login.js +115 -0
  393. package/dist/web-login.js.map +1 -1
  394. package/dist/web-mod-agent.d.ts +50 -0
  395. package/dist/web-mod-agent.js +207 -0
  396. package/dist/web-mod-agent.js.map +1 -0
  397. package/dist/web-mod-bundle.d.ts +157 -0
  398. package/dist/web-mod-bundle.js +529 -0
  399. package/dist/web-mod-bundle.js.map +1 -0
  400. package/dist/web-mod-export.d.ts +68 -0
  401. package/dist/web-mod-export.js +100 -0
  402. package/dist/web-mod-export.js.map +1 -0
  403. package/dist/web-mod-store.d.ts +289 -0
  404. package/dist/web-mod-store.js +638 -0
  405. package/dist/web-mod-store.js.map +1 -0
  406. package/dist/web-org.d.ts +3 -3
  407. package/dist/web-org.js +11 -17
  408. package/dist/web-org.js.map +1 -1
  409. package/dist/web-owner-css-store.d.ts +222 -0
  410. package/dist/web-owner-css-store.js +440 -0
  411. package/dist/web-owner-css-store.js.map +1 -0
  412. package/dist/web-owner-css.d.ts +58 -0
  413. package/dist/web-owner-css.js +552 -0
  414. package/dist/web-owner-css.js.map +1 -0
  415. package/dist/web-prefs.d.ts +62 -0
  416. package/dist/web-prefs.js +232 -36
  417. package/dist/web-prefs.js.map +1 -1
  418. package/dist/web-preview.d.ts +15 -3
  419. package/dist/web-preview.js +28 -9
  420. package/dist/web-preview.js.map +1 -1
  421. package/dist/web-returning-device.d.ts +41 -0
  422. package/dist/web-returning-device.js +57 -5
  423. package/dist/web-returning-device.js.map +1 -1
  424. package/dist/web-session-authorization.d.ts +132 -0
  425. package/dist/web-session-authorization.js +193 -0
  426. package/dist/web-session-authorization.js.map +1 -0
  427. package/dist/web-session.d.ts +70 -10
  428. package/dist/web-session.js +137 -22
  429. package/dist/web-session.js.map +1 -1
  430. package/dist/web-settings-patch.d.ts +67 -2
  431. package/dist/web-settings-patch.js +196 -1
  432. package/dist/web-settings-patch.js.map +1 -1
  433. package/dist/web-settings-tools.d.ts +89 -0
  434. package/dist/web-settings-tools.js +273 -0
  435. package/dist/web-settings-tools.js.map +1 -0
  436. package/dist/web-shell-routes.d.ts +2 -2
  437. package/dist/web-shell-routes.js +2 -2
  438. package/dist/web-shell-routes.js.map +1 -1
  439. package/dist/web-terminal-http.js +15 -2
  440. package/dist/web-terminal-http.js.map +1 -1
  441. package/dist/web-theme-store.d.ts +257 -0
  442. package/dist/web-theme-store.js +627 -0
  443. package/dist/web-theme-store.js.map +1 -0
  444. package/dist/web-theme.d.ts +123 -0
  445. package/dist/web-theme.js +416 -0
  446. package/dist/web-theme.js.map +1 -0
  447. package/dist/web-upload.d.ts +13 -0
  448. package/dist/web-upload.js +39 -3
  449. package/dist/web-upload.js.map +1 -1
  450. package/dist/web-widget.d.ts +72 -0
  451. package/dist/web-widget.js +245 -0
  452. package/dist/web-widget.js.map +1 -0
  453. package/dist/workflow-templates/default.md +1 -1
  454. package/package.json +1 -1
  455. package/templates/systemd.service.ejs +8 -0
@@ -0,0 +1,193 @@
1
+ /**
2
+ * #1939 S2b (design §0, §0.1): "Authorize this browser". A signed-in session asks; AgEnD posts a Confirm card in the DM of
3
+ * the account the session was signed in as; **Confirm** there authorizes that session, **Not me** signs it out. The card
4
+ * is the one gate that stops a stolen cookie speaking as the person, so it is guarded against confirm fatigue:
5
+ * - it says exactly what it authorizes (this browser, its device label, this computer or the network, when, until when);
6
+ * - one pending card per session, at most 3 asks an hour per account; a refusal is explained in the app;
7
+ * - a card expires after 5 minutes (marked in place), and Confirm is judged at the tap (the session read again by its
8
+ * handle and §3 run again — FleetManager.authorizeWebSession);
9
+ * - after Confirm the card stays as the notice, with Not me (sign the session out) for as long as the authorization lasts,
10
+ * and says so (no buttons) when it ends;
11
+ * - when no DM can be sent, the adapter's General gets one line with no details.
12
+ * Durations use a monotonic clock; what the card shows is wall-clock time.
13
+ */
14
+ import { randomBytes } from "node:crypto";
15
+ export const AUTHORIZATION_CARD_TTL_MS = 5 * 60_000;
16
+ export const AUTHORIZATION_ASKS_PER_HOUR = 3;
17
+ const HOUR_MS = 3_600_000;
18
+ export const AUTHORIZATION_CALLBACK_PREFIX = "wauth:";
19
+ export class SessionAuthorizationRequests {
20
+ deps;
21
+ byNonce = new Map();
22
+ /** One pending card per session. */
23
+ pendingByHandle = new Map();
24
+ /** Monotonic times of each account's asks in the last hour. */
25
+ asks = new Map();
26
+ constructor(deps) {
27
+ this.deps = deps;
28
+ }
29
+ choices(c, confirm) {
30
+ return [
31
+ ...(confirm ? [{ id: `${AUTHORIZATION_CALLBACK_PREFIX}${c.nonce}:confirm`, label: this.deps.text.confirmLabel() }] : []),
32
+ { id: `${AUTHORIZATION_CALLBACK_PREFIX}${c.nonce}:notme`, label: this.deps.text.notMeLabel() },
33
+ ];
34
+ }
35
+ recentAsks(key) {
36
+ const now = this.deps.now();
37
+ const kept = (this.asks.get(key) ?? []).filter(t => now - t < HOUR_MS);
38
+ if (kept.length)
39
+ this.asks.set(key, kept);
40
+ else
41
+ this.asks.delete(key);
42
+ return kept;
43
+ }
44
+ /**
45
+ * This session's pending card. One past its deadline is expired now, whether or not its timer has run yet (a timer can
46
+ * run late): the app never shows "waiting 0:00", and a new ask is not refused as "pending".
47
+ */
48
+ livePending(handle) {
49
+ const c = this.pendingByHandle.get(handle);
50
+ if (!c || c.state !== "pending")
51
+ return null;
52
+ if (this.deps.now() >= c.expiresAt) {
53
+ void this.expire(c);
54
+ return null;
55
+ }
56
+ return c;
57
+ }
58
+ /** What the app shows for this session: a pending card (until when), or nothing pending. */
59
+ pending(handle) {
60
+ const c = this.livePending(handle);
61
+ return c ? { expiresInMs: Math.max(0, c.expiresAt - this.deps.now()) } : null;
62
+ }
63
+ /**
64
+ * The session asks (it qualifies under §3 — the caller checked, on this request). Refused while a card for it is
65
+ * pending, after 3 asks this hour by the account, or when no DM can be sent (General then gets a line, no details).
66
+ */
67
+ async ask(input) {
68
+ const existing = this.livePending(input.handle);
69
+ if (existing)
70
+ return { ok: false, refusal: "pending", retryAfterMs: Math.max(0, existing.expiresAt - this.deps.now()) };
71
+ const key = `${input.owner.adapterId}:${input.owner.userId}`;
72
+ const recent = this.recentAsks(key);
73
+ if (recent.length >= AUTHORIZATION_ASKS_PER_HOUR)
74
+ return { ok: false, refusal: "rate_limited", retryAfterMs: Math.max(0, recent[0] + HOUR_MS - this.deps.now()) };
75
+ recent.push(this.deps.now());
76
+ this.asks.set(key, recent);
77
+ const c = { nonce: randomBytes(16).toString("hex"), handle: input.handle, owner: { ...input.owner }, label: input.label, peer: input.peer,
78
+ expiresAt: this.deps.now() + AUTHORIZATION_CARD_TTL_MS, state: "pending", where: null };
79
+ this.byNonce.set(c.nonce, c);
80
+ this.pendingByHandle.set(c.handle, c);
81
+ const wall = this.deps.wallNow();
82
+ let where = null;
83
+ try {
84
+ // Until when it would last: the authorization's end, or the session's own when that comes first (as Confirm says).
85
+ const until = Math.min(wall + this.deps.maxSessionMs(), input.sessionEnd);
86
+ where = await this.deps.sendDirect(c.owner, this.deps.text.ask({ label: c.label, peer: c.peer, askedAt: wall, until }), this.choices(c, true));
87
+ }
88
+ catch (err) {
89
+ this.deps.log.warn({ err: err?.message }, "Web authorization card could not be sent");
90
+ }
91
+ if (!where) {
92
+ this.forget(c);
93
+ try {
94
+ await this.deps.sendGeneralHint(c.owner, this.deps.text.generalHint());
95
+ }
96
+ catch { /* best effort: the app says why */ }
97
+ this.deps.event("session_authorization_failed", { session: c.handle, actor: key, peer: c.peer, reason: "dm_unavailable" });
98
+ return { ok: false, refusal: "dm_unavailable" };
99
+ }
100
+ c.where = where;
101
+ c.timer = setTimeout(() => { void this.expire(c); }, AUTHORIZATION_CARD_TTL_MS);
102
+ c.timer.unref?.();
103
+ this.deps.event("session_authorization_issued", { challenge: c.nonce, session: c.handle, actor: key, peer: c.peer });
104
+ return { ok: true, expiresInMs: AUTHORIZATION_CARD_TTL_MS };
105
+ }
106
+ forget(c) {
107
+ if (c.timer)
108
+ clearTimeout(c.timer);
109
+ if (this.pendingByHandle.get(c.handle) === c)
110
+ this.pendingByHandle.delete(c.handle);
111
+ }
112
+ async edit(c, text, confirm) {
113
+ if (!c.where)
114
+ return;
115
+ try {
116
+ await this.deps.editCard(c.owner, c.where, text, confirm === null ? [] : this.choices(c, confirm));
117
+ }
118
+ catch (err) {
119
+ this.deps.log.warn({ err: err?.message }, "Web authorization card could not be updated");
120
+ }
121
+ }
122
+ async expire(c) {
123
+ if (c.state !== "pending")
124
+ return;
125
+ c.state = "expired";
126
+ this.forget(c);
127
+ this.byNonce.delete(c.nonce);
128
+ this.deps.event("session_authorization_expired", { challenge: c.nonce, session: c.handle, actor: `${c.owner.adapterId}:${c.owner.userId}`, peer: c.peer });
129
+ await this.edit(c, this.deps.text.expired({ label: c.label }), null);
130
+ }
131
+ /** A confirmed card at its authorization's end: forgotten, and edited to say so (no Not me left to press). */
132
+ async end(c) {
133
+ if (c.state !== "confirmed" || this.byNonce.get(c.nonce) !== c)
134
+ return;
135
+ c.state = "ended";
136
+ this.byNonce.delete(c.nonce);
137
+ await this.edit(c, this.deps.text.ended({ label: c.label }), null);
138
+ }
139
+ /** A tap on a card's button. Only the account it was sent to may tap it; anyone else is told it is not theirs. */
140
+ async handleCallback(data, adapterId) {
141
+ if (!data.callbackData.startsWith(AUTHORIZATION_CALLBACK_PREFIX))
142
+ return false;
143
+ const m = /^wauth:([0-9a-f]{32}):(confirm|notme)$/.exec(data.callbackData);
144
+ const c = m ? this.byNonce.get(m[1]) : undefined;
145
+ if (!m || !c) {
146
+ data.ack?.();
147
+ return true;
148
+ }
149
+ if (c.owner.adapterId !== adapterId || c.owner.userId !== String(data.userId ?? "")) {
150
+ data.ack?.(this.deps.text.notYours());
151
+ return true;
152
+ }
153
+ const key = `${c.owner.adapterId}:${c.owner.userId}`;
154
+ if (m[2] === "notme") {
155
+ // Before or after Confirm: sign that session out (its authorization ends with it).
156
+ const was = c.state;
157
+ c.state = was === "confirmed" ? "revoked" : "denied";
158
+ this.forget(c);
159
+ this.byNonce.delete(c.nonce);
160
+ this.deps.signOut(c.handle);
161
+ this.deps.event("session_authorization_failed", { challenge: c.nonce, session: c.handle, actor: key, peer: c.peer, reason: was === "confirmed" ? "not_me_after_confirm" : "not_me" });
162
+ data.ack?.();
163
+ await this.edit(c, was === "confirmed" ? this.deps.text.revoked({ label: c.label }) : this.deps.text.denied({ label: c.label }), null);
164
+ return true;
165
+ }
166
+ // Confirm: only a pending card, before it expires, judged now.
167
+ if (c.state !== "pending" || this.deps.now() >= c.expiresAt) {
168
+ data.ack?.();
169
+ if (c.state === "pending")
170
+ await this.expire(c);
171
+ return true;
172
+ }
173
+ this.forget(c);
174
+ const r = this.deps.authorize(c.handle, c.peer);
175
+ data.ack?.();
176
+ if (!r.ok) {
177
+ c.state = "refused";
178
+ this.byNonce.delete(c.nonce);
179
+ this.deps.event("session_authorization_failed", { challenge: c.nonce, session: c.handle, actor: key, peer: c.peer, reason: r.refusal });
180
+ await this.edit(c, this.deps.text.refused({ label: c.label, refusal: r.refusal }), null);
181
+ return true;
182
+ }
183
+ c.state = "confirmed";
184
+ c.until = r.until;
185
+ this.deps.event("session_authorization_verified", { challenge: c.nonce, session: c.handle, actor: key, peer: c.peer });
186
+ // The card stays as the notice, with Not me, for as long as the authorization lasts; then it says it ended.
187
+ await this.edit(c, this.deps.text.confirmed({ label: c.label, peer: c.peer, until: r.until }), false);
188
+ const end = setTimeout(() => { void this.end(c); }, Math.max(0, Math.min(c.until - this.deps.wallNow(), 2 ** 31 - 1)));
189
+ end.unref?.();
190
+ return true;
191
+ }
192
+ }
193
+ //# sourceMappingURL=web-session-authorization.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"web-session-authorization.js","sourceRoot":"","sources":["../src/web-session-authorization.ts"],"names":[],"mappings":"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"}
@@ -1,7 +1,11 @@
1
1
  import { closeSync, fsyncSync, openSync, renameSync, unlinkSync, writeFileSync } from "node:fs";
2
2
  export type SessionSurface = "local" | "gateway";
3
- /** `admin` may do everything the panel can; `read` is reserved for the read-only tier (design D6). */
4
- export type SessionTier = "admin" | "read";
3
+ /**
4
+ * `admin` may do everything the panel can; `read` is reserved for the read-only tier (design D6). #1920: `guest` is a
5
+ * non-admin signed in as their own account (`owner`, always set), allowed only GUEST_ROUTES within a scope derived on
6
+ * every request (web-guest-access.ts). No code issues one yet (phase 2).
7
+ */
8
+ export type SessionTier = "admin" | "read" | "guest";
5
9
  export interface SessionPolicy {
6
10
  /** Hard cap from creation; activity never extends it. */
7
11
  readonly absoluteMs: number;
@@ -28,8 +32,31 @@ export declare function localSessionPolicy(cfg: {
28
32
  absolute_hours?: unknown;
29
33
  idle_hours?: unknown;
30
34
  } | undefined): SessionPolicy;
31
- /** More than this and the least recently used one is dropped. */
35
+ /**
36
+ * #1920 §5.4: a guest's session — shorter than an admin's, never longer than the surface's own policy (on the public
37
+ * link the link's 4 h / 30 min wins). `web.guests.session`: absolute 1–24 h, idle 15 min–12 h.
38
+ */
39
+ export declare const GUEST_SESSION_POLICY: SessionPolicy;
40
+ export declare const GUEST_SESSION_BOUNDS: {
41
+ readonly absoluteHours: {
42
+ readonly min: 1;
43
+ readonly max: 24;
44
+ };
45
+ readonly idleMinutes: {
46
+ readonly min: 15;
47
+ readonly max: 720;
48
+ };
49
+ };
50
+ /** The guest policy `web.guests.session` asks for, or the default for what it leaves out; defensive as localSessionPolicy. */
51
+ export declare function guestSessionPolicy(cfg: {
52
+ absolute_hours?: unknown;
53
+ idle_minutes?: unknown;
54
+ } | undefined): SessionPolicy;
55
+ /** More than this and the least recently used one is dropped (#1920: admins' and read sessions; guests count apart). */
32
56
  export declare const MAX_WEB_SESSIONS = 8;
57
+ /** #1920: guests never push an admin out — their own caps, the least recently used of their kind dropped first. */
58
+ export declare const MAX_GUEST_SESSIONS = 64;
59
+ export declare const MAX_GUEST_SESSIONS_PER_ACCOUNT = 4;
33
60
  /** `lastSeen` is only written to disk this often; creation and revocation always write. */
34
61
  export declare const SESSION_PERSIST_DEBOUNCE_MS: number;
35
62
  export interface SessionRecord {
@@ -109,6 +136,8 @@ export interface WebSessionStoreOptions {
109
136
  * can change while the fleet runs). Takes precedence over `policy.local`.
110
137
  */
111
138
  readonly localPolicy?: () => SessionPolicy;
139
+ /** #1920: a guest's policy, read at every check (`web.guests.session`); never longer than the surface's. */
140
+ readonly guestPolicy?: () => SessionPolicy;
112
141
  readonly maxSessions?: number;
113
142
  readonly onWarn?: (message: string) => void;
114
143
  }
@@ -118,6 +147,7 @@ export declare class WebSessionStore {
118
147
  private readonly now;
119
148
  private readonly policy;
120
149
  private readonly localPolicy;
150
+ private readonly guestPolicy;
121
151
  private readonly maxSessions;
122
152
  private readonly path;
123
153
  private readonly warn;
@@ -129,8 +159,11 @@ export declare class WebSessionStore {
129
159
  constructor(opts?: WebSessionStoreOptions);
130
160
  get size(): number;
131
161
  /** How long a session on this surface may sit unused before it ends. For showing people, not for deciding. */
132
- idleWindowMs(surface: SessionSurface): number;
133
- /** The policy in force for a surface now (#1620: the local one as configured at this moment; the gateway's fixed). */
162
+ idleWindowMs(surface: SessionSurface, tier?: SessionTier): number;
163
+ /**
164
+ * The policy in force for a surface now (#1620: the local one as configured at this moment; the gateway's fixed).
165
+ * #1920: a guest's is its own policy, never longer than the surface's.
166
+ */
134
167
  private policyFor;
135
168
  /**
136
169
  * #1620: a session's limits under the policy in force NOW. Shortening takes effect on existing sessions: the
@@ -183,9 +216,30 @@ export declare class WebSessionStore {
183
216
  count: number;
184
217
  durable: boolean;
185
218
  };
186
- /** Every live session, newest activity first. `currentIdHash` marks the caller's own. */
187
- list(currentIdHash?: string): SessionSummary[];
188
- revokeByHandle(handle: string): {
219
+ /**
220
+ * Every live session, newest activity first. `currentIdHash` marks the caller's own. #1920: `only` keeps those a
221
+ * non-admin may see (their own, web-guest-access.ts).
222
+ */
223
+ list(currentIdHash?: string, only?: (r: SessionRecord) => boolean): SessionSummary[];
224
+ /** #1920 §3.2: the accounts with a live guest session, each once — for the background room-membership reads. */
225
+ guestOwners(): Array<{
226
+ adapterId: string;
227
+ userId: string;
228
+ }>;
229
+ /** #1920 phase 3 (Settings → Guests): each account with a live guest session — how many, and when last seen. */
230
+ guestActivity(): Array<{
231
+ adapterId: string;
232
+ userId: string;
233
+ sessions: number;
234
+ lastSeen: number;
235
+ }>;
236
+ /**
237
+ * #1939 S2b: the live session with this handle (an active one, not expired — limits applied), or null. Read-only: a
238
+ * Confirm tapped in Discord is judged against the session as it is at that moment.
239
+ */
240
+ findByHandle(handle: string): SessionRecord | null;
241
+ /** #1920: `only` as in list() — a session it rejects is not found. */
242
+ revokeByHandle(handle: string, only?: (r: SessionRecord) => boolean): {
189
243
  found: boolean;
190
244
  durable: boolean;
191
245
  };
@@ -193,14 +247,20 @@ export declare class WebSessionStore {
193
247
  removed: boolean;
194
248
  durable: boolean;
195
249
  };
196
- /** Every session: how many there were, and whether the revocation is durable. */
197
- revokeAll(): {
250
+ /** Every session (#1920: or every one `only` keeps): how many there were, and whether the revocation is durable. */
251
+ revokeAll(only?: (r: SessionRecord) => boolean): {
198
252
  count: number;
199
253
  durable: boolean;
200
254
  };
201
255
  /** Write anything pending. Called on shutdown. */
202
256
  flush(): void;
203
257
  private purgeExpired;
258
+ /**
259
+ * The caps: admins' and read sessions together; guests apart (#1920) — per account, then all guests — so a guest
260
+ * signing in never drops an admin's session, and one account cannot fill the guests' room.
261
+ */
262
+ private enforceCaps;
263
+ private count;
204
264
  private evictLeastRecentlyUsed;
205
265
  private persistIfDue;
206
266
  /**
@@ -50,8 +50,24 @@ export function localSessionPolicy(cfg) {
50
50
  const absoluteMs = ms(cfg?.absolute_hours, def.absoluteMs);
51
51
  return { absoluteMs, idleMs: Math.min(ms(cfg?.idle_hours, def.idleMs), absoluteMs) };
52
52
  }
53
- /** More than this and the least recently used one is dropped. */
53
+ /**
54
+ * #1920 §5.4: a guest's session — shorter than an admin's, never longer than the surface's own policy (on the public
55
+ * link the link's 4 h / 30 min wins). `web.guests.session`: absolute 1–24 h, idle 15 min–12 h.
56
+ */
57
+ export const GUEST_SESSION_POLICY = { absoluteMs: 12 * HOUR, idleMs: 2 * HOUR };
58
+ export const GUEST_SESSION_BOUNDS = { absoluteHours: { min: 1, max: 24 }, idleMinutes: { min: 15, max: 720 } };
59
+ /** The guest policy `web.guests.session` asks for, or the default for what it leaves out; defensive as localSessionPolicy. */
60
+ export function guestSessionPolicy(cfg) {
61
+ const { absoluteHours: a, idleMinutes: i } = GUEST_SESSION_BOUNDS;
62
+ const ms = (v, min, max, unit, fallback) => typeof v !== "number" || !Number.isFinite(v) || v > max ? fallback : Math.max(v, min) * unit;
63
+ const absoluteMs = ms(cfg?.absolute_hours, a.min, a.max, HOUR, GUEST_SESSION_POLICY.absoluteMs);
64
+ return { absoluteMs, idleMs: Math.min(ms(cfg?.idle_minutes, i.min, i.max, MINUTE, GUEST_SESSION_POLICY.idleMs), absoluteMs) };
65
+ }
66
+ /** More than this and the least recently used one is dropped (#1920: admins' and read sessions; guests count apart). */
54
67
  export const MAX_WEB_SESSIONS = 8;
68
+ /** #1920: guests never push an admin out — their own caps, the least recently used of their kind dropped first. */
69
+ export const MAX_GUEST_SESSIONS = 64;
70
+ export const MAX_GUEST_SESSIONS_PER_ACCOUNT = 4;
55
71
  /** `lastSeen` is only written to disk this often; creation and revocation always write. */
56
72
  export const SESSION_PERSIST_DEBOUNCE_MS = 60 * 1000;
57
73
  const SESSION_ID_PATTERN = /^[0-9a-f]{64}$/;
@@ -120,19 +136,22 @@ function isRecord(value) {
120
136
  && typeof v.lastSeen === "number" && Number.isFinite(v.lastSeen)
121
137
  && typeof v.absoluteExpiry === "number" && Number.isFinite(v.absoluteExpiry)
122
138
  && typeof v.idleExpiry === "number" && Number.isFinite(v.idleExpiry)
123
- && (v.tier === "admin" || v.tier === "read")
139
+ && (v.tier === "admin" || v.tier === "read" || (v.tier === "guest" && v.owner !== undefined))
124
140
  && (v.surface === "local" || v.surface === "gateway")
125
141
  && (v.exposureId === undefined || (typeof v.exposureId === "string" && /^[0-9a-f]{32}$/.test(v.exposureId)))
126
142
  && (v.owner === undefined || isOwner(v.owner))
127
143
  && typeof v.label === "string" && v.label.length <= LABEL_MAX
128
144
  && typeof v.tokenEpoch === "string" && HASH_PATTERN.test(v.tokenEpoch);
129
145
  }
146
+ /** One account, as a map key (adapter ids and user ids never hold a newline). */
147
+ const accountKey = (o) => `${o.adapterId}\n${o.userId}`;
130
148
  export class WebSessionStore {
131
149
  pending = new Set();
132
150
  byHash = new Map();
133
151
  now;
134
152
  policy;
135
153
  localPolicy;
154
+ guestPolicy;
136
155
  maxSessions;
137
156
  path;
138
157
  warn;
@@ -148,6 +167,7 @@ export class WebSessionStore {
148
167
  gateway: opts.policy?.gateway ?? DEFAULT_SESSION_POLICY.gateway,
149
168
  };
150
169
  this.localPolicy = opts.localPolicy ?? null;
170
+ this.guestPolicy = opts.guestPolicy ?? (() => GUEST_SESSION_POLICY);
151
171
  this.maxSessions = opts.maxSessions ?? MAX_WEB_SESSIONS;
152
172
  this.path = opts.dataDir ? join(opts.dataDir, "web-sessions.json") : null;
153
173
  this.warn = opts.onWarn ?? (() => { });
@@ -156,10 +176,18 @@ export class WebSessionStore {
156
176
  }
157
177
  get size() { return this.byHash.size; }
158
178
  /** How long a session on this surface may sit unused before it ends. For showing people, not for deciding. */
159
- idleWindowMs(surface) { return this.policyFor(surface).idleMs; }
160
- /** The policy in force for a surface now (#1620: the local one as configured at this moment; the gateway's fixed). */
161
- policyFor(surface) {
162
- return surface === "local" && this.localPolicy ? this.localPolicy() : this.policy[surface];
179
+ idleWindowMs(surface, tier = "admin") { return this.policyFor(surface, tier).idleMs; }
180
+ /**
181
+ * The policy in force for a surface now (#1620: the local one as configured at this moment; the gateway's fixed).
182
+ * #1920: a guest's is its own policy, never longer than the surface's.
183
+ */
184
+ policyFor(surface, tier) {
185
+ const base = surface === "local" && this.localPolicy ? this.localPolicy() : this.policy[surface];
186
+ if (tier !== "guest")
187
+ return base;
188
+ const guest = this.guestPolicy();
189
+ const absoluteMs = Math.min(base.absoluteMs, guest.absoluteMs);
190
+ return { absoluteMs, idleMs: Math.min(base.idleMs, guest.idleMs, absoluteMs) };
163
191
  }
164
192
  /**
165
193
  * #1620: a session's limits under the policy in force NOW. Shortening takes effect on existing sessions: the
@@ -169,7 +197,7 @@ export class WebSessionStore {
169
197
  * up to that absolute limit. New sessions get the new windows whole.
170
198
  */
171
199
  limit(record) {
172
- const p = this.policyFor(record.surface);
200
+ const p = this.policyFor(record.surface, record.tier);
173
201
  const absoluteExpiry = Math.min(record.absoluteExpiry, record.created + p.absoluteMs);
174
202
  const idleExpiry = Math.min(record.idleExpiry, record.lastSeen + p.idleMs, absoluteExpiry);
175
203
  if (absoluteExpiry !== record.absoluteExpiry || idleExpiry !== record.idleExpiry) {
@@ -183,10 +211,13 @@ export class WebSessionStore {
183
211
  * in a cookie and forgets it.
184
212
  */
185
213
  create(input) {
214
+ // #1920: a guest is always someone's own account; never a guest of nobody.
215
+ if (input.tier === "guest" && !input.owner)
216
+ throw new Error("a guest session needs its owner");
186
217
  const now = this.now();
187
218
  this.purgeExpired(now);
188
219
  const sessionId = randomBytes(32).toString("hex");
189
- const policy = this.policyFor(input.surface);
220
+ const policy = this.policyFor(input.surface, input.tier);
190
221
  const absoluteExpiry = now + policy.absoluteMs;
191
222
  const record = {
192
223
  handle: randomBytes(8).toString("hex"),
@@ -205,8 +236,7 @@ export class WebSessionStore {
205
236
  this.byHash.set(record.idHash, record);
206
237
  if (input.pending)
207
238
  this.pending.add(record.idHash);
208
- while (this.byHash.size > this.maxSessions)
209
- this.evictLeastRecentlyUsed();
239
+ this.enforceCaps();
210
240
  this.persistNow();
211
241
  return { sessionId, record };
212
242
  }
@@ -239,7 +269,7 @@ export class WebSessionStore {
239
269
  }
240
270
  if (opts.touch !== false) {
241
271
  record.lastSeen = now;
242
- record.idleExpiry = Math.min(now + this.policyFor(record.surface).idleMs, record.absoluteExpiry);
272
+ record.idleExpiry = Math.min(now + this.policyFor(record.surface, record.tier).idleMs, record.absoluteExpiry);
243
273
  this.dirty = true;
244
274
  this.persistIfDue(now);
245
275
  }
@@ -271,10 +301,13 @@ export class WebSessionStore {
271
301
  }
272
302
  return { count, durable: this.persistNow() };
273
303
  }
274
- /** Every live session, newest activity first. `currentIdHash` marks the caller's own. */
275
- list(currentIdHash) {
304
+ /**
305
+ * Every live session, newest activity first. `currentIdHash` marks the caller's own. #1920: `only` keeps those a
306
+ * non-admin may see (their own, web-guest-access.ts).
307
+ */
308
+ list(currentIdHash, only) {
276
309
  this.purgeExpired(this.now());
277
- return [...this.byHash.values()].filter(r => !this.pending.has(r.idHash))
310
+ return [...this.byHash.values()].filter(r => !this.pending.has(r.idHash) && (!only || only(r)))
278
311
  .sort((a, b) => b.lastSeen - a.lastSeen)
279
312
  .map(r => ({
280
313
  handle: r.handle,
@@ -293,11 +326,56 @@ export class WebSessionStore {
293
326
  * Memory forgets it at once; the file may not. A caller that reports "signed out" must not report it
294
327
  * when it is not durable — the operator would believe a session is dead that the next start revives.
295
328
  */
296
- revokeByHandle(handle) {
329
+ /** #1920 §3.2: the accounts with a live guest session, each once — for the background room-membership reads. */
330
+ guestOwners() {
331
+ this.purgeExpired(this.now());
332
+ const seen = new Map();
333
+ for (const r of this.byHash.values()) {
334
+ if (r.tier !== "guest" || !r.owner || this.pending.has(r.idHash))
335
+ continue;
336
+ seen.set(`${r.owner.adapterId}\u0000${r.owner.userId}`, { adapterId: r.owner.adapterId, userId: r.owner.userId });
337
+ }
338
+ return [...seen.values()];
339
+ }
340
+ /** #1920 phase 3 (Settings → Guests): each account with a live guest session — how many, and when last seen. */
341
+ guestActivity() {
342
+ this.purgeExpired(this.now());
343
+ const by = new Map();
344
+ for (const r of this.byHash.values()) {
345
+ if (r.tier !== "guest" || !r.owner || this.pending.has(r.idHash))
346
+ continue;
347
+ const k = `${r.owner.adapterId}\u0000${r.owner.userId}`, cur = by.get(k);
348
+ if (cur) {
349
+ cur.sessions++;
350
+ cur.lastSeen = Math.max(cur.lastSeen, r.lastSeen);
351
+ }
352
+ else
353
+ by.set(k, { adapterId: r.owner.adapterId, userId: r.owner.userId, sessions: 1, lastSeen: r.lastSeen });
354
+ }
355
+ return [...by.values()];
356
+ }
357
+ /**
358
+ * #1939 S2b: the live session with this handle (an active one, not expired — limits applied), or null. Read-only: a
359
+ * Confirm tapped in Discord is judged against the session as it is at that moment.
360
+ */
361
+ findByHandle(handle) {
362
+ if (!HANDLE_PATTERN.test(handle))
363
+ return null;
364
+ const now = this.now();
365
+ for (const record of this.byHash.values()) {
366
+ if (record.handle !== handle || this.pending.has(record.idHash))
367
+ continue;
368
+ this.limit(record);
369
+ return now < record.absoluteExpiry && now < record.idleExpiry ? record : null;
370
+ }
371
+ return null;
372
+ }
373
+ /** #1920: `only` as in list() — a session it rejects is not found. */
374
+ revokeByHandle(handle, only) {
297
375
  if (!HANDLE_PATTERN.test(handle))
298
376
  return { found: false, durable: true };
299
377
  for (const [hash, record] of this.byHash) {
300
- if (record.handle === handle) {
378
+ if (record.handle === handle && (!only || only(record))) {
301
379
  this.byHash.delete(hash);
302
380
  this.pending.delete(hash);
303
381
  return { found: true, durable: this.persistNow() };
@@ -313,8 +391,18 @@ export class WebSessionStore {
313
391
  const removed = this.byHash.delete(hash);
314
392
  return { removed, durable: removed ? this.persistNow() : true };
315
393
  }
316
- /** Every session: how many there were, and whether the revocation is durable. */
317
- revokeAll() {
394
+ /** Every session (#1920: or every one `only` keeps): how many there were, and whether the revocation is durable. */
395
+ revokeAll(only) {
396
+ if (only) {
397
+ let count = 0;
398
+ for (const [hash, record] of this.byHash)
399
+ if (only(record)) {
400
+ this.byHash.delete(hash);
401
+ this.pending.delete(hash);
402
+ count++;
403
+ }
404
+ return { count, durable: count ? this.persistNow() : true };
405
+ }
318
406
  const count = this.byHash.size;
319
407
  this.byHash.clear();
320
408
  this.pending.clear();
@@ -338,10 +426,38 @@ export class WebSessionStore {
338
426
  if (removed)
339
427
  this.dirty = true;
340
428
  }
341
- evictLeastRecentlyUsed() {
429
+ /**
430
+ * The caps: admins' and read sessions together; guests apart (#1920) — per account, then all guests — so a guest
431
+ * signing in never drops an admin's session, and one account cannot fill the guests' room.
432
+ */
433
+ enforceCaps() {
434
+ const notGuest = (r) => r.tier !== "guest";
435
+ while (this.count(notGuest) > this.maxSessions)
436
+ this.evictLeastRecentlyUsed(notGuest);
437
+ const accounts = new Set();
438
+ for (const r of this.byHash.values())
439
+ if (r.tier === "guest")
440
+ accounts.add(accountKey(r.owner));
441
+ for (const key of accounts) {
442
+ const mine = (r) => r.tier === "guest" && accountKey(r.owner) === key;
443
+ while (this.count(mine) > MAX_GUEST_SESSIONS_PER_ACCOUNT)
444
+ this.evictLeastRecentlyUsed(mine);
445
+ }
446
+ const guest = (r) => r.tier === "guest";
447
+ while (this.count(guest) > MAX_GUEST_SESSIONS)
448
+ this.evictLeastRecentlyUsed(guest);
449
+ }
450
+ count(which) {
451
+ let n = 0;
452
+ for (const record of this.byHash.values())
453
+ if (which(record))
454
+ n++;
455
+ return n;
456
+ }
457
+ evictLeastRecentlyUsed(which) {
342
458
  let oldest = null;
343
459
  for (const record of this.byHash.values()) {
344
- if (!oldest || record.lastSeen < oldest.lastSeen)
460
+ if (which(record) && (!oldest || record.lastSeen < oldest.lastSeen))
345
461
  oldest = record;
346
462
  }
347
463
  if (oldest) {
@@ -459,8 +575,7 @@ export class WebSessionStore {
459
575
  continue;
460
576
  this.byHash.set(record.idHash, record);
461
577
  }
462
- while (this.byHash.size > this.maxSessions)
463
- this.evictLeastRecentlyUsed();
578
+ this.enforceCaps();
464
579
  this.persisted = new Set([...this.byHash.keys()].filter(hash => !this.pending.has(hash)));
465
580
  }
466
581
  }