@songsid/agend 2.2.0-beta.9 → 2.3.0-beta.1

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 (422) hide show
  1. package/README.md +40 -24
  2. package/README.zh-TW.md +40 -24
  3. package/dist/agent-cli.js +248 -2
  4. package/dist/agent-cli.js.map +1 -1
  5. package/dist/agent-endpoint.d.ts +2 -0
  6. package/dist/agent-endpoint.js +28 -0
  7. package/dist/agent-endpoint.js.map +1 -1
  8. package/dist/agent-file-read.d.ts +30 -0
  9. package/dist/agent-file-read.js +129 -0
  10. package/dist/agent-file-read.js.map +1 -0
  11. package/dist/agent-whats-new.js +20 -1
  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 +3 -1
  16. package/dist/auth-api.js +32 -1
  17. package/dist/auth-api.js.map +1 -1
  18. package/dist/backend/antigravity.d.ts +33 -0
  19. package/dist/backend/antigravity.js +129 -21
  20. package/dist/backend/antigravity.js.map +1 -1
  21. package/dist/backend/claude-code.d.ts +3 -0
  22. package/dist/backend/claude-code.js +9 -0
  23. package/dist/backend/claude-code.js.map +1 -1
  24. package/dist/backend/codex-metadata.d.ts +33 -0
  25. package/dist/backend/codex-metadata.js +122 -18
  26. package/dist/backend/codex-metadata.js.map +1 -1
  27. package/dist/backend/codex.d.ts +3 -0
  28. package/dist/backend/codex.js +17 -1
  29. package/dist/backend/codex.js.map +1 -1
  30. package/dist/backend/effort-metadata.d.ts +37 -1
  31. package/dist/backend/effort-metadata.js +86 -15
  32. package/dist/backend/effort-metadata.js.map +1 -1
  33. package/dist/backend/grok.d.ts +3 -0
  34. package/dist/backend/grok.js +9 -0
  35. package/dist/backend/grok.js.map +1 -1
  36. package/dist/backend/kiro.d.ts +3 -1
  37. package/dist/backend/kiro.js +7 -3
  38. package/dist/backend/kiro.js.map +1 -1
  39. package/dist/backend/launch-effort.d.ts +61 -0
  40. package/dist/backend/launch-effort.js +170 -0
  41. package/dist/backend/launch-effort.js.map +1 -0
  42. package/dist/backend/muse.js +10 -2
  43. package/dist/backend/muse.js.map +1 -1
  44. package/dist/backend/types.d.ts +24 -0
  45. package/dist/backend/types.js +12 -1
  46. package/dist/backend/types.js.map +1 -1
  47. package/dist/backend-command.d.ts +91 -0
  48. package/dist/backend-command.js +122 -0
  49. package/dist/backend-command.js.map +1 -0
  50. package/dist/channel/adapters/discord.d.ts +14 -0
  51. package/dist/channel/adapters/discord.js +66 -4
  52. package/dist/channel/adapters/discord.js.map +1 -1
  53. package/dist/channel/adapters/telegram.d.ts +26 -1
  54. package/dist/channel/adapters/telegram.js +98 -7
  55. package/dist/channel/adapters/telegram.js.map +1 -1
  56. package/dist/channel/mcp-tools.js +36 -0
  57. package/dist/channel/mcp-tools.js.map +1 -1
  58. package/dist/channel/tool-router.d.ts +2 -0
  59. package/dist/channel/tool-router.js +1 -1
  60. package/dist/channel/tool-router.js.map +1 -1
  61. package/dist/channel/types.d.ts +31 -0
  62. package/dist/cli.js +124 -24
  63. package/dist/cli.js.map +1 -1
  64. package/dist/command-table.d.ts +2 -2
  65. package/dist/command-table.js +5 -2
  66. package/dist/command-table.js.map +1 -1
  67. package/dist/compact-watch.d.ts +86 -0
  68. package/dist/compact-watch.js +288 -0
  69. package/dist/compact-watch.js.map +1 -0
  70. package/dist/config-validator.js +55 -0
  71. package/dist/config-validator.js.map +1 -1
  72. package/dist/config.d.ts +1 -0
  73. package/dist/config.js +2 -1
  74. package/dist/config.js.map +1 -1
  75. package/dist/daemon.d.ts +9 -0
  76. package/dist/daemon.js +394 -29
  77. package/dist/daemon.js.map +1 -1
  78. package/dist/delivery-outbox.d.ts +8 -1
  79. package/dist/delivery-outbox.js +6 -2
  80. package/dist/delivery-outbox.js.map +1 -1
  81. package/dist/doctor.js +7 -0
  82. package/dist/doctor.js.map +1 -1
  83. package/dist/fleet-context.d.ts +12 -0
  84. package/dist/fleet-manager.d.ts +247 -9
  85. package/dist/fleet-manager.js +1227 -142
  86. package/dist/fleet-manager.js.map +1 -1
  87. package/dist/forged-envelope-scan.d.ts +4 -3
  88. package/dist/forged-envelope-scan.js +4 -3
  89. package/dist/forged-envelope-scan.js.map +1 -1
  90. package/dist/general-knowledge/skills/model-discovery/SKILL.md +4 -1
  91. package/dist/general-knowledge/skills/web-appearance/SKILL.md +258 -0
  92. package/dist/install-lock.d.ts +18 -1
  93. package/dist/install-lock.js +30 -3
  94. package/dist/install-lock.js.map +1 -1
  95. package/dist/instance-avatar.d.ts +5 -4
  96. package/dist/instance-avatar.js +15 -8
  97. package/dist/instance-avatar.js.map +1 -1
  98. package/dist/instance-config-impact.js +3 -0
  99. package/dist/instance-config-impact.js.map +1 -1
  100. package/dist/instance-lifecycle.js +9 -4
  101. package/dist/instance-lifecycle.js.map +1 -1
  102. package/dist/instructions.js +2 -2
  103. package/dist/instructions.js.map +1 -1
  104. package/dist/kiro-compact-pane.d.ts +38 -0
  105. package/dist/kiro-compact-pane.js +57 -0
  106. package/dist/kiro-compact-pane.js.map +1 -0
  107. package/dist/launchd-handoff.d.ts +43 -0
  108. package/dist/launchd-handoff.js +56 -0
  109. package/dist/launchd-handoff.js.map +1 -0
  110. package/dist/locale.js +202 -30
  111. package/dist/locale.js.map +1 -1
  112. package/dist/log-service.js +10 -2
  113. package/dist/log-service.js.map +1 -1
  114. package/dist/login-shell.d.ts +21 -0
  115. package/dist/login-shell.js +98 -0
  116. package/dist/login-shell.js.map +1 -0
  117. package/dist/needs-you-hub.d.ts +4 -0
  118. package/dist/needs-you-hub.js +31 -6
  119. package/dist/needs-you-hub.js.map +1 -1
  120. package/dist/needs-you.d.ts +1 -13
  121. package/dist/needs-you.js +3 -37
  122. package/dist/needs-you.js.map +1 -1
  123. package/dist/outbound-handlers.d.ts +32 -0
  124. package/dist/outbound-handlers.js +150 -45
  125. package/dist/outbound-handlers.js.map +1 -1
  126. package/dist/outbound-schemas.d.ts +76 -0
  127. package/dist/outbound-schemas.js +65 -4
  128. package/dist/outbound-schemas.js.map +1 -1
  129. package/dist/provider-probe.d.ts +29 -0
  130. package/dist/provider-probe.js +198 -2
  131. package/dist/provider-probe.js.map +1 -1
  132. package/dist/public-web-gateway.d.ts +2 -0
  133. package/dist/public-web-gateway.js +25 -1
  134. package/dist/public-web-gateway.js.map +1 -1
  135. package/dist/quickstart-api.d.ts +1 -1
  136. package/dist/quickstart-api.js +8 -6
  137. package/dist/quickstart-api.js.map +1 -1
  138. package/dist/quickstart.d.ts +12 -0
  139. package/dist/quickstart.js +36 -6
  140. package/dist/quickstart.js.map +1 -1
  141. package/dist/reply-buttons-controller.d.ts +86 -5
  142. package/dist/reply-buttons-controller.js +271 -7
  143. package/dist/reply-buttons-controller.js.map +1 -1
  144. package/dist/reply-buttons.d.ts +48 -3
  145. package/dist/reply-buttons.js +105 -10
  146. package/dist/reply-buttons.js.map +1 -1
  147. package/dist/reply-polls.d.ts +85 -0
  148. package/dist/reply-polls.js +154 -0
  149. package/dist/reply-polls.js.map +1 -0
  150. package/dist/repo-worktree-guard.d.ts +36 -0
  151. package/dist/repo-worktree-guard.js +143 -0
  152. package/dist/repo-worktree-guard.js.map +1 -0
  153. package/dist/restart-guard.d.ts +6 -3
  154. package/dist/restart-guard.js +6 -3
  155. package/dist/restart-guard.js.map +1 -1
  156. package/dist/restart-progress.d.ts +0 -2
  157. package/dist/restart-progress.js +0 -2
  158. package/dist/restart-progress.js.map +1 -1
  159. package/dist/scheduler/scheduler.d.ts +58 -1
  160. package/dist/scheduler/scheduler.js +176 -10
  161. package/dist/scheduler/scheduler.js.map +1 -1
  162. package/dist/sd-notify.d.ts +22 -1
  163. package/dist/sd-notify.js +63 -10
  164. package/dist/sd-notify.js.map +1 -1
  165. package/dist/service-activation.d.ts +15 -7
  166. package/dist/service-activation.js +42 -26
  167. package/dist/service-activation.js.map +1 -1
  168. package/dist/service-installer.d.ts +24 -0
  169. package/dist/service-installer.js +49 -1
  170. package/dist/service-installer.js.map +1 -1
  171. package/dist/service-plan.d.ts +11 -0
  172. package/dist/service-plan.js +23 -0
  173. package/dist/service-plan.js.map +1 -1
  174. package/dist/settings-api.js +74 -1
  175. package/dist/settings-api.js.map +1 -1
  176. package/dist/settings-change.js +8 -0
  177. package/dist/settings-change.js.map +1 -1
  178. package/dist/settings-effect.js +16 -2
  179. package/dist/settings-effect.js.map +1 -1
  180. package/dist/settings-http-confirmation.js +5 -0
  181. package/dist/settings-http-confirmation.js.map +1 -1
  182. package/dist/systemd-peer-bus.d.ts +95 -0
  183. package/dist/systemd-peer-bus.js +430 -0
  184. package/dist/systemd-peer-bus.js.map +1 -0
  185. package/dist/systemd-runtime.d.ts +27 -1
  186. package/dist/systemd-runtime.js +69 -7
  187. package/dist/systemd-runtime.js.map +1 -1
  188. package/dist/tool-permissions.js +53 -3
  189. package/dist/tool-permissions.js.map +1 -1
  190. package/dist/topic-commands.d.ts +72 -2
  191. package/dist/topic-commands.js +156 -38
  192. package/dist/topic-commands.js.map +1 -1
  193. package/dist/topic-link.d.ts +128 -0
  194. package/dist/topic-link.js +269 -0
  195. package/dist/topic-link.js.map +1 -0
  196. package/dist/transcript-jsonl.d.ts +7 -2
  197. package/dist/transcript-jsonl.js +13 -7
  198. package/dist/transcript-jsonl.js.map +1 -1
  199. package/dist/transcript-sources.js +6 -1
  200. package/dist/transcript-sources.js.map +1 -1
  201. package/dist/types.d.ts +21 -0
  202. package/dist/ui/app-effects.js +187 -0
  203. package/dist/ui/chat-commands.js +1 -0
  204. package/dist/ui/chat-effect-posters.js +42 -0
  205. package/dist/ui/chat-effects.js +72 -0
  206. package/dist/ui/chat-live-replies.js +58 -0
  207. package/dist/ui/chat-render.js +59 -14
  208. package/dist/ui/chat-store.js +78 -8
  209. package/dist/ui/chat-strings.js +43 -16
  210. package/dist/ui/chat-thread.js +175 -31
  211. package/dist/ui/chat-tour.js +18 -15
  212. package/dist/ui/effect-trial-controller.js +119 -0
  213. package/dist/ui/effect-trial-offer.js +31 -0
  214. package/dist/ui/effect-trial-panel.js +37 -0
  215. package/dist/ui/image-lightbox.js +1 -1
  216. package/dist/ui/panel-chat.js +101 -27
  217. package/dist/ui/panel-details.js +18 -5
  218. package/dist/ui/panel-fleet.js +18 -5
  219. package/dist/ui/panel-log.js +10 -10
  220. package/dist/ui/panel-needs.js +13 -10
  221. package/dist/ui/panel-settings.js +66 -12
  222. package/dist/ui/preview-panel.js +25 -9
  223. package/dist/ui/settings-appearance.js +325 -0
  224. package/dist/ui/settings-dialogs.js +11 -6
  225. package/dist/ui/settings-effects.js +201 -0
  226. package/dist/ui/settings-model.js +3 -1
  227. package/dist/ui/settings-mods.js +257 -0
  228. package/dist/ui/settings-owner-effects.js +80 -0
  229. package/dist/ui/settings-strings.js +269 -10
  230. package/dist/ui/settings-token.js +7 -0
  231. package/dist/ui/settings-wizard.js +5 -3
  232. package/dist/ui/shared/agend-auth.js +27 -3
  233. package/dist/ui/shared/agent-widget.js +248 -0
  234. package/dist/ui/shared/app-i18n.js +3 -1
  235. package/dist/ui/shared/app-prefs.js +30 -10
  236. package/dist/ui/shared/app-route.js +23 -1
  237. package/dist/ui/shared/app-session.js +1 -1
  238. package/dist/ui/shared/app-shell.js +29 -16
  239. package/dist/ui/shared/app-store.js +2 -0
  240. package/dist/ui/shared/app-stream.js +15 -7
  241. package/dist/ui/shared/app-themes.js +188 -0
  242. package/dist/ui/shared/app.css +266 -11
  243. package/dist/ui/shared/app.js +6 -0
  244. package/dist/ui/shared/card-strings.js +28 -0
  245. package/dist/ui/shared/effect-app-runtime.js +248 -0
  246. package/dist/ui/shared/effect-baseline.js +64 -0
  247. package/dist/ui/shared/effect-chat-runtime.js +154 -0
  248. package/dist/ui/shared/effect-cursor.js +125 -0
  249. package/dist/ui/shared/effect-frame.js +127 -0
  250. package/dist/ui/shared/effect-geometry.js +122 -0
  251. package/dist/ui/shared/effect-host-style.js +10 -0
  252. package/dist/ui/shared/effect-owner-regions.js +74 -0
  253. package/dist/ui/shared/effect-owner-source.js +115 -0
  254. package/dist/ui/shared/effect-policy.js +137 -0
  255. package/dist/ui/shared/effect-poster-reader.js +98 -0
  256. package/dist/ui/shared/effect-posters.js +117 -0
  257. package/dist/ui/shared/effect-profiler.js +162 -0
  258. package/dist/ui/shared/effect-profiles.js +65 -0
  259. package/dist/ui/shared/effect-registry.js +496 -0
  260. package/dist/ui/shared/effect-runtime-loader.js +234 -0
  261. package/dist/ui/shared/effect-safety.js +153 -0
  262. package/dist/ui/shared/effect-stock-snapshot.js +34 -0
  263. package/dist/ui/shared/effect-stock-trust.js +165 -0
  264. package/dist/ui/shared/effect-trial-source.js +103 -0
  265. package/dist/ui/shared/effect-trial-stage.js +147 -0
  266. package/dist/ui/shared/header-tools.js +35 -15
  267. package/dist/ui/shared/identity-card.js +202 -0
  268. package/dist/ui/shared/identity-color.js +261 -0
  269. package/dist/ui/shared/instance-nav.js +22 -14
  270. package/dist/ui/shared/instance-switch.js +5 -2
  271. package/dist/ui/shared/owner-css.js +221 -0
  272. package/dist/ui/shared/owner-effect-prefs.js +117 -0
  273. package/dist/ui/shared/panel-view.js +96 -12
  274. package/dist/ui/shared/signin.js +17 -9
  275. package/dist/ui/shared/ui-dialog.js +1 -1
  276. package/dist/ui/shared/ui-icons.js +3 -0
  277. package/dist/ui/shared/ui-menu.js +1 -1
  278. package/dist/ui/shared/ui-toast.js +1 -1
  279. package/dist/ui/shared/view-strings.js +40 -2
  280. package/dist/ui/signin.html +9 -3
  281. package/dist/update-check.d.ts +31 -0
  282. package/dist/update-check.js +47 -0
  283. package/dist/update-check.js.map +1 -1
  284. package/dist/update-launch.d.ts +48 -1
  285. package/dist/update-launch.js +159 -5
  286. package/dist/update-launch.js.map +1 -1
  287. package/dist/usage/providers.d.ts +28 -8
  288. package/dist/usage/providers.js +96 -41
  289. package/dist/usage/providers.js.map +1 -1
  290. package/dist/view-api.d.ts +29 -0
  291. package/dist/view-api.js +214 -17
  292. package/dist/view-api.js.map +1 -1
  293. package/dist/web-api.d.ts +86 -2
  294. package/dist/web-api.js +1160 -92
  295. package/dist/web-api.js.map +1 -1
  296. package/dist/web-appearance-limits.d.ts +82 -0
  297. package/dist/web-appearance-limits.js +83 -0
  298. package/dist/web-appearance-limits.js.map +1 -0
  299. package/dist/web-appearance-policy.d.ts +66 -0
  300. package/dist/web-appearance-policy.js +94 -0
  301. package/dist/web-appearance-policy.js.map +1 -0
  302. package/dist/web-appearance-tools.d.ts +50 -0
  303. package/dist/web-appearance-tools.js +105 -0
  304. package/dist/web-appearance-tools.js.map +1 -0
  305. package/dist/web-appearance-view.d.ts +99 -0
  306. package/dist/web-appearance-view.js +163 -0
  307. package/dist/web-appearance-view.js.map +1 -0
  308. package/dist/web-appearance.d.ts +79 -0
  309. package/dist/web-appearance.js +298 -0
  310. package/dist/web-appearance.js.map +1 -0
  311. package/dist/web-asset-parts.d.ts +98 -0
  312. package/dist/web-asset-parts.js +133 -0
  313. package/dist/web-asset-parts.js.map +1 -0
  314. package/dist/web-auth.js +17 -1
  315. package/dist/web-auth.js.map +1 -1
  316. package/dist/web-card.d.ts +96 -0
  317. package/dist/web-card.js +232 -0
  318. package/dist/web-card.js.map +1 -0
  319. package/dist/web-chat-history.d.ts +39 -1
  320. package/dist/web-chat-history.js +69 -9
  321. package/dist/web-chat-history.js.map +1 -1
  322. package/dist/web-commands.d.ts +39 -3
  323. package/dist/web-commands.js +22 -5
  324. package/dist/web-commands.js.map +1 -1
  325. package/dist/web-effect-bundle.d.ts +28 -0
  326. package/dist/web-effect-bundle.js +126 -0
  327. package/dist/web-effect-bundle.js.map +1 -0
  328. package/dist/web-effect-canonical.d.ts +8 -0
  329. package/dist/web-effect-canonical.js +27 -0
  330. package/dist/web-effect-canonical.js.map +1 -0
  331. package/dist/web-effect-library.d.ts +114 -0
  332. package/dist/web-effect-library.js +530 -0
  333. package/dist/web-effect-library.js.map +1 -0
  334. package/dist/web-effect-poster.d.ts +8 -0
  335. package/dist/web-effect-poster.js +52 -0
  336. package/dist/web-effect-poster.js.map +1 -0
  337. package/dist/web-effect-profiles.d.ts +118 -0
  338. package/dist/web-effect-profiles.js +180 -0
  339. package/dist/web-effect-profiles.js.map +1 -0
  340. package/dist/web-effect-service.d.ts +86 -0
  341. package/dist/web-effect-service.js +312 -0
  342. package/dist/web-effect-service.js.map +1 -0
  343. package/dist/web-effect-tools.d.ts +47 -0
  344. package/dist/web-effect-tools.js +126 -0
  345. package/dist/web-effect-tools.js.map +1 -0
  346. package/dist/web-effect-trials.d.ts +86 -0
  347. package/dist/web-effect-trials.js +204 -0
  348. package/dist/web-effect-trials.js.map +1 -0
  349. package/dist/web-effect-upload.d.ts +29 -0
  350. package/dist/web-effect-upload.js +190 -0
  351. package/dist/web-effect-upload.js.map +1 -0
  352. package/dist/web-effect-worker.d.ts +8 -0
  353. package/dist/web-effect-worker.js +170 -0
  354. package/dist/web-effect-worker.js.map +1 -0
  355. package/dist/web-effect.d.ts +89 -0
  356. package/dist/web-effect.js +132 -0
  357. package/dist/web-effect.js.map +1 -0
  358. package/dist/web-identity-color.d.ts +87 -0
  359. package/dist/web-identity-color.js +209 -0
  360. package/dist/web-identity-color.js.map +1 -0
  361. package/dist/web-identity-palette.d.ts +113 -0
  362. package/dist/web-identity-palette.js +84 -0
  363. package/dist/web-identity-palette.js.map +1 -0
  364. package/dist/web-image-meta.d.ts +25 -0
  365. package/dist/web-image-meta.js +200 -0
  366. package/dist/web-image-meta.js.map +1 -0
  367. package/dist/web-login.d.ts +14 -0
  368. package/dist/web-login.js +4 -2
  369. package/dist/web-login.js.map +1 -1
  370. package/dist/web-mod-agent.d.ts +50 -0
  371. package/dist/web-mod-agent.js +203 -0
  372. package/dist/web-mod-agent.js.map +1 -0
  373. package/dist/web-mod-bundle.d.ts +144 -0
  374. package/dist/web-mod-bundle.js +464 -0
  375. package/dist/web-mod-bundle.js.map +1 -0
  376. package/dist/web-mod-export.d.ts +59 -0
  377. package/dist/web-mod-export.js +79 -0
  378. package/dist/web-mod-export.js.map +1 -0
  379. package/dist/web-mod-store.d.ts +283 -0
  380. package/dist/web-mod-store.js +624 -0
  381. package/dist/web-mod-store.js.map +1 -0
  382. package/dist/web-org.d.ts +3 -3
  383. package/dist/web-org.js +11 -17
  384. package/dist/web-org.js.map +1 -1
  385. package/dist/web-owner-css-store.d.ts +180 -0
  386. package/dist/web-owner-css-store.js +384 -0
  387. package/dist/web-owner-css-store.js.map +1 -0
  388. package/dist/web-owner-css.d.ts +58 -0
  389. package/dist/web-owner-css.js +552 -0
  390. package/dist/web-owner-css.js.map +1 -0
  391. package/dist/web-prefs.d.ts +66 -0
  392. package/dist/web-prefs.js +243 -35
  393. package/dist/web-prefs.js.map +1 -1
  394. package/dist/web-preview.d.ts +9 -1
  395. package/dist/web-preview.js +24 -7
  396. package/dist/web-preview.js.map +1 -1
  397. package/dist/web-profile.d.ts +32 -0
  398. package/dist/web-profile.js +132 -0
  399. package/dist/web-profile.js.map +1 -0
  400. package/dist/web-session.d.ts +14 -0
  401. package/dist/web-session.js +10 -0
  402. package/dist/web-session.js.map +1 -1
  403. package/dist/web-settings-patch.d.ts +18 -0
  404. package/dist/web-settings-patch.js +51 -0
  405. package/dist/web-settings-patch.js.map +1 -1
  406. package/dist/web-shell-routes.d.ts +1 -1
  407. package/dist/web-shell-routes.js +1 -1
  408. package/dist/web-shell-routes.js.map +1 -1
  409. package/dist/web-theme-store.d.ts +218 -0
  410. package/dist/web-theme-store.js +516 -0
  411. package/dist/web-theme-store.js.map +1 -0
  412. package/dist/web-theme.d.ts +121 -0
  413. package/dist/web-theme.js +407 -0
  414. package/dist/web-theme.js.map +1 -0
  415. package/dist/web-upload.d.ts +119 -4
  416. package/dist/web-upload.js +252 -39
  417. package/dist/web-upload.js.map +1 -1
  418. package/dist/web-widget.d.ts +72 -0
  419. package/dist/web-widget.js +245 -0
  420. package/dist/web-widget.js.map +1 -0
  421. package/launcher/install-admission.cjs +61 -6
  422. package/package.json +3 -3
