@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,248 @@
1
+ /** #1629: app-owned pool; Chat contributes a lease, never another registry/loader. */
2
+ import { createEffectTrialSource } from "./effect-trial-source.js";
3
+ import { createOwnerEffectSource } from "./effect-owner-source.js";
4
+ import { createEffectRuntimeLoader } from "./effect-runtime-loader.js";
5
+ import { createEffectRegistry } from "./effect-registry.js";
6
+ import { createEffectSafety } from "./effect-safety.js";
7
+ import { stockEffectTrust } from "./effect-stock-trust.js";
8
+ import { createEffectProfileCollector } from "./effect-profiles.js";
9
+ import { chatEffectIdentity, createEffectWriter } from "./effect-chat-runtime.js";
10
+
11
+ const episodes = new WeakSet();
12
+
13
+ /** `current` belongs to the page/session episode. Route cleanup only releases its contribution. */
14
+ export function createAppEffectRuntime({ window: host, current, observe, onChange = () => {}, onDispose = () => {}, resolveOwnerTarget = null, onOwnerUnavailable = () => {}, factories = {} }) {
15
+ let dead = false, error = null, loader = null, registry = null, writer = null, source = null, personal = null, trial = null, pending = null, lastMetadata = null, serial = 0, revision = 0, stamp = null, syncing = false, queued = false;
16
+ const bindings = new Map(), profiles = new Map();
17
+ const identity = chatEffectIdentity(host), auth = host?.AgendAuth;
18
+ const safe = fn => { try { return fn(); } catch { return null; } };
19
+ const alive = () => { if (dead) return false; const value = safe(() => current() === true && !auth.sessionEnded()); return !dead && value === true; };
20
+ const sourceAlive = s => {
21
+ if (!s || !alive() || s !== source || s.dead) return false;
22
+ const value = safe(() => s.current() === true && s.element.isConnected === true);
23
+ return !dead && s === source && !s.dead && value === true;
24
+ };
25
+ const placementIdentity = p => JSON.stringify([p.by, p.manifest.layer, p.params]);
26
+ const clock = () => host.performance.now();
27
+ let offAuth = null;
28
+ const snapshot = s => ({ error: error ?? s?.error ?? null, rows: registry?.snapshot().filter(row => s?.bindings.get(row.id)?.key === row.key) ?? [], effects: s?.metadata?.effects ?? [], loading: !!s && !s.dead && s.metadata === null && !s.error });
29
+ function tell() {
30
+ const s = source, value = snapshot(s); if (dead) return;
31
+ if (sourceAlive(s)) safe(() => s.onChange(value));
32
+ if (!dead) safe(() => onChange(snapshot(source)));
33
+ }
34
+ function discard(s) {
35
+ if (!s || s.dead) return;
36
+ s.dead = true; s.generation++; s.metadata = null;
37
+ if (source === s) source = null;
38
+ safe(() => trial?.stopFor(s));
39
+ for (const b of s.bindings.values()) bindings.delete(b.key); s.bindings.clear();
40
+ if (pending?.source === s) safe(() => pending.controller.abort());
41
+ revision++; stamp = null;
42
+ }
43
+ function dispose() {
44
+ if (dead) return; dead = true; discard(source);
45
+ safe(() => trial?.dispose()); safe(() => pending?.controller.abort()); safe(() => registry?.dispose?.()); safe(() => loader?.dispose?.()); safe(() => personal?.dispose()); safe(() => writer?.dispose()); safe(() => offAuth?.());
46
+ safe(() => host?.removeEventListener?.("pagehide", dispose));
47
+ for (const row of profiles.values()) safe(() => row.collector.dispose()); profiles.clear(); bindings.clear(); safe(() => onDispose({ error }));
48
+ }
49
+ function fail(reason) { if (dead) return; error = reason; dispose(); safe(() => onChange({ error, rows: [], effects: [], loading: false })); }
50
+ const unavailable = { ok: false, sync() {}, changedChat() {}, reply() {}, detachChat() {}, attachChat() { return null; }, attachTrial() { return null; }, stopTrial() {}, trialSnapshot: () => ({ rows: [] }), snapshot: () => snapshot(null), dispose };
51
+ if (!host || typeof host !== "object" || episodes.has(host) || !identity || typeof current !== "function" || typeof observe !== "function" || typeof auth?.onSessionEnd !== "function" || typeof auth?.sessionEnded !== "function") return unavailable;
52
+ episodes.add(host); // Claim before invoking current/auth callbacks: one attempt per Window episode.
53
+ if (!alive()) { dispose(); return unavailable; }
54
+ try {
55
+ offAuth = auth.onSessionEnd(dispose);
56
+ if (!alive()) { dispose(); safe(() => offAuth?.()); return unavailable; }
57
+ host.addEventListener?.("pagehide", dispose);
58
+ } catch { fail("runtime_unavailable"); return unavailable; }
59
+ const make = (key, fn) => factories[key] ?? fn;
60
+ function bindingAlive(b) { if (b?.kind === "trial") return trial?.current(b) === true; if (b?.kind === "owner") return personal?.current(b) === true; return !!b && sourceAlive(b.source) && b.source.generation === b.generation && bindings.get(b.key) === b && b.source.bindings.get(b.id) === b; }
61
+ const bindingFor = (key, id) => { const b = bindings.get(key) ?? trial?.binding(key, id) ?? personal?.binding(key, id); return b?.id === id && bindingAlive(b) ? b : null; };
62
+ try {
63
+ writer = createEffectWriter(host, alive);
64
+ loader = make("loader", createEffectRuntimeLoader)({ window: host, ownerEpoch: identity.epoch, current: alive, onRevoke: () => fail("runtime_unavailable") });
65
+ if (!loader.ok || typeof loader.placementsSettled !== "function" || typeof loader.resourceSettled !== "function") throw Error("runtime_unavailable");
66
+ registry = make("registry", createEffectRegistry)({ window: host, origin: host.document.body.dataset.previewOrigin, boot: host.document.body.dataset.previewBoot,
67
+ load: async (descriptor, signal) => {
68
+ const b = bindingFor(descriptor.key, descriptor.id);
69
+ if (!bindingAlive(b) || descriptor.id !== b.id || signal.aborted) return null;
70
+ if (b.kind === "trial") return trial.load(b, signal);
71
+ if (b.kind === "owner") return personal.load(b, signal);
72
+ const value = await loader.resourceSettled({ id: b.id, instance: b.source.name }, signal);
73
+ if (!bindingAlive(b) || signal.aborted) return null;
74
+ if (!value) { refuseSource(b.source); return null; }
75
+ return value;
76
+ },
77
+ safety: make("safety", createEffectSafety)({ storage: host.localStorage, crypto: host.crypto }),
78
+ stockSnapshot: ({ frameRoot }) => make("stock", stockEffectTrust)(host, { frameRoot, ownerCss: observe().ownerCss }),
79
+ onChange() { try { tell(); emit(); } catch { fail("runtime_unavailable"); } },
80
+ onMount(owner) {
81
+ const b = bindingFor(owner.key, owner.id); if (!bindingAlive(b) || b.id !== owner.id || !owner.current()) return;
82
+ const collector = make("collector", createEffectProfileCollector)({ key: owner.key, id: owner.id, device: owner.device, clock });
83
+ const began = safe(clock);
84
+ if (collector.ok && Number.isFinite(began) && began >= 0 && bindingAlive(b) && owner.current()) profiles.set(owner, { binding: b, collector, began, attempted: null }); else collector.dispose?.();
85
+ },
86
+ onRetire(owner) { profiles.get(owner)?.collector.dispose(); profiles.delete(owner); },
87
+ onProfile(raw, owner) {
88
+ const row = profiles.get(owner); if (!row || !bindingAlive(row.binding) || !owner.current()) return;
89
+ row.collector.add(raw); if (row.binding.kind === "trial") return; // Diagnostic samples stay transient; shared safety still runs in registry.
90
+ const now = clock();
91
+ if (!Number.isFinite(now) || now < row.began || now - row.began < 10000 && raw.verdict !== "paused" || row.attempted !== null && now - row.attempted < 10000) return;
92
+ const report = row.collector.report(); if (!report || !bindingAlive(row.binding) || !owner.current()) return;
93
+ row.attempted = now;
94
+ const query = row.binding.kind === "owner" ? "" : `instance=${encodeURIComponent(row.binding.source.name)}&`;
95
+ void writer.post(`/ui/effects/profile/${owner.id}?${query}ownerEpoch=${identity.epoch}`, report.profile);
96
+ }, clock,
97
+ });
98
+ if (!registry.ok) throw Error("runtime_unavailable");
99
+ trial = make("trialSource", createEffectTrialSource)({ window: host, current: () => {
100
+ if (!alive()) return false; const v = observe(), c = v.context;
101
+ return alive() && v.ownerCss === "off" && c.enabled === true && c.show === true && c.previewReady === true && c.optIn === true
102
+ && c.safe === false && c.publicLink === false && c.reducedMotion === false && c.saveData === false && c.hidden === false;
103
+ }, ownerCurrent: (s, name) => sourceAlive(s) && s.name === name,
104
+ stock: () => registry.frameRoot ? make("stock", stockEffectTrust)(host, { frameRoot: registry.frameRoot, ownerCss: observe().ownerCss }) : null,
105
+ readResource: (spec, signal) => typeof loader.trialResourceSettled === "function" ? loader.trialResourceSettled(spec, signal) : Promise.resolve(null),
106
+ onChange() { revision++; stamp = null; sync(); tell(); },
107
+ });
108
+ if (typeof resolveOwnerTarget === "function") {
109
+ if (typeof loader.ownerSettled !== "function") throw Error("runtime_unavailable");
110
+ personal = createOwnerEffectSource({ current: alive,
111
+ readMetadata: signal => loader.ownerSettled(signal), readResource: (spec, signal) => loader.resourceSettled(spec, signal), resolveTarget: resolveOwnerTarget,
112
+ stock: () => registry.frameRoot ? make("stock", stockEffectTrust)(host, { frameRoot: registry.frameRoot, ownerCss: observe().ownerCss }) : null,
113
+ onChange() { revision++; stamp = null; sync(); tell(); }, onUnavailable: (slot, id) => safe(() => onOwnerUnavailable(slot, id)),
114
+ });
115
+ if (!personal.ok) throw Error("runtime_unavailable");
116
+ }
117
+ } catch { fail("runtime_unavailable"); return unavailable; }
118
+ function descriptors(s) {
119
+ if (!sourceAlive(s) || s.error) return [];
120
+ return (s.metadata?.effects ?? []).filter(p => p.manifest.regions.includes("chat")).map((p, order) => {
121
+ let b = s.bindings.get(p.id); const identity = placementIdentity(p);
122
+ if (!b || b.identity !== identity) {
123
+ if (b) bindings.delete(b.key);
124
+ b = Object.freeze({ key: `chat:${s.serial}:${p.id}`, id: p.id, source: s, generation: s.generation, identity });
125
+ s.bindings.set(p.id, b); bindings.set(b.key, b);
126
+ }
127
+ return { key: b.key, id: p.id, by: p.by, region: "chat", layer: p.manifest.layer, order, paused: s.paused.get(p.id) === identity, visible: true, instance: s.name, params: p.params, element: s.element };
128
+ });
129
+ }
130
+ function observation() {
131
+ const value = observe();
132
+ return { ...value, context: { ...value.context, openInstance: sourceAlive(source) ? source.name : null } };
133
+ }
134
+ function emit() {
135
+ if (!alive() || !registry?.ok) return;
136
+ const s = source, value = observation();
137
+ if (sourceAlive(s)) { const state = safe(() => s.observe().state); if (sourceAlive(s) && ["idle", "working", "needs_you", "stopped"].includes(state)) registry.publish("state", { state }, s.name); }
138
+ if (alive() && ["light", "dark"].includes(value.mode)) registry.publish("theme", { mode: value.mode });
139
+ }
140
+ function refuseSource(s) {
141
+ if (!sourceAlive(s)) return;
142
+ s.error = "resource_unavailable"; s.metadata = null; s.generation++;
143
+ for (const b of s.bindings.values()) bindings.delete(b.key); s.bindings.clear(); revision++; stamp = null; sync(); tell();
144
+ }
145
+ function queueSync() {
146
+ if (queued || dead) return; queued = true;
147
+ Promise.resolve().then(() => { queued = false; if (alive()) sync(); else dispose(); });
148
+ }
149
+ function sync() {
150
+ if (!alive()) { dispose(); return; } if (syncing) { queueSync(); return; }
151
+ syncing = true;
152
+ try {
153
+ const candidate = source;
154
+ if (candidate && !sourceAlive(candidate)) discard(candidate);
155
+ const before = revision, value = observation(), s = source;
156
+ if (!alive() || revision !== before || s !== source) { if (alive()) queueSync(); return; }
157
+ const entries = [...(personal?.descriptors() ?? []), ...(trial?.descriptors() ?? []), ...descriptors(s)];
158
+ if (!alive() || revision !== before || s !== source) { stamp = null; if (alive()) queueSync(); return; }
159
+ const next = JSON.stringify([revision, value.context, entries.map(d => [d.key, d.paused])]);
160
+ if (stamp !== next) {
161
+ if (!alive() || revision !== before || s !== source) { stamp = null; if (alive()) queueSync(); return; }
162
+ stamp = next; registry.update(entries, value.context);
163
+ }
164
+ if (!alive() || revision !== before || s !== source) { stamp = null; if (alive()) queueSync(); return; }
165
+ emit();
166
+ const c = value.context;
167
+ if (!pending && c.enabled && c.show && c.previewReady && c.optIn && !c.safe && !c.publicLink && !c.reducedMotion && !c.saveData && !c.hidden) {
168
+ const ownerNeeds = personal?.needsMetadata() === true, chatNeeds = sourceAlive(s) && !s.error && !s.metadata;
169
+ if (!alive() || revision !== before || s !== source) { if (alive()) queueSync(); return; }
170
+ if (ownerNeeds && (!chatNeeds || lastMetadata !== "owner")) void readPersonal();
171
+ else if (chatNeeds) void read(s);
172
+ }
173
+ } catch { fail("runtime_unavailable"); }
174
+ finally { syncing = false; }
175
+ }
176
+ async function readPersonal() {
177
+ if (!personal || pending || !personal.needsMetadata() || !alive()) return;
178
+ const op = { source: personal, controller: new host.AbortController() }; pending = op; lastMetadata = "owner";
179
+ try { await personal.read(op.controller.signal); }
180
+ catch { fail("runtime_unavailable"); }
181
+ finally { if (pending === op) { pending = null; if (alive()) sync(); } }
182
+ }
183
+ function changedOwner() {
184
+ if (!personal || !alive()) return;
185
+ if (pending?.source === personal) safe(() => pending.controller.abort());
186
+ personal.invalidate();
187
+ }
188
+ function ownerSnapshot() {
189
+ return { ...(personal?.snapshot() ?? { error: null, rev: -1, loading: false, slots: {} }),
190
+ rows: registry?.snapshot().flatMap(row => { const b = personal?.binding(row.key, row.id); return b ? [{ ...row, slot: b.slot }] : []; }) ?? [] };
191
+ }
192
+ async function cursorImageSettled(selection, signal) {
193
+ const own = personal?.cursorSelection();
194
+ if (!alive() || !own || !personal.cursorCurrent(selection) || signal?.aborted) return null;
195
+ let value; try { value = await loader.cursorImageSettled(own.id, signal); } catch { value = null; }
196
+ if (!alive() || !personal.cursorCurrent(own) || !personal.cursorCurrent(selection) || signal?.aborted) return null;
197
+ if (!value) { personal.refuseCursor(own); return null; }
198
+ return value;
199
+ }
200
+ async function read(s) {
201
+ if (!sourceAlive(s) || pending || s.error) return;
202
+ const op = { source: s, generation: s.generation, controller: new host.AbortController() }; pending = op; lastMetadata = "chat";
203
+ try {
204
+ const answer = await loader.placementsSettled(s.name, op.controller.signal);
205
+ if (!sourceAlive(s) || op.generation !== s.generation || pending !== op || op.controller.signal.aborted) return;
206
+ if (!answer) { refuseSource(s); return; }
207
+ s.metadata = answer;
208
+ for (const [id, identity] of s.paused) if (!answer.effects.some(p => p.id === id && placementIdentity(p) === identity)) s.paused.delete(id);
209
+ revision++; stamp = null; sync(); tell();
210
+ } catch { if (sourceAlive(s) && op.generation === s.generation) refuseSource(s); }
211
+ finally { if (pending === op) { pending = null; if (alive()) sync(); } }
212
+ }
213
+ function changedSource(s) {
214
+ if (!sourceAlive(s) || s.error) return;
215
+ s.generation++; s.metadata = null;
216
+ for (const b of s.bindings.values()) bindings.delete(b.key); s.bindings.clear();
217
+ if (pending?.source === s) safe(() => pending.controller.abort()); revision++; stamp = null; sync(); tell();
218
+ }
219
+ function publishReply(s, event) { if (sourceAlive(s) && !s.error && event.instance === s.name) registry.publish("reply", {}, s.name); }
220
+ function attachChat(options) {
221
+ discard(source); const expected = revision; sync();
222
+ if (!alive() || revision !== expected || source !== null) return null;
223
+ const { name, element, current: ownCurrent, observe: ownObserve, onChange: ownChange = () => {} } = options ?? {};
224
+ if (!alive() || typeof name !== "string" || name.length < 1 || name.length > 128 || !element || element.isConnected !== true || typeof ownCurrent !== "function" || typeof ownObserve !== "function" || safe(() => ownCurrent() === true) !== true || revision !== expected || source !== null) return null;
225
+ const s = { name, element, current: ownCurrent, observe: ownObserve, onChange: ownChange, serial: ++serial, generation: 0, dead: false, error: null, metadata: null, bindings: new Map(), paused: new Map() };
226
+ source = s; revision++; stamp = null;
227
+ const placement = id => sourceAlive(s) && !s.error ? s.metadata?.effects.find(p => p.id === id) : null;
228
+ const changed = () => changedSource(s);
229
+ const api = { ok: true, sync,
230
+ changed,
231
+ reply(event) { publishReply(s, event); },
232
+ pause(id) { const p = placement(id); if (!p) return false; s.paused.set(id, placementIdentity(p)); revision++; sync(); return true; },
233
+ resume(id) { const p = placement(id); if (!p) return false; s.paused.delete(id); revision++; sync(); const b = s.bindings.get(id); return bindingAlive(b) && registry.resume(b.key); },
234
+ async remove(id) { const p = placement(id); if (!p) return false; const generation = s.generation; const ok = await writer.post("/ui/effects/remove", { instance: s.name, id }); if (!sourceAlive(s) || generation !== s.generation) return false; if (ok) changed(); return ok; },
235
+ snapshot: () => sourceAlive(s) ? snapshot(s) : { error: null, rows: [], effects: [], loading: false },
236
+ dispose() { if (source !== s) return; discard(s); sync(); },
237
+ };
238
+ sync(); tell(); return sourceAlive(s) ? api : null;
239
+ }
240
+ return { ok: true, sync, attachChat, changedOwner, ownerSnapshot,
241
+ attachTrial(options) { const s = source; return sourceAlive(s) ? trial.attach(options, s) : null; },
242
+ stopTrial() { trial?.stop(); },
243
+ trialSnapshot() { return { rows: registry?.snapshot().filter(row => trial?.binding(row.key, row.id)) ?? [] }; },
244
+ cursorSelection: () => personal?.cursorSelection() ?? null, cursorImageSettled,
245
+ pauseOwner(slot) { return personal?.pause(slot) === true; },
246
+ resumeOwner(slot) { if (!personal?.unpause(slot)) return false; const b = personal.slotBinding(slot); return bindingAlive(b) && registry.resume(b.key); },
247
+ changedChat() { changedSource(source); }, reply(event) { publishReply(source, event); }, detachChat() { discard(source); sync(); }, snapshot: () => snapshot(source), dispose };
248
+ }
@@ -0,0 +1,64 @@
1
+ /** #1629: host cadence before ANY effect frame; never learns a baseline from an active effect. */
2
+ const finite = n => typeof n === "number" && Number.isFinite(n);
3
+ export function captureEffectBaseline({ window: host, empty, clock = () => host.performance.now() } = {}) {
4
+ let finish, settled = false, raf = null, timer = null, last = null, first = null, count = 0;
5
+ const promise = new Promise(resolve => { finish = resolve; });
6
+ const end = value => {
7
+ if (settled) return;
8
+ settled = true;
9
+ if (raf !== null) host.cancelAnimationFrame(raf);
10
+ if (timer !== null) host.clearTimeout(timer);
11
+ host.document.removeEventListener("visibilitychange", visibility);
12
+ finish(value);
13
+ };
14
+ const fail = reason => end({ ok: false, reason });
15
+ const gates = () => {
16
+ try { return host.document.visibilityState === "visible" && empty() === true; } catch { return false; }
17
+ };
18
+ const time = () => {
19
+ let now; try { now = clock(); } catch { return null; }
20
+ if (!finite(now) || now < 0 || last !== null && now < last) return null;
21
+ last = now; return now;
22
+ };
23
+ const visibility = () => { if (!gates()) fail("baseline_unavailable"); };
24
+ const step = () => {
25
+ if (settled) return;
26
+ raf = null;
27
+ if (!gates()) { fail("baseline_unavailable"); return; }
28
+ const now = time();
29
+ if (now === null) { fail("clock_unavailable"); return; }
30
+ if (now - began >= 10000) { fail("baseline_timeout"); return; }
31
+ if (first === null) { first = now; count = 1; }
32
+ else if (now > sample) count++;
33
+ sample = now;
34
+ if (now - first >= 2000) {
35
+ const fps = (count - 1) * 1000 / (now - first);
36
+ if (!finite(fps) || fps <= 0 || fps > 1000 || !gates()) fail("baseline_unavailable");
37
+ else end({ ok: true, baselineFps: fps });
38
+ return;
39
+ }
40
+ raf = host.requestAnimationFrame(step);
41
+ };
42
+ let began = null, sample = null;
43
+ if (!host || !host.document || typeof empty !== "function" || typeof clock !== "function" || ["requestAnimationFrame", "cancelAnimationFrame", "setTimeout", "clearTimeout"].some(k => typeof host[k] !== "function") || typeof host.document.addEventListener !== "function" || typeof host.document.removeEventListener !== "function") {
44
+ settled = true; finish({ ok: false, reason: "baseline_unsupported" });
45
+ return { promise, cancel() {} };
46
+ }
47
+ began = time();
48
+ if (began === null) fail("clock_unavailable");
49
+ else if (!gates()) fail("baseline_unavailable");
50
+ else {
51
+ host.document.addEventListener("visibilitychange", visibility);
52
+ const deadline = () => {
53
+ if (settled) return;
54
+ if (!gates()) { fail("baseline_unavailable"); return; }
55
+ const now = time();
56
+ if (now === null) fail("clock_unavailable");
57
+ else if (now - began >= 10000) fail("baseline_timeout");
58
+ else timer = host.setTimeout(deadline, Math.max(1, 10000 - (now - began)));
59
+ };
60
+ timer = host.setTimeout(deadline, 10000);
61
+ raf = host.requestAnimationFrame(step);
62
+ }
63
+ return { promise, cancel() { fail("baseline_cancelled"); } };
64
+ }
@@ -0,0 +1,154 @@
1
+ /** #1629: one committed local Chat lease joins the reviewed host-only effect primitives. */
2
+ import { createEffectRuntimeLoader } from "./effect-runtime-loader.js";
3
+ import { createEffectRegistry } from "./effect-registry.js";
4
+ import { createEffectSafety } from "./effect-safety.js";
5
+ import { stockEffectTrust } from "./effect-stock-trust.js";
6
+ import { createEffectProfileCollector } from "./effect-profiles.js";
7
+
8
+ const writers = new WeakMap();
9
+ /** Physical write reservations outlive a canceled caller. Responses never supply runtime authority. */
10
+ export function createEffectWriter(host, current) {
11
+ let dead = false;
12
+ const own = new Set();
13
+ if (!writers.has(host)) writers.set(host, new Set());
14
+ const all = writers.get(host);
15
+ let previous = null;
16
+ function stop() { if (dead) return; dead = true; for (const op of own) op.cancel(); }
17
+ function time() {
18
+ let now; try { now = host.performance.now(); } catch { stop(); return null; }
19
+ if (!Number.isFinite(now) || now < 0 || previous !== null && now < previous) { stop(); return null; }
20
+ previous = now; return now;
21
+ }
22
+ return {
23
+ async post(path, body) {
24
+ if (dead || !current() || all.size >= 3) return false;
25
+ const began = time(); if (began === null) return false;
26
+ const text = JSON.stringify(body); if (new TextEncoder().encode(text).length > 16384) return false;
27
+ const controller = new host.AbortController(), op = { controller }; own.add(op); all.add(op);
28
+ let timer, cancel;
29
+ const aborted = new Promise(resolve => { cancel = () => { controller.abort(); resolve(false); }; }); op.cancel = cancel;
30
+ const work = Promise.resolve().then(async () => {
31
+ if (dead || !current() || controller.signal.aborted) return false;
32
+ const r = await host.fetch(path, { method: "POST", credentials: "same-origin", cache: "no-store", redirect: "error", headers: { "Content-Type": "application/json" }, body: text, signal: controller.signal });
33
+ // Settle the body before returning the physical reservation, even if the caller already timed out.
34
+ try { await r.body?.cancel(); } catch { /* no data is consumed */ }
35
+ const now = time();
36
+ return !dead && current() && !controller.signal.aborted && Number.isFinite(began) && began >= 0 && now !== null && now >= began && now - began < 10000 && r.ok && !r.redirected;
37
+ }).catch(() => false).finally(() => { host.clearTimeout(timer); controller.abort(); own.delete(op); all.delete(op); });
38
+ timer = host.setTimeout(cancel, 10000);
39
+ return Promise.race([work, aborted]);
40
+ },
41
+ dispose: stop,
42
+ };
43
+ }
44
+
45
+ /** Frozen, server-rendered session identity only. A header-token/public/View load has no identity. */
46
+ export function chatEffectIdentity(host) {
47
+ try {
48
+ const b = host.document.body.dataset, x = JSON.parse(host.document.documentElement.getAttribute("data-prefs") || "null");
49
+ if (b.mode !== "full" || b.publicLink === "1" || b.webTransport === "poll" || !["person", "operator"].includes(x?.ownerScope) || x.unreadable === true || typeof x.ownerEpoch !== "string" || !/^[a-f0-9]{16}$/.test(x.ownerEpoch)) return null;
50
+ return Object.freeze({ epoch: x.ownerEpoch, scope: x.ownerScope });
51
+ } catch { return null; }
52
+ }
53
+ export function chatEffectState(state, name) {
54
+ const inst = state.instances?.find(i => i.name === name);
55
+ if (!inst) return null;
56
+ if (["stopped", "paused"].includes(inst.status)) return "stopped";
57
+ if (Object.hasOwn(state.awaiting ?? {}, name)) return "needs_you";
58
+ return ["working", "stuck"].includes(state.exec?.[name]) ? "working" : state.exec?.[name] === "idle" ? "idle" : null;
59
+ }
60
+
61
+ /** `observe` supplies closed host gates, never event/message content. No timers poll server state. */
62
+ export function createChatEffectRuntime({ window: host, name, element, current, observe, onChange = () => {}, factories = {} }) {
63
+ let dead = false, error = null, loader = null, registry = null, writer = null, active = null, queued = false, generation = 0, metadata = null, ctx = null, lastContext = null;
64
+ const paused = new Map(), owners = new Map();
65
+ const placementIdentity = p => JSON.stringify([p.by, p.manifest.layer, p.params]);
66
+ const identity = chatEffectIdentity(host), auth = host.AgendAuth;
67
+ const alive = () => !dead && current() === true && element.isConnected === true && !auth.sessionEnded();
68
+ const tell = () => { if (!dead && current()) onChange({ error, rows: registry?.snapshot() ?? [], effects: metadata?.effects ?? [], loading: metadata === null && !error }); };
69
+ const fail = reason => { if (dead) return; error = reason; generation++; metadata = null; active?.controller.abort(); registry?.dispose?.(); registry = null; loader?.dispose?.(); loader = null; writer?.dispose(); tell(); };
70
+ function dispose() {
71
+ if (dead) return; dead = true; generation++; active?.controller.abort(); registry?.dispose?.(); loader?.dispose?.(); writer?.dispose(); offAuth?.();
72
+ for (const value of owners.values()) value.collector.dispose(); owners.clear();
73
+ }
74
+ let offAuth = null;
75
+ if (!identity || typeof auth?.onSessionEnd !== "function" || typeof auth?.sessionEnded !== "function" || typeof name !== "string" || name.length < 1 || name.length > 128 || !current()) return { ok: false, dispose };
76
+ offAuth = auth.onSessionEnd(dispose); // May synchronously replay an already-ended episode.
77
+ if (dead || !alive()) { dispose(); return { ok: false, dispose }; }
78
+ const clock = () => host.performance.now();
79
+ try {
80
+ const make = (key, fn) => factories[key] ?? fn;
81
+ writer = createEffectWriter(host, alive);
82
+ loader = make("loader", createEffectRuntimeLoader)({ window: host, ownerEpoch: identity.epoch, current: alive });
83
+ if (!loader.ok) throw Error("runtime_unavailable");
84
+ registry = make("registry", createEffectRegistry)({ window: host, origin: host.document.body.dataset.previewOrigin, boot: host.document.body.dataset.previewBoot,
85
+ load: async (d, signal) => {
86
+ const mine = generation, value = await loader.resource({ id: d.id, instance: name }, signal);
87
+ if (!alive() || mine !== generation || signal.aborted) return null;
88
+ if (!value) { fail("resource_unavailable"); return null; }
89
+ return value;
90
+ },
91
+ safety: make("safety", createEffectSafety)({ storage: host.localStorage, crypto: host.crypto }),
92
+ stockSnapshot: ({ frameRoot }) => make("stock", stockEffectTrust)(host, { frameRoot, ownerCss: observe().ownerCss }),
93
+ onChange() { tell(); emit(); },
94
+ onMount(owner) {
95
+ if (!alive() || !owner.current()) return;
96
+ const collector = make("collector", createEffectProfileCollector)({ key: owner.key, id: owner.id, device: owner.device, clock });
97
+ if (collector.ok) owners.set(owner, { collector, began: clock(), attempted: null });
98
+ },
99
+ onRetire(owner) { const row = owners.get(owner); row?.collector.dispose(); owners.delete(owner); },
100
+ onProfile(raw, owner) {
101
+ const row = owners.get(owner); if (!row || !alive() || !owner.current()) return;
102
+ row.collector.add(raw);
103
+ const now = clock();
104
+ if (now - row.began < 10000 && raw.verdict !== "paused" || row.attempted !== null && now - row.attempted < 10000) return;
105
+ const report = row.collector.report(); if (!report || !owner.current()) return;
106
+ row.attempted = now;
107
+ void writer.post(`/ui/effects/profile/${owner.id}?instance=${encodeURIComponent(name)}&ownerEpoch=${identity.epoch}`, report.profile);
108
+ }, clock,
109
+ });
110
+ if (!registry.ok) throw Error("runtime_unavailable");
111
+ } catch { fail("runtime_unavailable"); }
112
+ function descriptors() {
113
+ return (metadata?.effects ?? []).filter(p => p.manifest.regions.includes("chat")).map((p, order) => ({ key: `chat:${p.id}`, id: p.id, by: p.by, region: "chat", layer: p.manifest.layer,
114
+ order, paused: paused.get(p.id) === placementIdentity(p), visible: true, instance: name, params: p.params, element }));
115
+ }
116
+ function emit() {
117
+ if (!alive() || error || !registry?.ok) return;
118
+ const value = observe();
119
+ if (["idle", "working", "needs_you", "stopped"].includes(value.state)) registry.publish("state", { state: value.state }, name);
120
+ if (["light", "dark"].includes(value.mode)) registry.publish("theme", { mode: value.mode });
121
+ }
122
+ function sync() {
123
+ if (!alive()) { dispose(); return; } if (error) return;
124
+ const value = observe(); ctx = value.context;
125
+ const stamp = JSON.stringify(ctx);
126
+ if (stamp !== lastContext) { lastContext = stamp; registry.update(descriptors(), ctx); }
127
+ emit();
128
+ if (!metadata && !active && ctx.enabled && ctx.show && ctx.previewReady && ctx.optIn && !ctx.safe && !ctx.publicLink && !ctx.reducedMotion && !ctx.saveData && !ctx.hidden) void read();
129
+ }
130
+ async function read() {
131
+ if (!alive() || error || active) return;
132
+ const op = { controller: new host.AbortController(), generation }; active = op;
133
+ try {
134
+ const answer = await loader.placements(name, op.controller.signal);
135
+ if (!alive() || active !== op) return;
136
+ if (op.generation !== generation) return;
137
+ if (!answer) { fail("resource_unavailable"); return; }
138
+ metadata = answer;
139
+ for (const [id, stamp] of paused) if (!answer.effects.some(p => p.id === id && placementIdentity(p) === stamp)) paused.delete(id);
140
+ lastContext = null; sync(); tell();
141
+ } finally {
142
+ if (active === op) active = null;
143
+ if (queued && alive() && !error) { queued = false; sync(); }
144
+ }
145
+ }
146
+ return { ok: !error && !dead, sync,
147
+ changed() { if (!alive() || error) return; generation++; metadata = null; registry.update([], observe().context); lastContext = null; if (active) queued = true; else sync(); tell(); },
148
+ reply(event) { if (alive() && !error && event.instance === name) registry.publish("reply", {}, name); },
149
+ pause(id) { if (!alive() || !metadata?.effects.some(p => p.id === id)) return false; paused.set(id, placementIdentity(metadata.effects.find(p => p.id === id))); lastContext = null; sync(); return true; },
150
+ resume(id) { if (!alive() || error || !metadata?.effects.some(p => p.id === id)) return false; paused.delete(id); lastContext = null; sync(); return registry.resume(`chat:${id}`); },
151
+ async remove(id) { if (!alive() || error || !metadata?.effects.some(p => p.id === id)) return false; const mine = generation; const ok = await writer.post("/ui/effects/remove", { instance: name, id }); if (!alive() || mine !== generation) return false; if (ok) this.changed(); return ok; },
152
+ snapshot: () => ({ error, rows: registry?.snapshot() ?? [], effects: metadata?.effects ?? [], loading: metadata === null && !error }), dispose,
153
+ };
154
+ }
@@ -0,0 +1,125 @@
1
+ /** #1629: host-owned static cursor controller; no product startup, metadata reader, Worker or frame. */
2
+ import { effectHostStyle } from "./effect-host-style.js";
3
+ import { checkStaticEffectCursor } from "./effect-runtime-loader.js";
4
+ const owners = new WeakMap(), ATTRIBUTE = "data-agend-effect-cursor";
5
+ let serial = 0;
6
+ const finite = n => typeof n === "number" && Number.isFinite(n) && n >= 0;
7
+ function selection(value) {
8
+ try {
9
+ if (!value || ![Object.prototype, null].includes(Object.getPrototypeOf(value))) return null;
10
+ const fields = Object.getOwnPropertyDescriptors(value);
11
+ if (Reflect.ownKeys(fields).length !== 3 || !["id", "generation", "hotspot"].every(k => fields[k] && Object.hasOwn(fields[k], "value"))) return null;
12
+ const { id, generation, hotspot } = value;
13
+ if (typeof id !== "string" || !/^[a-f0-9]{32}$/.test(id) || !Number.isSafeInteger(generation) || generation < 0 || !Array.isArray(hotspot) || Object.getPrototypeOf(hotspot) !== Array.prototype) return null;
14
+ const entries = Object.getOwnPropertyDescriptors(hotspot);
15
+ if (Reflect.ownKeys(entries).length !== 3 || entries.length?.value !== 2 || ![0, 1].every(k => entries[k] && Object.hasOwn(entries[k], "value") && Number.isInteger(entries[k].value) && entries[k].value >= 0 && entries[k].value <= 63)) return null;
16
+ return Object.freeze({ id, generation, hotspot: Object.freeze([entries[0].value, entries[1].value]) });
17
+ } catch { return null; }
18
+ }
19
+ function cursorMatches(value, url, hotspot) {
20
+ const m = typeof value === "string" && /^url\((["']?)(blob:[^"'()\s]+)\1\)\s+(\d+)\s+(\d+),\s*auto$/.exec(value);
21
+ return !!m && m[2] === url && Number(m[3]) === hotspot[0] && Number(m[4]) === hotspot[1];
22
+ }
23
+ /** readImage must be the existing shared loader's settled cursor operation. enabled is a host gate, never effect data. */
24
+ export function createEffectCursor({ window: host, current, readImage, onChange = () => {} } = {}) {
25
+ const unavailable = { ok: false, update() {}, snapshot: () => ({ status: "unavailable", id: null }), dispose() {} };
26
+ if (!host?.document || owners.has(host) || ![current, readImage, onChange, host.getComputedStyle, host.Blob, host.URL?.createObjectURL, host.URL?.revokeObjectURL, host.AbortController, host.setTimeout, host.clearTimeout, host.performance?.now].every(x => typeof x === "function")) return unavailable;
27
+ const token = {}; owners.set(host, token);
28
+ const doc = host.document; let dead = false, previous = null, wanted = null, pending = null, active = null, blocked = null, generation = 0, checking = false, notifying = false, status = "off";
29
+ const safe = fn => { try { return fn(); } catch { return null; } };
30
+ const release = () => { if (dead && !pending && owners.get(host) === token) owners.delete(host); };
31
+ function alive() {
32
+ if (dead || checking) return false;
33
+ checking = true; const value = safe(() => current() === true && host.document === doc); checking = false;
34
+ return !dead && value === true;
35
+ }
36
+ function now() {
37
+ const value = safe(() => host.performance.now());
38
+ if (!finite(value) || previous !== null && value < previous) { dispose(); return null; }
39
+ previous = value; return value;
40
+ }
41
+ function target() {
42
+ const all = doc.querySelectorAll("#app > .shell"), el = all.length === 1 ? all[0] : null;
43
+ return el?.ownerDocument === doc && el.isConnected === true ? el : null;
44
+ }
45
+ function remove(row) {
46
+ if (!row) return;
47
+ // Removing our exact attribute disarms even a stylesheet whose CSSOM has become unreadable.
48
+ safe(() => { if (row.element.getAttribute(ATTRIBUTE) === row.token) row.element.removeAttribute(ATTRIBUTE); });
49
+ safe(() => row.rule?.style.removeProperty("cursor"));
50
+ safe(() => { const at = [...row.sheet.cssRules].indexOf(row.rule); if (at >= 0) row.sheet.deleteRule(at); });
51
+ if (row.url) { safe(() => host.URL.revokeObjectURL(row.url)); row.url = null; }
52
+ }
53
+ function clear() { const old = active; active = null; remove(old); }
54
+ function tell() { if (dead || notifying) return; notifying = true; try { safe(() => onChange(snapshot())); } finally { notifying = false; } }
55
+ function snapshot() { return Object.freeze({ status: dead ? "unavailable" : status, id: wanted?.selection.id ?? null }); }
56
+ function dispose() {
57
+ if (dead) return; dead = true; generation++; wanted = null; clear();
58
+ if (pending) { safe(() => pending.controller.abort()); remove(pending); }
59
+ safe(() => host.removeEventListener?.("pagehide", dispose)); release();
60
+ }
61
+ if (!alive() || now() === null || !effectHostStyle(host)) { dispose(); return unavailable; }
62
+ safe(() => host.addEventListener?.("pagehide", dispose));
63
+ function valid(op) {
64
+ if (!alive() || pending !== op || op.generation !== generation || op.controller.signal.aborted || wanted !== op.wanted) return false;
65
+ const t = now();
66
+ return t !== null && t >= op.began && t - op.began < 10000 && target() === op.element && effectHostStyle(host) === op.sheet;
67
+ }
68
+ function fail(op) {
69
+ if (!dead && pending === op && generation === op.generation && wanted === op.wanted) { blocked = wanted; status = "unavailable"; tell(); }
70
+ }
71
+ async function load(op) {
72
+ try {
73
+ if (!valid(op)) return;
74
+ const bytes = await readImage(op.wanted.selection, op.controller.signal);
75
+ if (!valid(op)) return;
76
+ const checked = checkStaticEffectCursor(bytes); if (!checked) { fail(op); return; }
77
+ const view = new DataView(checked.buffer, checked.byteOffset, checked.byteLength), width = view.getUint32(16), height = view.getUint32(20);
78
+ if (op.wanted.selection.hotspot.some((v, n) => v >= (n === 0 ? width : height))) { fail(op); return; }
79
+ op.url = host.URL.createObjectURL(new host.Blob([checked], { type: "image/png" }));
80
+ const url = new URL(op.url); if (url.protocol !== "blob:" || url.origin !== host.location.origin || !/^blob:[^"'()\s\\]+$/.test(op.url)) { fail(op); return; }
81
+ const image = doc.createElement("img"); image.alt = ""; image.src = op.url; op.image = image;
82
+ if (typeof image.decode !== "function") { fail(op); return; }
83
+ await image.decode();
84
+ if (!valid(op)) return;
85
+ if (image.naturalWidth !== width || image.naturalHeight !== height) { fail(op); return; }
86
+ if (op.element.hasAttribute(ATTRIBUTE)) { fail(op); return; }
87
+ op.token = `agend-effect-cursor-${++serial}`;
88
+ const index = op.sheet.insertRule(`#app > .shell[${ATTRIBUTE}="${op.token}"]{}`, op.sheet.cssRules.length); op.rule = op.sheet.cssRules[index];
89
+ const hotspot = op.wanted.selection.hotspot, value = `url("${op.url}") ${hotspot[0]} ${hotspot[1]}, auto`;
90
+ op.rule.style.setProperty("cursor", value); op.element.setAttribute(ATTRIBUTE, op.token);
91
+ if (!valid(op) || !cursorMatches(op.rule.style.getPropertyValue("cursor"), op.url, hotspot) || !cursorMatches(host.getComputedStyle(op.element).cursor, op.url, hotspot)) { fail(op); return; }
92
+ active = op; status = "running"; tell();
93
+ } catch { fail(op); }
94
+ finally {
95
+ safe(() => host.clearTimeout(op.timer));
96
+ if (active !== op) remove(op);
97
+ if (pending === op) { pending = null; if (!dead && wanted === op.wanted && !active && blocked !== wanted) { blocked = wanted; status = "unavailable"; tell(); } }
98
+ release(); if (!dead) start();
99
+ }
100
+ }
101
+ function start() {
102
+ if (!alive() || pending || active || !wanted || blocked === wanted) return;
103
+ const began = now(), element = safe(target), sheet = effectHostStyle(host);
104
+ if (began === null || !element || !sheet || element.hasAttribute(ATTRIBUTE)) { blocked = wanted; status = "unavailable"; tell(); return; }
105
+ const op = { wanted, generation, began, element, sheet, controller: new host.AbortController(), url: null, rule: null, token: null, timer: null }; pending = op; status = "loading";
106
+ op.timer = host.setTimeout(() => { if (pending !== op) return; op.controller.abort(); remove(op); fail(op); }, 10000);
107
+ tell(); void load(op);
108
+ }
109
+ function update(value, enabled) {
110
+ if (!alive() || now() === null) { dispose(); return; }
111
+ const next = enabled === true ? selection(value) : null;
112
+ const same = !!next && wanted && next.id === wanted.selection.id && next.generation === wanted.selection.generation && next.hotspot.every((n, i) => n === wanted.selection.hotspot[i]);
113
+ if (!same) {
114
+ generation++; wanted = next ? { selection: next } : null; blocked = null; clear();
115
+ if (pending) { safe(() => pending.controller.abort()); remove(pending); }
116
+ status = next ? "loading" : "off";
117
+ }
118
+ if (active) {
119
+ const row = active;
120
+ if (safe(target) !== row.element || effectHostStyle(host) !== row.sheet || row.element.getAttribute(ATTRIBUTE) !== row.token || !cursorMatches(safe(() => host.getComputedStyle(row.element).cursor), row.url, row.wanted.selection.hotspot)) { clear(); blocked = wanted; status = "unavailable"; }
121
+ }
122
+ start(); tell();
123
+ }
124
+ return { ok: true, update, snapshot, dispose };
125
+ }