@@ -0,0 +1,98 @@
1
+ /** #1629: closed static-poster reads only. No epoch, library/runtime route, code, or response-derived URL. */
2
+ const pools = new WeakMap(), ID = /^[0-9a-f]{32}$/;
3
+ const plain = x => !!x && (Object.getPrototypeOf(x) === Object.prototype || Object.getPrototypeOf(x) === null);
4
+ const exact = (x, keys) => plain(x) && Reflect.ownKeys(x).length === keys.length && keys.every(k => Object.hasOwn(x, k) && Object.hasOwn(Object.getOwnPropertyDescriptor(x, k), "value"));
5
+ const instanceOK = x => typeof x === "string" && x.length >= 1 && x.length <= 128;
6
+ export function checkEffectPosters(value) {
7
+ if (!exact(value, ["rev", "posters"]) || !Number.isSafeInteger(value.rev) || value.rev < 0 || !Array.isArray(value.posters) || value.posters.length > 2) return null;
8
+ const out = [], ids = new Set(), descriptors = Object.getOwnPropertyDescriptors(value.posters);
9
+ if (Reflect.ownKeys(descriptors).length !== value.posters.length + 1) return null;
10
+ for (let n = 0; n < value.posters.length; n++) {
11
+ const descriptor = descriptors[n]; if (!descriptor || !Object.hasOwn(descriptor, "value")) return null;
12
+ const p = descriptor.value;
13
+ // Titles are display-only, never selectors/URLs/CSS. The server's normalized UTF-16 input bound is 400.
14
+ if (!exact(p, ["id", "title"]) || typeof p.id !== "string" || !ID.test(p.id) || ids.has(p.id) || typeof p.title !== "string" || p.title.length < 1 || p.title.length > 400) return null;
15
+ ids.add(p.id); out.push(Object.freeze({ id: p.id, title: p.title }));
16
+ }
17
+ return Object.freeze({ rev: value.rev, posters: Object.freeze(out) });
18
+ }
19
+ /** Refuse animation/non-PNG/oversized images before DOM decode. This is not a CRC or compressed-data decoder. */
20
+ export function checkStaticEffectPoster(bytes) {
21
+ if (!(bytes instanceof Uint8Array) || bytes.length < 45 || bytes.length > 1048576 || [137, 80, 78, 71, 13, 10, 26, 10].some((b, n) => bytes[n] !== b)) return null;
22
+ const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength), width = view.getUint32(16), height = view.getUint32(20);
23
+ if (!width || !height || width > 4096 || height > 4096 || width * height > 8388608) return null;
24
+ let offset = 8, count = 0, data = false;
25
+ while (offset + 12 <= bytes.length && ++count <= 4096) {
26
+ const length = view.getUint32(offset), type = String.fromCharCode(...bytes.subarray(offset + 4, offset + 8));
27
+ if (offset + length + 12 > bytes.length || ["acTL", "fcTL", "fdAT"].includes(type) || (count === 1 ? type !== "IHDR" || length !== 13 : type === "IHDR")) return null;
28
+ if (type === "IDAT" && length) data = true;
29
+ offset += length + 12;
30
+ if (type === "IEND") return !length && data && offset === bytes.length ? new Uint8Array(bytes) : null;
31
+ }
32
+ return null;
33
+ }
34
+ /** Two physical requests per Window, across canceled/replaced readers; cancellation does not release early. */
35
+ export function createEffectPosterReader({ window: host, current } = {}) {
36
+ const unavailable = { ok: false, dispose() {} };
37
+ if (!host || typeof current !== "function" || ![host.fetch, host.AbortController, host.setTimeout, host.clearTimeout, host.performance?.now].every(x => typeof x === "function")) return unavailable;
38
+ if (!pools.has(host)) pools.set(host, new Set());
39
+ const all = pools.get(host), own = new Set(); let dead = false, revoked = false, previous = null;
40
+ const alive = () => !dead && !revoked && current() === true;
41
+ function stop() { if (dead) return; dead = true; for (const op of own) op.abort(); }
42
+ function now() {
43
+ let n; try { n = host.performance.now(); } catch { stop(); return null; }
44
+ if (!Number.isFinite(n) || n < 0 || previous !== null && n < previous) { stop(); return null; }
45
+ previous = n; return n;
46
+ }
47
+ function read(path, mime, limit, project, signal) {
48
+ if (!alive() || signal?.aborted || all.size >= 2) return Promise.resolve(null);
49
+ const began = now(); if (began === null) return Promise.resolve(null);
50
+ const controller = new host.AbortController(), op = {}; all.add(op); own.add(op);
51
+ let reader = null, body = null, cancellation = null, timer, release;
52
+ const cancelled = new Promise(resolve => { release = resolve; });
53
+ const cancelBody = () => {
54
+ if (cancellation) return cancellation;
55
+ try { cancellation = Promise.resolve(reader ? reader.cancel() : body?.cancel?.()).catch(() => {}); }
56
+ catch { cancellation = Promise.resolve(); }
57
+ return cancellation;
58
+ };
59
+ op.abort = () => { controller.abort(); if (body) void cancelBody(); release(null); };
60
+ const valid = () => {
61
+ const t = now();
62
+ if (!alive() || controller.signal.aborted || t === null || t < began || t - began >= 10000) { op.abort(); return false; }
63
+ return true;
64
+ };
65
+ const work = Promise.resolve().then(async () => {
66
+ if (!valid()) return null;
67
+ const response = await host.fetch(path, { credentials: "same-origin", cache: "no-store", redirect: "error", signal: controller.signal }); body = response.body;
68
+ if (!valid()) return null;
69
+ if ([401, 403, 409].includes(response.status)) { revoked = true; for (const pending of own) pending.abort(); return null; }
70
+ if (!response.ok || response.redirected || (response.headers.get("content-type") || "").split(";")[0].trim().toLowerCase() !== mime) return null;
71
+ const length = response.headers.get("content-length");
72
+ if (length !== null && (!/^(0|[1-9][0-9]*)$/.test(length) || !Number.isSafeInteger(Number(length)) || Number(length) > limit) || typeof body?.getReader !== "function") return null;
73
+ reader = body.getReader(); const chunks = []; let size = 0;
74
+ for (;;) {
75
+ const part = await reader.read(); if (!valid()) return null;
76
+ if (part.done) break;
77
+ if (!(part.value instanceof Uint8Array) || (size += part.value.length) > limit) return null;
78
+ chunks.push(new Uint8Array(part.value));
79
+ }
80
+ const bytes = new Uint8Array(size); let offset = 0; for (const part of chunks) { bytes.set(part, offset); offset += part.length; }
81
+ if (!valid()) return null; const answer = project(bytes); return valid() ? answer : null;
82
+ }).catch(() => null).finally(async () => {
83
+ controller.abort(); await cancelBody(); all.delete(op); own.delete(op); host.clearTimeout(timer); signal?.removeEventListener("abort", op.abort);
84
+ });
85
+ timer = host.setTimeout(op.abort, 10000); signal?.addEventListener("abort", op.abort, { once: true });
86
+ return Promise.race([work, cancelled]);
87
+ }
88
+ return { ok: true,
89
+ summary(instance, signal) {
90
+ return instanceOK(instance) ? read(`/ui/effect-posters?instance=${encodeURIComponent(instance)}`, "application/json", 8192, bytes => {
91
+ try { return checkEffectPosters(JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes))); } catch { return null; }
92
+ }, signal) : Promise.resolve(null);
93
+ },
94
+ image(instance, id, signal) {
95
+ return instanceOK(instance) && typeof id === "string" && ID.test(id) ? read(`/ui/effect-posters/${id}.png?instance=${encodeURIComponent(instance)}`, "image/png", 1048576, checkStaticEffectPoster, signal) : Promise.resolve(null);
96
+ }, dispose: stop,
97
+ };
98
+ }
@@ -0,0 +1,117 @@
1
+ /** #1629: static current-chat posters. No Worker, preview frame, events, profile write or personal-slot authority. */
2
+ import { createEffectPosterReader, checkEffectPosters } from "./effect-poster-reader.js";
3
+ import { createEffectGeometry, hostEffectRect } from "./effect-geometry.js";
4
+ import { stockEffectTrust } from "./effect-stock-trust.js";
5
+ import { effectStockSnapshot } from "./effect-stock-snapshot.js";
6
+ const decoding = new WeakMap();
7
+ export function createChatPosters({ window: host, name, element, current, observe, factories = {} } = {}) {
8
+ let dead = false, failed = false, generation = 0, allowed = false, metadata = null, request = null, queued = false, raf = null;
9
+ let root = null, geometry = null, reader = null, mutation = null, resize = null, offAuth = null;
10
+ const rows = new Map(), offs = [];
11
+ const alive = () => !dead && current() === true && element?.isConnected === true && element.ownerDocument === host.document && host.document.body.dataset.mode === "full" && !host.AgendAuth.sessionEnded();
12
+ const make = (key, fallback) => factories[key] ?? fallback;
13
+ const gate = () => {
14
+ try {
15
+ const x = observe();
16
+ if (!alive() || x.shown !== true || x.fallback !== true || host.document.visibilityState !== "visible") return null;
17
+ const rect = hostEffectRect(element), proof = effectStockSnapshot(make("stock", stockEffectTrust)(host, { frameRoot: root, ownerCss: x.ownerCss }));
18
+ if (!rect || !proof || rect.x + rect.width <= 0 || rect.y + rect.height <= 0 || rect.x >= host.innerWidth || rect.y >= host.innerHeight) return null;
19
+ return { rect, proof };
20
+ } catch { return null; }
21
+ };
22
+ function clear() {
23
+ for (const row of rows.values()) {
24
+ row.controller.abort(); row.binding?.remove?.(); row.image.remove();
25
+ if (row.url) host.URL.revokeObjectURL(row.url);
26
+ }
27
+ rows.clear(); geometry?.hide?.();
28
+ }
29
+ function cancel() { generation++; request?.controller.abort(); clear(); metadata = null; }
30
+ function fail() { if (dead) return; failed = true; cancel(); }
31
+ function dispose() {
32
+ if (dead) return; dead = true; cancel(); reader?.dispose?.();
33
+ if (raf !== null) host.cancelAnimationFrame(raf); raf = null;
34
+ for (const off of offs) { try { off(); } catch { /* release all */ } }
35
+ offAuth?.(); mutation?.disconnect(); resize?.disconnect(); geometry?.dispose?.(); root?.remove();
36
+ }
37
+ const unavailable = () => ({ ok: false, dispose });
38
+ try {
39
+ if (!host?.document || typeof current !== "function" || typeof observe !== "function" || typeof name !== "string" || !name.length || name.length > 128 || host.document.body.dataset.mode !== "full" ||
40
+ ![host.MutationObserver, host.ResizeObserver, host.requestAnimationFrame, host.cancelAnimationFrame, host.URL?.createObjectURL, host.URL?.revokeObjectURL, host.Blob, host.AbortController, host.setTimeout, host.clearTimeout, host.performance?.now, host.AgendAuth?.onSessionEnd, host.AgendAuth?.sessionEnded].every(x => typeof x === "function")) return unavailable();
41
+ offAuth = host.AgendAuth.onSessionEnd(dispose);
42
+ if (!alive()) { dispose(); return unavailable(); }
43
+ root = host.document.createElement("div"); root.className = "agend-effect-layer"; host.document.body.append(root);
44
+ geometry = make("geometry", createEffectGeometry)({ window: host }); if (!geometry.ok) throw Error("geometry unavailable");
45
+ reader = make("reader", createEffectPosterReader)({ window: host, current: alive }); if (!reader.ok) throw Error("reader unavailable");
46
+ if (!decoding.has(host)) decoding.set(host, new Set());
47
+ mutation = new host.MutationObserver(invalidate); mutation.observe(host.document.documentElement, { subtree: true, childList: true, attributes: true, characterData: true });
48
+ resize = new host.ResizeObserver(invalidate); resize.observe(host.document.documentElement); resize.observe(element);
49
+ for (const type of ["scroll", "resize", "orientationchange"]) { host.addEventListener(type, invalidate, true); offs.push(() => host.removeEventListener(type, invalidate, true)); }
50
+ const visibility = () => { if (host.document.visibilityState !== "visible") { cancel(); allowed = false; } invalidate(); };
51
+ host.document.addEventListener("visibilitychange", visibility); offs.push(() => host.document.removeEventListener("visibilitychange", visibility));
52
+ host.addEventListener("pagehide", dispose); offs.push(() => host.removeEventListener("pagehide", dispose));
53
+ } catch { dispose(); return unavailable(); }
54
+ function schedule() { if (alive() && raf === null) raf = host.requestAnimationFrame(() => { raf = null; sync(); }); }
55
+ function invalidate() { if (dead) return; geometry.hide(); schedule(); }
56
+ function valid(mine) { return alive() && generation === mine && !failed && !!gate(); }
57
+ async function loadImage(row, mine) {
58
+ try {
59
+ const bytes = await reader.image(name, row.id, row.controller.signal);
60
+ if (!valid(mine) || rows.get(row.id) !== row) return;
61
+ if (!bytes) { fail(); return; }
62
+ const pending = decoding.get(host); if (pending.size >= 2) { fail(); return; }
63
+ const token = {}; pending.add(token);
64
+ let timer = null, aborted = false;
65
+ const abort = () => { aborted = true; row.controller.abort(); if (rows.get(row.id) === row) fail(); };
66
+ try {
67
+ const began = host.performance.now();
68
+ if (!Number.isFinite(began) || began < 0) { fail(); return; }
69
+ timer = host.setTimeout(abort, 10000);
70
+ row.url = host.URL.createObjectURL(new host.Blob([bytes], { type: "image/png" }));
71
+ row.image.src = row.url;
72
+ await row.image.decode();
73
+ if (aborted || !valid(mine) || rows.get(row.id) !== row) return;
74
+ const ended = host.performance.now();
75
+ if (!Number.isFinite(ended) || ended < began || ended - began >= 10000) { fail(); return; }
76
+ const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
77
+ if (row.image.naturalWidth !== view.getUint32(16) || row.image.naturalHeight !== view.getUint32(20)) { fail(); return; }
78
+ row.ready = true; sync();
79
+ } finally { host.clearTimeout(timer); pending.delete(token); }
80
+ } catch { if (alive() && generation === mine) fail(); }
81
+ }
82
+ async function read() {
83
+ const op = { controller: new host.AbortController(), generation }; request = op;
84
+ try {
85
+ const result = await reader.summary(name, op.controller.signal);
86
+ if (!valid(op.generation) || request !== op) return;
87
+ const checked = checkEffectPosters(result);
88
+ if (!checked) { fail(); return; }
89
+ metadata = checked;
90
+ for (const p of checked.posters) {
91
+ const image = host.document.createElement("img"); image.className = "agend-effect-poster"; image.setAttribute("alt", ""); image.setAttribute("aria-hidden", "true"); image.decoding = "async";
92
+ root.append(image);
93
+ const binding = geometry.bind(image, { layer: "behind", by: "agent" });
94
+ if (!binding.ok || typeof image.decode !== "function") { binding.remove?.(); image.remove(); fail(); return; }
95
+ const row = { id: p.id, image, binding, controller: new host.AbortController(), url: null, ready: false }; rows.set(p.id, row);
96
+ // The IMG is proved hidden synchronously before any source is assigned or control yields.
97
+ void loadImage(row, op.generation);
98
+ }
99
+ } catch { if (alive() && generation === op.generation) fail(); }
100
+ finally { if (request === op) request = null; if (queued && alive()) { queued = false; sync(); } }
101
+ }
102
+ function sync() {
103
+ if (!alive()) { dispose(); return; }
104
+ geometry.hide();
105
+ const evidence = gate();
106
+ if (!evidence) { if (allowed) cancel(); allowed = false; return; }
107
+ allowed = true;
108
+ if (failed) return;
109
+ for (const row of rows.values()) if (row.ready && !row.binding.sync(evidence.rect, evidence.proof)) { fail(); return; }
110
+ if (!metadata && !request) void read();
111
+ if (rows.size) schedule();
112
+ }
113
+ return { ok: true, sync,
114
+ changed() { if (!alive()) return; cancel(); failed = false; if (request) queued = true; else sync(); },
115
+ dispose, snapshot: () => ({ failed, count: rows.size, ready: [...rows.values()].filter(r => r.ready).length }),
116
+ };
117
+ }
@@ -0,0 +1,162 @@
1
+ /** #1629: host observations only. This pure module mounts nothing and accepts no frame/Worker messages. */
2
+ const finite = v => typeof v === "number" && Number.isFinite(v);
3
+ const plain = v => !!v && (Object.getPrototypeOf(v) === Object.prototype || Object.getPrototypeOf(v) === null);
4
+ const DAY = 86400000;
5
+ export const EFFECT_BUDGETS = Object.freeze({
6
+ standard: Object.freeze({ fps: 55, longTaskMsPerSecond: 50 }),
7
+ constrained: Object.freeze({ fps: 50, longTaskMsPerSecond: 25 }),
8
+ });
9
+ const deviceClass = v => v === "standard" || v === "constrained";
10
+ const id = v => typeof v === "string" && /^[0-9a-f]{32}$/.test(v);
11
+
12
+ /** One host-owned mount. The registry must remove the frame when remove is true, never merely hide it. */
13
+ export class EffectBudget {
14
+ constructor({ device, baselineFps, clock = () => performance.now(), longTasksSupported = false } = {}) {
15
+ if (!deviceClass(device) || !finite(baselineFps) || baselineFps <= 0 || baselineFps > 1000 || typeof clock !== "function" || typeof longTasksSupported !== "boolean") throw new TypeError("effect budget options");
16
+ this.device = device;
17
+ // Measured by the host while NO effects are mounted; never author-supplied or learned from a running effect.
18
+ this.fpsFloor = Math.min(EFFECT_BUDGETS[device].fps, baselineFps * 0.9);
19
+ this.clock = clock;
20
+ this.longTasksSupported = longTasksSupported;
21
+ this.last = null;
22
+ this.epoch = this.time();
23
+ this.lastWindow = null;
24
+ this.frames = [];
25
+ this.tasks = [];
26
+ this.overflow = false;
27
+ this.hiddenAt = null;
28
+ this.verdict = "ok";
29
+ this.reason = null;
30
+ this.badWindows = 0;
31
+ this.metrics = null;
32
+ this.loadMs = null;
33
+ if (this.epoch === null) this.stop("clock_unavailable");
34
+ }
35
+ time() {
36
+ let now;
37
+ try { now = this.clock(); } catch { return null; }
38
+ if (!finite(now) || now < 0 || this.last !== null && now < this.last) return null;
39
+ this.last = now;
40
+ return now;
41
+ }
42
+ stop(reason = "stopped") {
43
+ if (this.verdict === "paused") return this.snapshot();
44
+ this.verdict = "paused";
45
+ this.reason = reason;
46
+ this.frames = [];
47
+ this.tasks = [];
48
+ return this.snapshot();
49
+ }
50
+ /** Invoke on host-observed load, not on child-provided timing. First load only. */
51
+ loaded() {
52
+ const now = this.time();
53
+ if (now === null) return this.stop("clock_unavailable");
54
+ if (this.verdict !== "paused" && this.loadMs === null) this.loadMs = now - this.epoch;
55
+ return this.snapshot();
56
+ }
57
+ /** Invoke from the host's rAF callback; elapsed time always comes from the host clock. */
58
+ frame() {
59
+ const now = this.time();
60
+ if (now === null) return this.stop("clock_unavailable");
61
+ if (this.verdict === "paused" || this.hiddenAt !== null || now < this.epoch + 1000) return this.snapshot();
62
+ this.trim(now);
63
+ if (this.frames.at(-1) !== now) {
64
+ if (this.frames.length >= 2048) this.overflow = true;
65
+ else this.frames.push(now);
66
+ }
67
+ return this.snapshot();
68
+ }
69
+ /** Only the host observer calls this. `attributed` means it matched THIS host-owned container. */
70
+ longTask(start, duration, attributed = false) {
71
+ const now = this.time();
72
+ if (now === null) return this.stop("clock_unavailable");
73
+ if (this.verdict === "paused" || this.hiddenAt !== null || !this.longTasksSupported) return false;
74
+ if (!finite(start) || start < 0 || !finite(duration) || duration < 50 || duration > 60000 || start + duration > now || typeof attributed !== "boolean") return false;
75
+ const from = Math.max(start, this.epoch + 1000, now - 2000), to = start + duration;
76
+ if (from >= to) return false;
77
+ this.trim(now);
78
+ if (this.tasks.length >= 256) { this.overflow = true; return false; }
79
+ this.tasks.push({ from, to, attributed });
80
+ return true;
81
+ }
82
+ trim(now) {
83
+ this.frames = this.frames.filter(t => t > now - 2000);
84
+ this.tasks = this.tasks.filter(t => t.to > now - 2000);
85
+ }
86
+ /** Strict booleans: unknown page gates fail closed. No setting can revive a paused mount. */
87
+ gates(raw = {}) {
88
+ const now = this.time();
89
+ if (now === null) return this.stop("clock_unavailable");
90
+ if (!plain(raw)) return this.stop("gates_unknown");
91
+ const { hidden, reducedMotion, saveData, show } = raw;
92
+ if ([hidden, reducedMotion, saveData, show].some(v => typeof v !== "boolean")) return this.stop("gates_unknown");
93
+ if (this.verdict === "paused") return this.snapshot();
94
+ if (reducedMotion || saveData || !show) return this.stop(reducedMotion ? "reduced_motion" : saveData ? "save_data" : "viewer_off");
95
+ if (hidden && this.hiddenAt === null) this.hiddenAt = now;
96
+ if (hidden && now - this.hiddenAt >= 10000) return this.stop("hidden");
97
+ if (!hidden && this.hiddenAt !== null) {
98
+ // A delayed visibility callback must not revive a frame whose removal deadline already passed.
99
+ if (now - this.hiddenAt >= 10000) return this.stop("hidden");
100
+ this.hiddenAt = null;
101
+ this.epoch = now;
102
+ this.lastWindow = null;
103
+ this.frames = [];
104
+ this.tasks = [];
105
+ this.overflow = false;
106
+ this.badWindows = 0;
107
+ this.metrics = null;
108
+ }
109
+ return this.snapshot();
110
+ }
111
+ /** Poll no faster than once per second; a delayed callback never fabricates missed windows. */
112
+ poll() {
113
+ const now = this.time();
114
+ if (now === null) return this.stop("clock_unavailable");
115
+ if (this.verdict === "paused") return this.snapshot();
116
+ if (this.hiddenAt !== null) return now - this.hiddenAt >= 10000 ? this.stop("hidden") : this.snapshot();
117
+ if (now < this.epoch + 3000 || this.lastWindow !== null && now - this.lastWindow < 1000) return this.snapshot();
118
+ this.lastWindow = now;
119
+ this.trim(now);
120
+ const sum = attributed => this.tasks.filter(t => attributed === null || t.attributed === attributed)
121
+ .reduce((n, t) => n + Math.max(0, t.to - Math.max(t.from, now - 2000)), 0) / 2;
122
+ const fps = this.frames.length / 2;
123
+ const hostMs = this.longTasksSupported ? sum(null) : null;
124
+ const ownMs = this.longTasksSupported && this.tasks.some(t => t.attributed) ? sum(true) : null;
125
+ const limits = EFFECT_BUDGETS[this.device];
126
+ const bad = this.overflow || fps < this.fpsFloor || hostMs !== null && hostMs > limits.longTaskMsPerSecond;
127
+ this.metrics = { at: now, hostFps: fps, hostLongTaskMsPerSecond: hostMs, attributedLongTaskMsPerSecond: ownMs,
128
+ attribution: ownMs === null ? "unknown" : "observed", fpsFloor: this.fpsFloor, windowMs: 2000 };
129
+ this.overflow = false;
130
+ if (bad) {
131
+ this.badWindows++;
132
+ this.verdict = "degraded";
133
+ this.reason = "host_budget";
134
+ if (this.badWindows >= 3) return this.stop("host_budget");
135
+ } else this.badWindows = 0;
136
+ return this.snapshot();
137
+ }
138
+ snapshot() {
139
+ const remove = this.verdict === "paused";
140
+ return { verdict: this.verdict, reason: this.reason, remove, forward: !remove && this.hiddenAt === null,
141
+ quality: this.verdict === "ok" ? "normal" : "low", pointerHz: this.verdict === "ok" ? 60 : 30,
142
+ badWindows: this.badWindows, loadMs: this.loadMs, metrics: this.metrics ? { ...this.metrics } : null };
143
+ }
144
+ }
145
+
146
+ /** Persisted calendar policy is separate from monotonic measurement. No storage is opened by this module. */
147
+ export function newEffectPauseHistory(effectId, device, wall) {
148
+ if (!id(effectId) || !deviceClass(device) || !finite(wall) || wall < 0) return null;
149
+ return { v: 1, id: effectId, device, pauses: [], off: false, lastWall: wall };
150
+ }
151
+ export function recordEffectPause(history, wall) {
152
+ if (!plain(history) || Object.keys(history).sort().join(",") !== "device,id,lastWall,off,pauses,v" || history.v !== 1 || !id(history.id) || !deviceClass(history.device) || typeof history.off !== "boolean" || !finite(history.lastWall) || history.lastWall < 0 || !finite(wall) || wall < 0 || !Array.isArray(history.pauses) || history.pauses.length > 3) return { ok: false, error: "pause_history_unknown" };
153
+ let previous = -1;
154
+ for (const at of history.pauses) {
155
+ if (!finite(at) || at < 0 || at < previous || at > history.lastWall) return { ok: false, error: "pause_history_unknown" };
156
+ previous = at;
157
+ }
158
+ // A wall-clock rollback cannot age out pauses. A forward jump can; off remains sticky for this content id.
159
+ const now = Math.max(wall, history.lastWall);
160
+ const pauses = [...history.pauses.filter(t => t > now - DAY), now].slice(-3);
161
+ return { ok: true, history: { ...history, pauses, lastWall: now, off: history.off || pauses.length >= 3 } };
162
+ }
@@ -0,0 +1,65 @@
1
+ /** #1629: bounded host-window quantiles; not a frame message handler or admission verdict. */
2
+ const plain = o => !!o && (Object.getPrototypeOf(o) === Object.prototype || Object.getPrototypeOf(o) === null);
3
+ const finite = x => typeof x === "number" && Number.isFinite(x);
4
+ const bounded = (x, max) => finite(x) && x >= 0 && x <= max;
5
+ const reasons = [null, "host_budget", "clock_unavailable", "gates_unknown", "hidden", "reduced_motion", "save_data", "viewer_off"];
6
+ const keys = ["key", "id", "device", "verdict", "reason", "remove", "forward", "quality", "pointerHz", "badWindows", "loadMs", "metrics"];
7
+ const metricKeys = ["at", "hostFps", "hostLongTaskMsPerSecond", "attributedLongTaskMsPerSecond", "attribution", "fpsFloor", "windowMs"];
8
+ const exact = (o, allowed) => plain(o) && Object.keys(o).sort().join(",") === [...allowed].sort().join(",");
9
+ const quantile = (values, q) => { if (!values.length) return null; const a = [...values].sort((x, y) => x - y); return a[Math.max(0, Math.ceil(q * a.length) - 1)]; };
10
+
11
+ /** A new collector belongs to one exact registry key/id/class mount. It never reads message content or child data. */
12
+ export function createEffectProfileCollector(options = {}) {
13
+ let key, id, device, clock;
14
+ try { if (!exact(options, ["key", "id", "device", "clock"])) return { ok: false }; ({ key, id, device, clock } = options); } catch { return { ok: false }; }
15
+ if (typeof key !== "string" || key.length < 1 || key.length > 256 || typeof id !== "string" || !/^[a-f0-9]{32}$/.test(id) || !["standard", "constrained"].includes(device) || typeof clock !== "function") return { ok: false };
16
+ let dead = false, previous = null, lastSample = null, floor = null, last = null;
17
+ const samples = [];
18
+ const time = () => {
19
+ let n; try { n = clock(); } catch { dead = true; return null; }
20
+ if (!finite(n) || n < 0 || previous !== null && n < previous) { dead = true; return null; }
21
+ previous = n; return n;
22
+ };
23
+ if (time() === null) return { ok: false };
24
+ return { ok: true,
25
+ add(raw) {
26
+ if (dead) return false;
27
+ try {
28
+ // Reject unknown keys without reading their values or accessors.
29
+ if (!exact(raw, keys) || raw.key !== key || raw.id !== id || raw.device !== device || !["ok", "degraded", "paused"].includes(raw.verdict) || !reasons.includes(raw.reason)
30
+ || typeof raw.remove !== "boolean" || typeof raw.forward !== "boolean" || raw.remove !== (raw.verdict === "paused") || raw.remove && raw.forward
31
+ || raw.quality !== (raw.verdict === "ok" ? "normal" : "low") || raw.pointerHz !== (raw.verdict === "ok" ? 60 : 30)
32
+ || !Number.isInteger(raw.badWindows) || raw.badWindows < 0 || raw.badWindows > 3 || raw.loadMs !== null && !bounded(raw.loadMs, 600000)) { dead = true; return false; }
33
+ const n = time(); if (n === null) return false;
34
+ // Warmup is not an observed window and never turns missing measurements into zero cost.
35
+ if (raw.metrics === null) return false;
36
+ const m = raw.metrics;
37
+ if (!exact(m, metricKeys) || !finite(m.at) || m.at < 0 || m.at > n || !bounded(m.hostFps, 1000) || !finite(m.fpsFloor) || m.fpsFloor <= 0 || m.fpsFloor > 1000 || m.windowMs !== 2000
38
+ || m.hostLongTaskMsPerSecond !== null && !bounded(m.hostLongTaskMsPerSecond, 256000)
39
+ || m.attributedLongTaskMsPerSecond !== null && (!bounded(m.attributedLongTaskMsPerSecond, 256000) || m.hostLongTaskMsPerSecond === null || m.attributedLongTaskMsPerSecond > m.hostLongTaskMsPerSecond)
40
+ || m.attribution !== (m.attributedLongTaskMsPerSecond === null ? "unknown" : "observed") || floor !== null && m.fpsFloor !== floor) { dead = true; return false; }
41
+ // Only poll() creates a window. rAF/gates/load/stop snapshots retain its immutable host timestamp.
42
+ // A paused duplicate may describe the last verdict, but cannot renew the observation or add a sample.
43
+ if (lastSample !== null && m.at === lastSample && last) last = { verdict: raw.verdict, reason: raw.reason, loadMs: raw.loadMs };
44
+ if (n - m.at > 5000 || lastSample !== null && m.at - lastSample < 1000) return false;
45
+ floor = m.fpsFloor; lastSample = m.at;
46
+ samples.push({ fps: m.hostFps, task: m.hostLongTaskMsPerSecond, own: m.attributedLongTaskMsPerSecond });
47
+ if (samples.length > 120) samples.shift();
48
+ last = { verdict: raw.verdict, reason: raw.reason, loadMs: raw.loadMs };
49
+ return true;
50
+ } catch { dead = true; return false; }
51
+ },
52
+ report(options = {}) {
53
+ let off;
54
+ try { if (!plain(options) || Object.keys(options).some(k => k !== "off")) return null; off = options.off ?? false; } catch { dead = true; return null; }
55
+ if (dead || typeof off !== "boolean" || lastSample === null || !last) return null;
56
+ const n = time(); if (n === null || n - lastSample > 5000) return null;
57
+ const host = samples.filter(s => s.task !== null).map(s => s.task), own = samples.filter(s => s.own !== null).map(s => s.own), fps = samples.map(s => s.fps);
58
+ return { id, profile: { v: 1, device, verdict: off ? "off" : last.verdict, reason: last.reason, samples: samples.length,
59
+ fps_p50: quantile(fps, 0.5), fps_p10: quantile(fps, 0.1), fps_floor: floor,
60
+ long_task_p95: quantile(host, 0.95), long_task_samples: host.length,
61
+ attributed_long_task_p95: quantile(own, 0.95), attributed_samples: own.length, attribution: own.length ? "observed" : "unknown", load_ms: last.loadMs } };
62
+ },
63
+ dispose() { dead = true; samples.length = 0; last = null; },
64
+ };
65
+ }