@songsid/agend 2.3.0-beta.2 → 2.3.0-beta.20

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 (502) hide show
  1. package/README.md +7 -1
  2. package/README.zh-TW.md +7 -1
  3. package/dist/agent-cli-instructions.md +9 -2
  4. package/dist/agent-cli.js +63 -2
  5. package/dist/agent-cli.js.map +1 -1
  6. package/dist/agent-endpoint.js +4 -0
  7. package/dist/agent-endpoint.js.map +1 -1
  8. package/dist/agent-file-read.d.ts +4 -1
  9. package/dist/agent-file-read.js +189 -16
  10. package/dist/agent-file-read.js.map +1 -1
  11. package/dist/agent-whats-new.d.ts +11 -0
  12. package/dist/agent-whats-new.js +29 -5
  13. package/dist/agent-whats-new.js.map +1 -1
  14. package/dist/auth-api.d.ts +30 -1
  15. package/dist/auth-api.js +45 -13
  16. package/dist/auth-api.js.map +1 -1
  17. package/dist/authority/authority-state.d.ts +38 -0
  18. package/dist/authority/authority-state.js +114 -0
  19. package/dist/authority/authority-state.js.map +1 -0
  20. package/dist/authority/session-authorizations.d.ts +43 -0
  21. package/dist/authority/session-authorizations.js +102 -0
  22. package/dist/authority/session-authorizations.js.map +1 -0
  23. package/dist/authority/web-authority.d.ts +120 -0
  24. package/dist/authority/web-authority.js +157 -0
  25. package/dist/authority/web-authority.js.map +1 -0
  26. package/dist/backend/claude-code.d.ts +2 -2
  27. package/dist/backend/claude-code.js +18 -8
  28. package/dist/backend/claude-code.js.map +1 -1
  29. package/dist/backend/codex-metadata.d.ts +8 -0
  30. package/dist/backend/codex-metadata.js +74 -17
  31. package/dist/backend/codex-metadata.js.map +1 -1
  32. package/dist/backend/codex.js +15 -2
  33. package/dist/backend/codex.js.map +1 -1
  34. package/dist/backend/credential-profile.d.ts +2 -0
  35. package/dist/backend/credential-profile.js +2 -0
  36. package/dist/backend/credential-profile.js.map +1 -1
  37. package/dist/backend/types.d.ts +6 -0
  38. package/dist/backend/types.js +15 -0
  39. package/dist/backend/types.js.map +1 -1
  40. package/dist/backend-availability.d.ts +120 -0
  41. package/dist/backend-availability.js +396 -0
  42. package/dist/backend-availability.js.map +1 -0
  43. package/dist/backend-command.d.ts +23 -3
  44. package/dist/backend-command.js +25 -4
  45. package/dist/backend-command.js.map +1 -1
  46. package/dist/bug-report-cli.d.ts +30 -0
  47. package/dist/bug-report-cli.js +138 -0
  48. package/dist/bug-report-cli.js.map +1 -0
  49. package/dist/bug-report.d.ts +47 -0
  50. package/dist/bug-report.js +136 -0
  51. package/dist/bug-report.js.map +1 -0
  52. package/dist/bundled-skills.d.ts +14 -0
  53. package/dist/bundled-skills.js +29 -0
  54. package/dist/bundled-skills.js.map +1 -0
  55. package/dist/cache-ledger.d.ts +16 -3
  56. package/dist/cache-ledger.js +33 -13
  57. package/dist/cache-ledger.js.map +1 -1
  58. package/dist/cache-service.d.ts +55 -3
  59. package/dist/cache-service.js +152 -16
  60. package/dist/cache-service.js.map +1 -1
  61. package/dist/channel/adapters/discord-client-retirement.d.ts +5 -0
  62. package/dist/channel/adapters/discord-client-retirement.js +57 -0
  63. package/dist/channel/adapters/discord-client-retirement.js.map +1 -0
  64. package/dist/channel/adapters/discord.d.ts +9 -0
  65. package/dist/channel/adapters/discord.js +49 -4
  66. package/dist/channel/adapters/discord.js.map +1 -1
  67. package/dist/channel/mcp-tools.js +22 -11
  68. package/dist/channel/mcp-tools.js.map +1 -1
  69. package/dist/channel/types.d.ts +6 -0
  70. package/dist/classic-channel-manager.d.ts +10 -5
  71. package/dist/classic-channel-manager.js +12 -9
  72. package/dist/classic-channel-manager.js.map +1 -1
  73. package/dist/cli.js +224 -111
  74. package/dist/cli.js.map +1 -1
  75. package/dist/config-validator.d.ts +6 -0
  76. package/dist/config-validator.js +129 -3
  77. package/dist/config-validator.js.map +1 -1
  78. package/dist/config.d.ts +13 -1
  79. package/dist/config.js +14 -5
  80. package/dist/config.js.map +1 -1
  81. package/dist/cross-instance-notice.d.ts +7 -0
  82. package/dist/cross-instance-notice.js +10 -0
  83. package/dist/cross-instance-notice.js.map +1 -1
  84. package/dist/daemon.d.ts +24 -1
  85. package/dist/daemon.js +151 -19
  86. package/dist/daemon.js.map +1 -1
  87. package/dist/decision-list.d.ts +25 -0
  88. package/dist/decision-list.js +30 -0
  89. package/dist/decision-list.js.map +1 -0
  90. package/dist/delivery-outbox.d.ts +10 -0
  91. package/dist/delivery-outbox.js +17 -1
  92. package/dist/delivery-outbox.js.map +1 -1
  93. package/dist/diagnostic-redaction.d.ts +10 -0
  94. package/dist/diagnostic-redaction.js +167 -0
  95. package/dist/diagnostic-redaction.js.map +1 -0
  96. package/dist/fleet-context.d.ts +10 -0
  97. package/dist/fleet-manager.d.ts +505 -15
  98. package/dist/fleet-manager.js +1870 -204
  99. package/dist/fleet-manager.js.map +1 -1
  100. package/dist/fleet-process-handlers.d.ts +18 -0
  101. package/dist/fleet-process-handlers.js +69 -0
  102. package/dist/fleet-process-handlers.js.map +1 -0
  103. package/dist/fleet-traffic.d.ts +74 -0
  104. package/dist/fleet-traffic.js +196 -0
  105. package/dist/fleet-traffic.js.map +1 -0
  106. package/dist/general-knowledge/skills/bug-report/SKILL.md +31 -0
  107. package/dist/general-knowledge/skills/credential-profiles/SKILL.md +17 -6
  108. package/dist/general-knowledge/skills/cross-instance-messaging/SKILL.md +6 -1
  109. package/dist/general-knowledge/skills/fleet-config/SKILL.md +2 -0
  110. package/dist/general-knowledge/skills/multi-channel/SKILL.md +3 -2
  111. package/dist/general-knowledge/skills/persona-emoji/SKILL.md +1 -1
  112. package/dist/general-knowledge/skills/scheduling/SKILL.md +4 -0
  113. package/dist/general-knowledge/skills/web-appearance/SKILL.md +190 -47
  114. package/dist/general-knowledge/skills/web-dashboard/SKILL.md +545 -0
  115. package/dist/general-knowledge/skills/worker-collaboration/SKILL.md +1 -1
  116. package/dist/human-delivery-receipts.d.ts +24 -1
  117. package/dist/human-delivery-receipts.js +64 -11
  118. package/dist/human-delivery-receipts.js.map +1 -1
  119. package/dist/instance-config-impact.js +7 -1
  120. package/dist/instance-config-impact.js.map +1 -1
  121. package/dist/instance-lifecycle.d.ts +10 -1
  122. package/dist/instance-lifecycle.js +79 -18
  123. package/dist/instance-lifecycle.js.map +1 -1
  124. package/dist/instance-names.d.ts +4 -0
  125. package/dist/instance-names.js +12 -0
  126. package/dist/instance-names.js.map +1 -0
  127. package/dist/instructions.d.ts +12 -1
  128. package/dist/instructions.js +13 -2
  129. package/dist/instructions.js.map +1 -1
  130. package/dist/locale.js +173 -12
  131. package/dist/locale.js.map +1 -1
  132. package/dist/log-steps.d.ts +1 -0
  133. package/dist/log-steps.js +15 -0
  134. package/dist/log-steps.js.map +1 -1
  135. package/dist/login-controller.d.ts +45 -0
  136. package/dist/login-controller.js +101 -5
  137. package/dist/login-controller.js.map +1 -1
  138. package/dist/ls-attach-resolve.d.ts +96 -0
  139. package/dist/ls-attach-resolve.js +180 -0
  140. package/dist/ls-attach-resolve.js.map +1 -0
  141. package/dist/ls-rows.d.ts +12 -0
  142. package/dist/ls-rows.js +82 -3
  143. package/dist/ls-rows.js.map +1 -1
  144. package/dist/memory-hogs.d.ts +27 -0
  145. package/dist/memory-hogs.js +124 -0
  146. package/dist/memory-hogs.js.map +1 -0
  147. package/dist/needs-you.d.ts +10 -0
  148. package/dist/needs-you.js +2 -1
  149. package/dist/needs-you.js.map +1 -1
  150. package/dist/outbound-handlers.d.ts +11 -0
  151. package/dist/outbound-handlers.js +139 -11
  152. package/dist/outbound-handlers.js.map +1 -1
  153. package/dist/outbound-schemas.d.ts +200 -4
  154. package/dist/outbound-schemas.js +124 -11
  155. package/dist/outbound-schemas.js.map +1 -1
  156. package/dist/public-web-gateway.js +17 -4
  157. package/dist/public-web-gateway.js.map +1 -1
  158. package/dist/public-web-link.d.ts +9 -0
  159. package/dist/public-web-link.js +8 -0
  160. package/dist/public-web-link.js.map +1 -1
  161. package/dist/quickstart-api.d.ts +19 -1
  162. package/dist/quickstart-api.js +94 -4
  163. package/dist/quickstart-api.js.map +1 -1
  164. package/dist/reply-buttons-controller.d.ts +4 -0
  165. package/dist/reply-buttons-controller.js +2 -2
  166. package/dist/reply-buttons-controller.js.map +1 -1
  167. package/dist/restart-progress.d.ts +80 -1
  168. package/dist/restart-progress.js +306 -21
  169. package/dist/restart-progress.js.map +1 -1
  170. package/dist/rollout-index.d.ts +5 -0
  171. package/dist/rollout-index.js +26 -5
  172. package/dist/rollout-index.js.map +1 -1
  173. package/dist/scheduler/db.d.ts +40 -13
  174. package/dist/scheduler/db.js +123 -89
  175. package/dist/scheduler/db.js.map +1 -1
  176. package/dist/scheduler/task-board-text.d.ts +26 -0
  177. package/dist/scheduler/task-board-text.js +135 -0
  178. package/dist/scheduler/task-board-text.js.map +1 -0
  179. package/dist/scheduler/task-board.d.ts +650 -0
  180. package/dist/scheduler/task-board.js +1938 -0
  181. package/dist/scheduler/task-board.js.map +1 -0
  182. package/dist/scheduler/types.d.ts +6 -1
  183. package/dist/search/search-docs.d.ts +58 -0
  184. package/dist/search/search-docs.js +63 -0
  185. package/dist/search/search-docs.js.map +1 -0
  186. package/dist/search/search-index.d.ts +141 -0
  187. package/dist/search/search-index.js +440 -0
  188. package/dist/search/search-index.js.map +1 -0
  189. package/dist/search/search-store.d.ts +122 -0
  190. package/dist/search/search-store.js +236 -0
  191. package/dist/search/search-store.js.map +1 -0
  192. package/dist/search/search-worker.d.ts +1 -0
  193. package/dist/search/search-worker.js +163 -0
  194. package/dist/search/search-worker.js.map +1 -0
  195. package/dist/service-installer.js +12 -0
  196. package/dist/service-installer.js.map +1 -1
  197. package/dist/settings-api.d.ts +23 -0
  198. package/dist/settings-api.js +164 -3
  199. package/dist/settings-api.js.map +1 -1
  200. package/dist/settings-change.js +104 -0
  201. package/dist/settings-change.js.map +1 -1
  202. package/dist/settings-confirmation.d.ts +3 -1
  203. package/dist/settings-confirmation.js +2 -0
  204. package/dist/settings-confirmation.js.map +1 -1
  205. package/dist/settings-effect.js +34 -3
  206. package/dist/settings-effect.js.map +1 -1
  207. package/dist/settings-http-confirmation.d.ts +11 -0
  208. package/dist/settings-http-confirmation.js +10 -0
  209. package/dist/settings-http-confirmation.js.map +1 -1
  210. package/dist/setup-form.d.ts +1 -1
  211. package/dist/setup-form.js +23 -7
  212. package/dist/setup-form.js.map +1 -1
  213. package/dist/spawn-gate.d.ts +1 -0
  214. package/dist/spawn-gate.js +13 -3
  215. package/dist/spawn-gate.js.map +1 -1
  216. package/dist/task-board-tool.d.ts +25 -0
  217. package/dist/task-board-tool.js +154 -0
  218. package/dist/task-board-tool.js.map +1 -0
  219. package/dist/task-board-waker.d.ts +44 -0
  220. package/dist/task-board-waker.js +128 -0
  221. package/dist/task-board-waker.js.map +1 -0
  222. package/dist/tmux-exec-helper.d.ts +94 -0
  223. package/dist/tmux-exec-helper.js +407 -0
  224. package/dist/tmux-exec-helper.js.map +1 -0
  225. package/dist/tmux-manager.d.ts +5 -0
  226. package/dist/tmux-manager.js +42 -11
  227. package/dist/tmux-manager.js.map +1 -1
  228. package/dist/tool-permissions.d.ts +0 -7
  229. package/dist/tool-permissions.js +44 -5
  230. package/dist/tool-permissions.js.map +1 -1
  231. package/dist/topic-commands.d.ts +17 -1
  232. package/dist/topic-commands.js +45 -19
  233. package/dist/topic-commands.js.map +1 -1
  234. package/dist/tunnel/cloudflared.js +37 -2
  235. package/dist/tunnel/cloudflared.js.map +1 -1
  236. package/dist/tunnel/types.d.ts +3 -1
  237. package/dist/tunnel/types.js.map +1 -1
  238. package/dist/types.d.ts +63 -1
  239. package/dist/ui/app-effects.js +90 -32
  240. package/dist/ui/backend-signin.js +297 -0
  241. package/dist/ui/chat-ambient.js +307 -0
  242. package/dist/ui/chat-effect-posters.js +8 -8
  243. package/dist/ui/chat-effects.js +110 -15
  244. package/dist/ui/chat-msg-actions.js +176 -0
  245. package/dist/ui/chat-render.js +21 -3
  246. package/dist/ui/chat-store.js +64 -12
  247. package/dist/ui/chat-strings.js +85 -8
  248. package/dist/ui/chat-thread.js +204 -21
  249. package/dist/ui/effect-trial-controller.js +13 -8
  250. package/dist/ui/effect-trial-panel.js +19 -16
  251. package/dist/ui/effects-waker.js +70 -0
  252. package/dist/ui/fleet-canvas-layout.js +117 -0
  253. package/dist/ui/fleet-canvas.js +555 -0
  254. package/dist/ui/fleet-org.js +27 -12
  255. package/dist/ui/fleet-route-scene.js +207 -0
  256. package/dist/ui/fleet-route-worker.js +21 -0
  257. package/dist/ui/fleet-route.js +88 -0
  258. package/dist/ui/fleet-task-card.js +436 -0
  259. package/dist/ui/fleet-task-common.js +448 -0
  260. package/dist/ui/fleet-tasks.js +535 -0
  261. package/dist/ui/fleet-traffic.js +359 -0
  262. package/dist/ui/fleet-usage.js +435 -0
  263. package/dist/ui/panel-chat.js +264 -66
  264. package/dist/ui/panel-details.js +2 -1
  265. package/dist/ui/panel-fleet.js +65 -83
  266. package/dist/ui/panel-needs.js +4 -2
  267. package/dist/ui/panel-settings.js +56 -11
  268. package/dist/ui/preview.js +3 -2
  269. package/dist/ui/search-dialog.js +349 -0
  270. package/dist/ui/settings-appearance.js +44 -19
  271. package/dist/ui/settings-apply.js +3 -3
  272. package/dist/ui/settings-catalog.js +92 -0
  273. package/dist/ui/settings-confirm.js +7 -2
  274. package/dist/ui/settings-dialogs.js +40 -12
  275. package/dist/ui/settings-effects.js +4 -3
  276. package/dist/ui/settings-guests.js +117 -0
  277. package/dist/ui/settings-learn.js +25 -0
  278. package/dist/ui/settings-model.js +15 -6
  279. package/dist/ui/settings-mods.js +23 -9
  280. package/dist/ui/settings-owner-effects.js +15 -13
  281. package/dist/ui/settings-strings.js +122 -42
  282. package/dist/ui/settings-web-authority.js +91 -0
  283. package/dist/ui/shared/agend-auth.js +1 -1
  284. package/dist/ui/shared/app-i18n.js +11 -2
  285. package/dist/ui/shared/app-prefs.js +17 -4
  286. package/dist/ui/shared/app-route.js +19 -1
  287. package/dist/ui/shared/app-session.js +2 -0
  288. package/dist/ui/shared/app-shell.js +174 -30
  289. package/dist/ui/shared/app-store.js +26 -1
  290. package/dist/ui/shared/app-stream.js +5 -1
  291. package/dist/ui/shared/app-themes.js +140 -7
  292. package/dist/ui/shared/app.css +704 -20
  293. package/dist/ui/shared/app.js +26 -15
  294. package/dist/ui/shared/card-strings.js +8 -0
  295. package/dist/ui/shared/effect-app-runtime.js +68 -19
  296. package/dist/ui/shared/effect-chat-runtime.js +16 -3
  297. package/dist/ui/shared/effect-frame.js +26 -7
  298. package/dist/ui/shared/effect-geometry.js +24 -9
  299. package/dist/ui/shared/effect-owner-source.js +8 -1
  300. package/dist/ui/shared/effect-policy.js +21 -7
  301. package/dist/ui/shared/effect-profiler.js +31 -9
  302. package/dist/ui/shared/effect-registry.js +97 -50
  303. package/dist/ui/shared/effect-stock-snapshot.js +9 -0
  304. package/dist/ui/shared/effect-stock-trust.js +62 -10
  305. package/dist/ui/shared/effect-trial-shadow.js +62 -0
  306. package/dist/ui/shared/effect-trial-source.js +1 -1
  307. package/dist/ui/shared/effect-trial-stage.js +54 -31
  308. package/dist/ui/shared/effect-trial.css +40 -0
  309. package/dist/ui/shared/fx-motion.js +67 -0
  310. package/dist/ui/shared/identity-card.js +100 -11
  311. package/dist/ui/shared/identity-color.js +14 -10
  312. package/dist/ui/shared/instance-nav.js +179 -23
  313. package/dist/ui/shared/owner-css.js +30 -1
  314. package/dist/ui/shared/panel-view.js +24 -4
  315. package/dist/ui/shared/tokens.css +9 -3
  316. package/dist/ui/shared/ui-icons.js +2 -0
  317. package/dist/ui/shared/ui-menu.js +20 -7
  318. package/dist/ui/shared/ui-toast.js +16 -3
  319. package/dist/ui/shared/usage-dialog.js +34 -3
  320. package/dist/ui/shared/view-strings.js +72 -0
  321. package/dist/ui/shared/web-authorize.js +198 -0
  322. package/dist/ui/web-terminal/terminal.css +4 -4
  323. package/dist/ui/web-terminal/terminal.html +1 -1
  324. package/dist/update-launch.d.ts +9 -0
  325. package/dist/update-launch.js +38 -12
  326. package/dist/update-launch.js.map +1 -1
  327. package/dist/usage/format-rich.js +6 -3
  328. package/dist/usage/format-rich.js.map +1 -1
  329. package/dist/usage/i18n-keys.d.ts +1 -1
  330. package/dist/usage/i18n-keys.js +2 -1
  331. package/dist/usage/i18n-keys.js.map +1 -1
  332. package/dist/usage/i18n.d.ts +13 -1
  333. package/dist/usage/i18n.js +29 -0
  334. package/dist/usage/i18n.js.map +1 -1
  335. package/dist/usage/providers.d.ts +81 -2
  336. package/dist/usage/providers.js +240 -38
  337. package/dist/usage/providers.js.map +1 -1
  338. package/dist/usage/usage-api.d.ts +5 -1
  339. package/dist/usage/usage-api.js +184 -58
  340. package/dist/usage/usage-api.js.map +1 -1
  341. package/dist/usage-report.d.ts +158 -0
  342. package/dist/usage-report.js +226 -0
  343. package/dist/usage-report.js.map +1 -0
  344. package/dist/usage-rollup.d.ts +51 -0
  345. package/dist/usage-rollup.js +168 -0
  346. package/dist/usage-rollup.js.map +1 -0
  347. package/dist/user-systemd.d.ts +24 -0
  348. package/dist/user-systemd.js +75 -0
  349. package/dist/user-systemd.js.map +1 -0
  350. package/dist/view-api.d.ts +39 -0
  351. package/dist/view-api.js +69 -6
  352. package/dist/view-api.js.map +1 -1
  353. package/dist/web-ambient.d.ts +72 -0
  354. package/dist/web-ambient.js +161 -0
  355. package/dist/web-ambient.js.map +1 -0
  356. package/dist/web-api.d.ts +98 -14
  357. package/dist/web-api.js +1027 -83
  358. package/dist/web-api.js.map +1 -1
  359. package/dist/web-appearance-policy.d.ts +4 -6
  360. package/dist/web-appearance-policy.js +5 -12
  361. package/dist/web-appearance-policy.js.map +1 -1
  362. package/dist/web-appearance-tools.d.ts +3 -0
  363. package/dist/web-appearance-tools.js +22 -3
  364. package/dist/web-appearance-tools.js.map +1 -1
  365. package/dist/web-appearance-view.d.ts +27 -2
  366. package/dist/web-appearance-view.js +129 -14
  367. package/dist/web-appearance-view.js.map +1 -1
  368. package/dist/web-appearance.d.ts +17 -3
  369. package/dist/web-appearance.js +85 -8
  370. package/dist/web-appearance.js.map +1 -1
  371. package/dist/web-asset-parts.d.ts +2 -2
  372. package/dist/web-asset-parts.js +3 -3
  373. package/dist/web-asset-parts.js.map +1 -1
  374. package/dist/web-auth.js +3 -1
  375. package/dist/web-auth.js.map +1 -1
  376. package/dist/web-backend-login.d.ts +130 -0
  377. package/dist/web-backend-login.js +225 -0
  378. package/dist/web-backend-login.js.map +1 -0
  379. package/dist/web-card.d.ts +14 -0
  380. package/dist/web-card.js +40 -6
  381. package/dist/web-card.js.map +1 -1
  382. package/dist/web-channel-echo.d.ts +7 -1
  383. package/dist/web-channel-echo.js +6 -2
  384. package/dist/web-channel-echo.js.map +1 -1
  385. package/dist/web-chat-history.d.ts +83 -0
  386. package/dist/web-chat-history.js +137 -5
  387. package/dist/web-chat-history.js.map +1 -1
  388. package/dist/web-commands.d.ts +6 -1
  389. package/dist/web-commands.js +1 -1
  390. package/dist/web-commands.js.map +1 -1
  391. package/dist/web-effect-bootstrap.d.ts +12 -0
  392. package/dist/web-effect-bootstrap.js +49 -0
  393. package/dist/web-effect-bootstrap.js.map +1 -0
  394. package/dist/web-effect-bundle.js +46 -1
  395. package/dist/web-effect-bundle.js.map +1 -1
  396. package/dist/web-effect-playback.d.ts +30 -0
  397. package/dist/web-effect-playback.js +71 -0
  398. package/dist/web-effect-playback.js.map +1 -0
  399. package/dist/web-effect-profiles.d.ts +4 -4
  400. package/dist/web-effect-service.d.ts +11 -4
  401. package/dist/web-effect-service.js +25 -4
  402. package/dist/web-effect-service.js.map +1 -1
  403. package/dist/web-effect-tools.d.ts +1 -0
  404. package/dist/web-effect-tools.js +4 -4
  405. package/dist/web-effect-tools.js.map +1 -1
  406. package/dist/web-effect-trials.d.ts +2 -2
  407. package/dist/web-effect-trials.js +3 -3
  408. package/dist/web-effect-trials.js.map +1 -1
  409. package/dist/web-effect-upload.js +1 -1
  410. package/dist/web-effect-upload.js.map +1 -1
  411. package/dist/web-effect-worker.d.ts +1 -1
  412. package/dist/web-effect-worker.js +2 -2
  413. package/dist/web-effect-worker.js.map +1 -1
  414. package/dist/web-effect.d.ts +1 -1
  415. package/dist/web-guest-access.d.ts +126 -0
  416. package/dist/web-guest-access.js +238 -0
  417. package/dist/web-guest-access.js.map +1 -0
  418. package/dist/web-guest-rooms.d.ts +82 -0
  419. package/dist/web-guest-rooms.js +135 -0
  420. package/dist/web-guest-rooms.js.map +1 -0
  421. package/dist/web-host-guard.d.ts +11 -0
  422. package/dist/web-host-guard.js +11 -0
  423. package/dist/web-host-guard.js.map +1 -1
  424. package/dist/web-identity-color.d.ts +1 -1
  425. package/dist/web-identity-color.js +2 -2
  426. package/dist/web-identity-color.js.map +1 -1
  427. package/dist/web-identity-palette.d.ts +1 -1
  428. package/dist/web-identity-palette.js +2 -2
  429. package/dist/web-identity-palette.js.map +1 -1
  430. package/dist/web-image-meta.d.ts +13 -0
  431. package/dist/web-image-meta.js +62 -0
  432. package/dist/web-image-meta.js.map +1 -1
  433. package/dist/web-libavoid.d.ts +14 -0
  434. package/dist/web-libavoid.js +46 -0
  435. package/dist/web-libavoid.js.map +1 -0
  436. package/dist/web-login.d.ts +47 -0
  437. package/dist/web-login.js +115 -0
  438. package/dist/web-login.js.map +1 -1
  439. package/dist/web-mod-agent.d.ts +1 -1
  440. package/dist/web-mod-agent.js +7 -5
  441. package/dist/web-mod-agent.js.map +1 -1
  442. package/dist/web-mod-bundle.d.ts +9 -1
  443. package/dist/web-mod-bundle.js +62 -3
  444. package/dist/web-mod-bundle.js.map +1 -1
  445. package/dist/web-mod-export.d.ts +9 -0
  446. package/dist/web-mod-export.js +21 -0
  447. package/dist/web-mod-export.js.map +1 -1
  448. package/dist/web-mod-store.d.ts +7 -1
  449. package/dist/web-mod-store.js +19 -6
  450. package/dist/web-mod-store.js.map +1 -1
  451. package/dist/web-owner-css-store.d.ts +44 -1
  452. package/dist/web-owner-css-store.js +70 -9
  453. package/dist/web-owner-css-store.js.map +1 -1
  454. package/dist/web-prefs.d.ts +14 -0
  455. package/dist/web-prefs.js +37 -4
  456. package/dist/web-prefs.js.map +1 -1
  457. package/dist/web-preview.d.ts +6 -2
  458. package/dist/web-preview.js +4 -2
  459. package/dist/web-preview.js.map +1 -1
  460. package/dist/web-returning-device.d.ts +41 -0
  461. package/dist/web-returning-device.js +57 -5
  462. package/dist/web-returning-device.js.map +1 -1
  463. package/dist/web-session-authorization.d.ts +132 -0
  464. package/dist/web-session-authorization.js +193 -0
  465. package/dist/web-session-authorization.js.map +1 -0
  466. package/dist/web-session.d.ts +70 -10
  467. package/dist/web-session.js +137 -22
  468. package/dist/web-session.js.map +1 -1
  469. package/dist/web-settings-patch.d.ts +49 -2
  470. package/dist/web-settings-patch.js +145 -1
  471. package/dist/web-settings-patch.js.map +1 -1
  472. package/dist/web-settings-tools.d.ts +94 -0
  473. package/dist/web-settings-tools.js +301 -0
  474. package/dist/web-settings-tools.js.map +1 -0
  475. package/dist/web-shell-routes.d.ts +1 -1
  476. package/dist/web-shell-routes.js +1 -1
  477. package/dist/web-shell-routes.js.map +1 -1
  478. package/dist/web-terminal-http.js +15 -2
  479. package/dist/web-terminal-http.js.map +1 -1
  480. package/dist/web-theme-store.d.ts +39 -0
  481. package/dist/web-theme-store.js +122 -11
  482. package/dist/web-theme-store.js.map +1 -1
  483. package/dist/web-theme.d.ts +31 -0
  484. package/dist/web-theme.js +99 -2
  485. package/dist/web-theme.js.map +1 -1
  486. package/dist/web-upload.d.ts +13 -0
  487. package/dist/web-upload.js +39 -3
  488. package/dist/web-upload.js.map +1 -1
  489. package/dist/windows-shortcuts.d.ts +78 -0
  490. package/dist/windows-shortcuts.js +203 -0
  491. package/dist/windows-shortcuts.js.map +1 -0
  492. package/dist/workflow-templates/default.md +4 -1
  493. package/dist/yaml-shared-nodes.d.ts +25 -0
  494. package/dist/yaml-shared-nodes.js +185 -0
  495. package/dist/yaml-shared-nodes.js.map +1 -0
  496. package/launcher/agend-install.cjs +553 -0
  497. package/launcher/install-admission.cjs +4 -39
  498. package/launcher/postinstall.cjs +14 -12
  499. package/package.json +4 -2
  500. package/scripts/preinstall-guard.cjs +2 -5
  501. package/templates/windows-shortcuts.ps1 +181 -0
  502. package/launcher/old-updater-note.cjs +0 -48
@@ -28,6 +28,7 @@ const P = () => globalThis.AgendPreview;
28
28
  /** #1672: a status line longer than this (lines, or characters) is folded until it is opened. */
29
29
  export const STATUS_FOLD_LINES = 6;
30
30
  export const STATUS_FOLD_CHARS = 600;
31
+ const REPLY = '<svg class="icon" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M9 14 4 9l5-5"/><path d="M4 9h10.5a5.5 5.5 0 0 1 0 11H11"/></svg>';
31
32
  const COPY = '<svg class="icon" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>';
32
33
 
33
34
  /** Escapes & " ' < >: safe for text and inside a quoted attribute. */
@@ -39,6 +40,41 @@ export const msgKey = (x) => `${x.boot}-${x.id}`;
39
40
  */
40
41
  export const THREAD_TAIL = 24;
41
42
  export const THREAD_CHUNK = 40;
43
+ /** #2158: a reader's scroll (smooth, momentum) is theirs until it has been this quiet since their last input or scroll. */
44
+ export const READER_QUIET_MS = 250;
45
+ const SCROLL_KEYS = new Set(["PageUp", "PageDown", "ArrowUp", "ArrowDown", "Home", "End", " "]);
46
+
47
+ /**
48
+ * #2053: a message's rendered Markdown, kept across renders and chat switches. A draw compares each message's html with
49
+ * its node's, so every render of a thread rendered every drawn message's Markdown again (~320 calls per 200-message
50
+ * switch). renderMarkdown's output depends only on the text, whether its HTML cards are on (an agent's message), and —
51
+ * for a text that has them — whether the page knows each server-emoji id and the name it has for each mention: the key
52
+ * holds exactly those, so a cached html is always what renderMarkdown would return now. Bounded: least recently used out.
53
+ */
54
+ export const MD_CACHE_MAX = 4000;
55
+ const mdCache = new Map();
56
+ const EMOJI_IDS = /<a?:[A-Za-z0-9_]{2,32}:(\d{15,25})>/g;
57
+ const MENTION_IDS = /<(@!?|@&|#)(\d{15,25})>/g;
58
+ const MENTION_KINDS = { "@": "u", "@!": "u", "@&": "r", "#": "c" };
59
+ export function renderedMarkdown(text, cards, emojis, mentions) {
60
+ const src = typeof text === "string" ? text : String(text == null ? "" : text);
61
+ let sig = cards ? "c" : "-";
62
+ if (src.includes("<")) {
63
+ // Read as renderMarkdown reads it: it drops NUL first (its placeholder mark), so "<@\0id>" is a mention there too.
64
+ const seen = src.includes("\u0000") ? src.replace(/\u0000/g, "") : src;
65
+ for (const m of seen.matchAll(EMOJI_IDS)) sig += `|e${m[1]}${emojis && emojis(m[1]) ? 1 : 0}`;
66
+ for (const m of seen.matchAll(MENTION_IDS)) { const name = mentions ? mentions(MENTION_KINDS[m[1]], m[2]) : null; sig += `|m${m[1]}${m[2]}=${typeof name === "string" ? name.length + ":" + name : "-"}`; }
67
+ }
68
+ const key = `${sig}\u0000${src}`;
69
+ let html = mdCache.get(key);
70
+ if (html === undefined) html = R().renderMarkdown(src, { htmlCards: cards, emojis, mentions });
71
+ else mdCache.delete(key);
72
+ mdCache.set(key, html);
73
+ while (mdCache.size > MD_CACHE_MAX) mdCache.delete(mdCache.keys().next().value);
74
+ return html;
75
+ }
76
+ /** Tests only: forget every rendered Markdown. */
77
+ export function clearRenderedMarkdown() { mdCache.clear(); }
42
78
 
43
79
  /**
44
80
  * Bind a renderer to `list` (the thread element) inside `scroller`. opts: { t, tf, isUser(x), onJump({show, unseen}),
@@ -47,6 +83,17 @@ export const THREAD_CHUNK = 40;
47
83
  */
48
84
  export function createThread(list, scroller, opts) {
49
85
  const nodes = new Map(); // key → { html, node }
86
+ // A server emoji's picture that does not load reads `:name:` (its alt) instead of an empty box (error does not bubble:
87
+ // caught on the way down).
88
+ list.addEventListener("error", (e) => {
89
+ const img = e.target;
90
+ if (!img || img.tagName !== "IMG" || !img.classList || !img.classList.contains("emoji")) return;
91
+ const span = list.ownerDocument.createElement("span");
92
+ span.className = "emoji-text";
93
+ span.textContent = img.getAttribute("alt") || "";
94
+ span.title = img.getAttribute("title") || span.textContent;
95
+ img.replaceWith(span);
96
+ }, true);
50
97
  let msgs = [];
51
98
  let unseen = 0;
52
99
  // The reader is at the newest message. While pinned, content that grows after it was drawn — an image loading, a
@@ -54,12 +101,15 @@ export function createThread(list, scroller, opts) {
54
101
  // Only a move UP unpins: the browser's own scroll anchoring also fires scroll events when content grows (the view
55
102
  // moves down or stays), and those must not count as the reader leaving the bottom. Reaching the bottom pins again.
56
103
  let pinned = true;
57
- let lastTop = 0;
104
+ // Where the view was (and how tall it was) at the last scroll the page saw or made: a move up from there unpins.
105
+ let lastTop = 0, lastView = 0;
106
+ const mark = () => { lastTop = scroller.scrollTop; lastView = scroller.clientHeight; };
58
107
  // The oldest message drawn (by key): everything after it is drawn, so new messages never push the window. Null: none
59
108
  // drawn yet — the next render draws the newest THREAD_TAIL and the rest follows (grow).
60
109
  let firstKey = null;
61
- let growTimer = null, growFrame = null, disposed = false;
110
+ let growTimer = null, growFrame = null, disposed = false, parked = false;
62
111
  const cardRefresh = new WeakMap(); // card node → re-read availability (weak: a card that left takes its source with it)
112
+ const cardDrop = new WeakMap(); // card node → drop the HTML read it is waiting on (the chat left: park)
63
113
  const tr = opts.t, trf = opts.tf;
64
114
  const stopIn = (node) => (P() ? P().stopIn(node) : []);
65
115
  /** #1770: write a message node's time label for now; unchanged text is not touched. */
@@ -92,7 +142,22 @@ export function createThread(list, scroller, opts) {
92
142
  const buttons = x.role === "agent" ? replyButtonsHtml(x.buttons) : "";
93
143
  // A person's click on a reply button shows as the button's label, never the line the agent was given (its value).
94
144
  const clicked = x.role === "user" && x.click && typeof x.click.label === "string";
95
- return `<div class="msg ${u ? "user" : "agent"}"><div class="meta"><span class="sender">${escAttr(x.sender)}</span>${time}${ticks}</div><div class="body">${clicked ? clickHtml(x.click) : `<div class="md">${R().renderMarkdown(x.text, { htmlCards: x.role === "agent", emojis: opts.emojis, mentions: opts.mentions })}</div>`}${R().attachmentsHtml(x.attachments, { htmlCards: x.role === "agent", goneTitle: tr("chat.attGone"), goneLabel: tr("chat.attUnavailable"), inlinePreviews: !PUBLIC_LINK() })}${buttons}</div>${tools}</div>`;
145
+ // #2272: reply to it — a hover strip at its top right (phones: swipe left or long-press; R on a focused message).
146
+ const key = msgKey(x);
147
+ const act = opts.canReply?.() === false ? "" : `<div data-trust="thread-control" class="msg-actions"><button type="button" class="chip-btn icon-only" data-act="reply" data-arg="${escAttr(key)}" title="${escAttr(tr("chat.reply"))}" aria-label="${escAttr(tr("chat.reply"))}">${REPLY}</button></div>`;
148
+ return `<div class="msg ${u ? "user" : "agent"}" data-key="${escAttr(key)}" tabindex="-1">${act}<div class="meta"><span class="sender">${escAttr(x.sender)}</span>${time}${ticks}</div>${quoteHtml(x.replyTo)}<div class="body">${clicked ? clickHtml(x.click) : `<div class="md">${renderedMarkdown(x.text, x.role === "agent", opts.emojis, opts.mentions)}</div>`}${R().attachmentsHtml(x.attachments, { htmlCards: x.role === "agent", goneTitle: tr("chat.attGone"), goneLabel: tr("chat.attUnavailable"), inlinePreviews: !PUBLIC_LINK() })}${buttons}</div>${tools}</div>`;
149
+ }
150
+ /**
151
+ * #2272: what a reply quotes, above its body — the quoted sender and first words (or that it had files), as the server
152
+ * read them when it was sent; a button that shows that message. A guest that may not see it gets only "an earlier
153
+ * message". Text only: everything is escaped.
154
+ */
155
+ function quoteHtml(q) {
156
+ if (!q || typeof q !== "object" || typeof q.boot !== "string" || typeof q.id !== "number") return "";
157
+ const words = q.hidden ? tr("chat.replyHidden")
158
+ : (typeof q.text === "string" && q.text) || (q.media === "photo" ? tr("chat.replyPhoto") : q.media === "document" ? tr("chat.replyFile") : "");
159
+ const who = !q.hidden && typeof q.sender === "string" && q.sender ? `<span class="quote-sender">${escAttr(q.sender)}</span> ` : "";
160
+ return `<button type="button" data-trust="thread-control" class="msg-quote" data-act="jumpQuote" data-arg="${escAttr(`${q.boot}-${q.id}`)}" title="${escAttr(tr("chat.replyJump"))}"><span class="quote-mark">${REPLY}</span> ${who}<span class="quote-text">${escAttr(words)}</span></button>`;
96
161
  }
97
162
  /**
98
163
  * A button click as a chip: 🔘 and the label (escaped: the label is the agent's text). With `of` (live, while its
@@ -106,10 +171,18 @@ export function createThread(list, scroller, opts) {
106
171
  : `<span class="click-text">${inner}</span>`}</div>`;
107
172
  }
108
173
  function trialNow() { try { const t = opts.serverNow ? opts.serverNow() : Date.now(); return Number.isFinite(t) ? t : Infinity; } catch { return Infinity; } }
174
+ const trialReasons = new Set(["checking", "localAdmin", "chatChanged", "busy", "safe", "effectsOff", "reducedMotion", "saveData", "unsupported", "transport", "inactive", "dialog", "expired"]);
175
+ function trialStatus(message) {
176
+ const reason = opts.effectTrialStatus?.(message);
177
+ return reason === "ready" || trialReasons.has(reason) ? reason : opts.canTryEffect?.(message) === true ? "ready" : "localAdmin";
178
+ }
109
179
  function trialHtml(offer, time, message) {
110
- const expired = trialNow() >= offer.expiresAt, available = !PUBLIC_LINK() && !expired && opts.canTryEffect?.(message) === true;
111
- const button = available ? `<button type="button" class="btn btn-sm effect-trial-start" data-act="effectTrial" data-arg="${escAttr(msgKey(message))}">${escAttr(tr("chat.effectTrialStart"))}</button>` : "";
112
- return `<div class="msg status-line"><div class="meta">${time}</div><div class="body"><div class="effect-trial-offer" data-trust="thread-control"><strong>${escAttr(tr("chat.effectTrial"))}: ${escAttr(offer.title)}</strong><div>${escAttr(tr(expired ? "chat.effectTrialExpired" : "chat.effectTrialLocal"))}</div>${button}</div></div></div>`;
180
+ const reason = trialNow() >= offer.expiresAt ? "expired" : PUBLIC_LINK() ? "localAdmin" : trialStatus(message);
181
+ const rawFailure = opts.effectTrialFailure?.(message), failure = typeof rawFailure === "string" ? rawFailure.replace(/[\u0000-\u001f\u007f]/g, "").slice(0, 80) : null;
182
+ const available = reason === "ready", fix = !PUBLIC_LINK() && !available && reason !== "expired" && reason !== "checking" && typeof opts.fixEffectTrial === "function";
183
+ const label = available ? "chat.effectTrialStart" : reason === "busy" ? "chat.effectTrialStopOther" : reason === "localAdmin" ? "chat.effectTrialSignIn" : reason === "reducedMotion" ? "chat.effectTrialPlayMotion" : "chat.effectTrialSettings";
184
+ const button = available || fix ? `<button type="button" class="btn btn-sm effect-trial-start" data-act="effectTrial" data-arg="${escAttr(msgKey(message))}">${escAttr(tr(label))}</button>` : "";
185
+ return `<div class="msg status-line"><div class="meta">${time}</div><div class="body"><div class="effect-trial-offer" data-trust="thread-control"><strong>${escAttr(tr("chat.effectTrial"))}: ${escAttr(offer.title)}</strong><div>${escAttr(tr(available ? "chat.effectTrialLocal" : "chat.effectTrialReason_" + reason))}</div>${failure ? `<div class="effect-trial-failure" role="status">${escAttr(tr("chat.effectTrialStopped"))}: ${escAttr(failure)}</div>` : ""}${button}</div></div></div>`;
113
186
  }
114
187
  function armTrials() {
115
188
  if (trialTimer !== null) clearTimeout(trialTimer); trialTimer = null;
@@ -269,7 +342,7 @@ export function createThread(list, scroller, opts) {
269
342
  const note = el("div", "pv-note"); note.setAttribute("data-trust", "thread-control");
270
343
  const body = el("div", "tc-body"); body.hidden = true;
271
344
  ph.append(head, note, body);
272
- let text = null, reading = false, all = false;
345
+ let text = null, reading = 0, reads = 0, all = false;
273
346
  const draw = () => {
274
347
  body.textContent = "";
275
348
  const lines = text.split(/\r?\n/);
@@ -305,12 +378,15 @@ export function createThread(list, scroller, opts) {
305
378
  body.append(more);
306
379
  }
307
380
  };
381
+ // #2053: as an HTML card's read (cardDrop), one still waiting when the chat left is dropped, the card not left Loading.
382
+ cardDrop.set(ph, () => { if (reading) { reading = 0; note.textContent = ""; } });
308
383
  show.onclick = async () => {
309
384
  if (reading) return;
310
385
  if (text !== null) { body.hidden = !body.hidden; show.textContent = tr(body.hidden ? "chat.tcShow" : "chat.tcHide"); return; }
311
- reading = true; note.textContent = tr("chat.pvLoading");
386
+ const mine = reading = ++reads; note.textContent = tr("chat.pvLoading");
312
387
  const r = await loadHtmlAttachment({ id: att.id, name: att.name, size: att.size });
313
- reading = false;
388
+ if (reading !== mine) return; // dropped (its chat left meanwhile): a new click reads again
389
+ reading = 0;
314
390
  if (!ph.isConnected) return; // the card left the page while it was read: nothing to draw
315
391
  if (!r.ok) { note.textContent = r.reason === "over" ? tr("chat.tcOver") : attReason(r.reason); return; }
316
392
  note.textContent = "";
@@ -448,6 +524,10 @@ export function createThread(list, scroller, opts) {
448
524
  optB.onclick = () => { menu.open = false; opts.setPreviewOptIn(!P().optedIn()); };
449
525
  neverB.onclick = () => { menu.open = false; P().setNever(!P().never()); refreshCards(); };
450
526
  cardRefresh.set(ph, refresh);
527
+ // #2053: a kept card is shown again later. A read still waiting when its chat left is dropped then: its answer —
528
+ // whenever it comes — starts nothing (no preview, no panel without a new click), and the card is not left Loading
529
+ // (shown again, it is refreshed: refreshCards).
530
+ cardDrop.set(ph, () => { reading = 0; });
451
531
  refresh(stoppedKeys.includes(key) ? tr("chat.pvChanged") : null);
452
532
  }
453
533
  /** Re-read this device's preview choice on every card in this thread. */
@@ -462,12 +542,45 @@ export function createThread(list, scroller, opts) {
462
542
  // newest bubble clipped behind it, and the next growth past 48 px put the pill over it.
463
543
  const atBottom = () => R().isNearBottom(scroller.scrollTop, scroller.clientHeight, scroller.scrollHeight, 1);
464
544
  function reportJump() { opts.onJump({ show: !nearBottom(), unseen }); }
465
- function toBottom() { scroller.scrollTop = scroller.scrollHeight; pinned = true; lastTop = scroller.scrollTop; }
545
+ function toBottom() { snap(); scroller.scrollTop = scroller.scrollHeight; pinned = true; mark(); }
546
+ /**
547
+ * #2184: a thread's height is rarely a whole number of pixels, and a pinned view's offset is: so each time older messages
548
+ * were drawn above, the whole view moved by the change in that fraction (0.6–0.7 px — the faint jitter of a chat
549
+ * opened cold). A sub-pixel margin above the list keeps the thread's height whole, so the bottom never moves.
550
+ */
551
+ // #2188: one read (the list's box excludes its own margin, so nothing is reset first) and a write only on a change —
552
+ // a reset-then-measure laid a 200-message thread out twice on every pinned draw (2.3 s at 4× CPU on a kept chat).
553
+ let snapped = null;
554
+ function snap() {
555
+ const h = list.getBoundingClientRect().height, f = h - Math.floor(h);
556
+ const want = f > 0.001 && f < 0.999 ? (1 - f) + "px" : "0px";
557
+ if (want !== snapped) { snapped = want; list.style.setProperty("--thread-snap", want); }
558
+ }
466
559
  // Sizes change without a scroll event (layout after the render, the dock under the thread): while pinned, the view
467
560
  // stays flush with the bottom; a reader who scrolled up is never moved.
468
561
  const resized = () => { if (pinned && !atBottom()) toBottom(); reportJump(); };
469
- const ro = typeof ResizeObserver === "function" ? new ResizeObserver(resized) : null;
562
+ let ro = typeof ResizeObserver === "function" ? new ResizeObserver(resized) : null;
470
563
  if (ro) { ro.observe(list); ro.observe(scroller); }
564
+ // #2158: the reader's own hand on the thread — a wheel, a touch, the scrollbar (a pointer held), a scrolling key
565
+ // outside a text field — and the scroll that follows it until it has been quiet READER_QUIET_MS.
566
+ let inputAt = -Infinity, held = false;
567
+ const touched = () => { inputAt = performance.now(); };
568
+ const hold = (on) => () => { held = on; touched(); };
569
+ const onKey = (ev) => {
570
+ const el = ev.target;
571
+ if (SCROLL_KEYS.has(ev.key) && !(el && (el.isContentEditable || /^(INPUT|TEXTAREA|SELECT)$/.test(el.tagName || "")))) touched();
572
+ };
573
+ const onScroller = [["wheel", touched], ["touchstart", hold(true)], ["touchend", hold(false)], ["touchcancel", hold(false)], ["pointerdown", hold(true)]];
574
+ const onDocument = [["pointerup", hold(false)], ["pointercancel", hold(false)], ["keydown", onKey]];
575
+ /** Listen for the reader's input on `sc` (and its document: a pointer let go anywhere, a key), or stop. */
576
+ function watchReader(sc, on) {
577
+ const doc = sc.ownerDocument, how = on ? "addEventListener" : "removeEventListener";
578
+ for (const [type, fn] of onScroller) sc[how](type, fn, { passive: true });
579
+ if (doc) for (const [type, fn] of onDocument) doc[how](type, fn, true);
580
+ if (!on) held = false;
581
+ }
582
+ const readerActive = () => held || performance.now() - inputAt < READER_QUIET_MS;
583
+ watchReader(scroller, true);
471
584
 
472
585
  /** Where drawing starts in `msgs`: at the oldest drawn message, or (none yet) the newest THREAD_TAIL. */
473
586
  function startIndex() {
@@ -481,14 +594,14 @@ export function createThread(list, scroller, opts) {
481
594
  /** Draw the next chunk of older messages, keeping the reader where they were; then the next, until all are drawn. */
482
595
  function grow() {
483
596
  growTimer = growFrame = null;
484
- if (disposed) return; // the chat left: nothing more is drawn
597
+ if (disposed || parked) return; // the chat left: nothing more is drawn (a kept one fills in when shown again)
485
598
  const at = startIndex();
486
599
  if (at <= 0) return;
487
600
  firstKey = msgKey(msgs[Math.max(0, at - THREAD_CHUNK)]);
488
601
  const fromBottom = scroller.scrollHeight - scroller.scrollTop;
489
602
  draw({ older: true }); // drawn above the reader: never "new below" (render alone counts those)
490
603
  if (pinned) toBottom();
491
- else { scroller.scrollTop = scroller.scrollHeight - fromBottom; lastTop = scroller.scrollTop; }
604
+ else { scroller.scrollTop = scroller.scrollHeight - fromBottom; mark(); }
492
605
  scheduleGrow();
493
606
  }
494
607
  /** After the next frame has painted (the click's own frame stays light), draw a chunk more. */
@@ -529,7 +642,7 @@ export function createThread(list, scroller, opts) {
529
642
  if ("restore" in renderOpts) {
530
643
  unseen = 0;
531
644
  if (renderOpts.restore == null) toBottom();
532
- else { scroller.scrollTop = renderOpts.restore; pinned = nearBottom(); lastTop = scroller.scrollTop; }
645
+ else { scroller.scrollTop = renderOpts.restore; pinned = nearBottom(); mark(); }
533
646
  } else if (stick) toBottom();
534
647
  else unseen += added;
535
648
  reportJump();
@@ -559,14 +672,39 @@ export function createThread(list, scroller, opts) {
559
672
  }
560
673
  armDay();
561
674
  armPolls();
675
+ rove();
562
676
  return added;
563
677
  }
678
+ /**
679
+ * #2272 (review): one message in the thread is in the Tab order — the last one focused while it is still drawn, else
680
+ * the newest; ↑/↓ move between messages (chat-msg-actions.js). The rest are -1, so Tab passes the thread in one step.
681
+ */
682
+ let roving = null;
683
+ function rove(key) {
684
+ if (key !== undefined) roving = key;
685
+ const at = (roving && nodes.has(roving) ? nodes.get(roving).node : null) || [...list.querySelectorAll(".msg[data-key]")].at(-1) || null;
686
+ for (const el of list.querySelectorAll('.msg[data-key][tabindex="0"]')) if (el !== at) el.setAttribute("tabindex", "-1");
687
+ if (at) at.setAttribute("tabindex", "0");
688
+ }
564
689
 
565
690
  function onScroll() {
691
+ // #2158: the thread got shorter under a pinned view (text settling on a cold load, a card) and the browser clamped
692
+ // scrollTop to its new end — for the view as tall as it was then; the view got shorter too before WebKit sent that
693
+ // clamp's scroll event (it comes before the ResizeObserver). A view resting exactly at the bottom of the view as it
694
+ // was at the last mark, with no hand of the reader's on the thread, was put there by the browser: still pinned, back
695
+ // at the bottom. Geometry alone can't tell (a reader's move up by the dock's growth lands there too): any input of
696
+ // theirs, and the scroll after it, is theirs and unpins as before. The page's own scrolls (reveal) have no input but
697
+ // don't land there.
698
+ const reader = readerActive();
699
+ if (reader) touched(); // the reader's scroll goes on (smooth, momentum): still theirs while it does
700
+ else if (pinned && !atBottom() && Math.abs(scroller.scrollHeight - lastView - scroller.scrollTop) <= 1) toBottom();
566
701
  const bottom = nearBottom(), top = scroller.scrollTop;
567
- if (bottom) pinned = true;
702
+ // #2087: pinned only at the exact bottom — a reader still wheeling through the last 48 px was pinned there, so
703
+ // the next resize (a picture, a card, the dock) pulled the view the rest of the way down under them. Near the bottom
704
+ // still counts for a new message (render follows it) and for the pill; nothing else moves a view the reader moves.
705
+ if (atBottom()) pinned = true;
568
706
  else if (top < lastTop) pinned = false;
569
- lastTop = top;
707
+ mark();
570
708
  if (bottom) unseen = 0;
571
709
  reportJump();
572
710
  return bottom ? null : scroller.scrollTop;
@@ -628,21 +766,66 @@ export function createThread(list, scroller, opts) {
628
766
  // Only the current host-produced keyed card and a real local click can request admission. No token is read from DOM.
629
767
  const key = el.dataset.arg, row = msgs.find(x => msgKey(x) === key), owner = nodes.get(key), node = owner?.node, offer = effectTrialMessage(row);
630
768
  if (PUBLIC_LINK() || e.defaultPrevented || e.isTrusted !== true || owner?.trialButton !== el || el.className !== "btn btn-sm effect-trial-start" || !node?.contains(el) || el.closest(".effect-trial-offer")?.getAttribute("data-trust") !== "thread-control"
631
- || !offer || trialNow() >= offer.expiresAt || opts.canTryEffect?.(row) !== true) return;
632
- opts.startEffectTrial?.(row);
769
+ || !offer || trialNow() >= offer.expiresAt) return;
770
+ if (trialStatus(row) === "ready" && opts.canTryEffect?.(row) === true) opts.startEffectTrial?.(row);
771
+ else opts.fixEffectTrial?.(row, trialStatus(row));
633
772
  } else if (act === "showButtons") { const x = msgs.find(y => y.buttons && y.buttons.id === el.dataset.arg); if (x) reveal(msgKey(x)); }
773
+ else if (act === "reply") { const x = msgs.find(y => msgKey(y) === el.dataset.arg); if (x) opts.startReply?.(x); }
774
+ else if (act === "jumpQuote") void jumpTo(el.dataset.arg);
634
775
  }
776
+ /**
777
+ * #2272: show the message a reply quotes, flashed — drawn now, or after the chat reads back the longest history the
778
+ * server keeps; otherwise it says it is no longer there.
779
+ */
780
+ async function jumpTo(key) {
781
+ if (typeof key !== "string" || !key) return;
782
+ if (reveal(key)) return;
783
+ const found = opts.findQuoted ? await opts.findQuoted(key) : false;
784
+ if (!found || !reveal(key)) opts.notice?.(tr("chat.replyGone"));
785
+ }
786
+ /** #2272: a drawn message's row (for the message menu and gestures), or undefined. */
787
+ function messageOf(key) { return msgs.find(y => msgKey(y) === key); }
635
788
 
636
789
  /** The thread goes (the chat left, or another instance opened): every preview in it stops first. */
637
- function dispose() {
790
+ /**
791
+ * #2053: kept for a later visit — the chat left as it always did (every preview stops: "leave"), but its drawn nodes
792
+ * stay. Nothing of it runs meanwhile: no timer (day labels, polls, effect trials, filling in), no size observer; the
793
+ * caller drops its subscriptions. Shown again: attach, then render — only what changed is drawn.
794
+ */
795
+ function park() {
796
+ parked = true;
797
+ watchReader(scroller, false);
798
+ if (P()) P().stopAll("leave");
799
+ for (const node of list.querySelectorAll(".html-card[data-att], .text-card[data-att]")) { const f = cardDrop.get(node); if (f) f(); }
800
+ if (ro) { ro.disconnect(); ro = null; }
801
+ if (trialTimer !== null) clearTimeout(trialTimer); trialTimer = null;
802
+ if (growFrame !== null && typeof cancelAnimationFrame === "function") cancelAnimationFrame(growFrame);
803
+ if (growTimer !== null) clearTimeout(growTimer);
804
+ growTimer = growFrame = null;
805
+ if (offDay) { offDay(); offDay = null; }
806
+ if (pollTick) { clearInterval(pollTick); pollTick = null; }
807
+ }
808
+ /** In view in `sc` (the view's scroller): sizes watched there from now on; the caller restores the position. */
809
+ function attach(sc) {
810
+ if (!parked) watchReader(scroller, false); // moved from the scroller it was made with
811
+ parked = false;
812
+ scroller = sc;
813
+ watchReader(sc, true);
814
+ if (ro) ro.disconnect();
815
+ ro = typeof ResizeObserver === "function" ? new ResizeObserver(resized) : null;
816
+ if (ro) { ro.observe(list); ro.observe(sc); }
817
+ }
818
+ /** `own`: only this thread's previews stop (a thread let go while another chat is in view); else every one, as leaving. */
819
+ function dispose(own = false) {
638
820
  disposed = true;
821
+ if (!parked) watchReader(scroller, false);
639
822
  if (trialTimer !== null) clearTimeout(trialTimer); trialTimer = null;
640
823
  if (ro) ro.disconnect();
641
- if (P()) P().stopAll("leave");
824
+ if (P()) { if (own) P().stopIn(list); else P().stopAll("leave"); }
642
825
  nodes.clear();
643
826
  armDay();
644
827
  armPolls();
645
828
  }
646
829
 
647
- return { render, onScroll, onClick, jumpLatest, reveal, refreshCards, dispose, nodes, get unseen() { return unseen; } };
830
+ return { render, onScroll, onClick, jumpLatest, reveal, jumpTo, messageOf, rove, refreshCards, park, attach, dispose, nodes, get unseen() { return unseen; } };
648
831
  }
@@ -5,7 +5,7 @@ import { chatEffectIdentity } from "/assets/effect-chat-runtime.js";
5
5
  const owners = new WeakSet(), safe = fn => { try { return fn(); } catch { return null; } };
6
6
  const LIMIT = 128 * 1024;
7
7
  export function createEffectTrialController({ window: host, current, allowed, onStart, onChange = () => {} }) {
8
- let dead = false, active = null, physical = null, previous = null, broken = false, clockBusy = false;
8
+ let dead = false, active = null, physical = null, previous = null, broken = false, clockBusy = false, endedReason = null, endedOffer = null;
9
9
  const cleanup = [], releases = [], identity = safe(() => chatEffectIdentity(host));
10
10
  const tell = () => { if (!dead) safe(onChange); };
11
11
  function clock() {
@@ -26,11 +26,16 @@ export function createEffectTrialController({ window: host, current, allowed, on
26
26
  }
27
27
  function end(op, reason = "stopped") {
28
28
  if (!op || op.dead) return false;
29
- op.dead = true; if (active === op) active = null; safe(() => op.resolve?.(false));
29
+ op.dead = true; if (active === op) active = null;
30
+ endedReason = typeof reason === "string" && /^[a-z][a-z0-9_]{0,79}$/.test(reason) ? reason : "trial_failed";
31
+ endedOffer = op.offer.offer;
32
+ safe(() => op.resolve?.(false));
30
33
  safe(() => host.clearTimeout(op.timer)); safe(() => op.controller.abort());
31
34
  if (op.reply && !op.stopQueued) { op.stopQueued = true; cleanup.push(op); }
32
35
  // Physical paint is removed before callbacks, cleanup I/O or another explicit click.
33
- safe(() => op.dispose?.()); op.dispose = null; tell(); drain(); return true;
36
+ safe(() => op.dispose?.()); op.dispose = null;
37
+ if (!["stopped", "replaced"].includes(endedReason)) safe(() => host.console?.warn("[AgEnD effects] Try stopped:", endedReason));
38
+ tell(); drain(); return true;
34
39
  }
35
40
  function arm(op) {
36
41
  safe(() => host.clearTimeout(op.timer));
@@ -67,7 +72,7 @@ export function createEffectTrialController({ window: host, current, allowed, on
67
72
  if (dead) return; dead = true; end(active); for (const off of releases.splice(0)) safe(off); tell();
68
73
  }
69
74
  const api = { ok: false, start: async () => false, stop: () => end(active), sync() { const op = active; if (op && !eligible(op)) end(op, "trial_ended"); },
70
- snapshot: () => Object.freeze({ busy: !!physical || cleanup.length > 0, phase: active ? active.reply ? "running" : "starting" : "idle" }), dispose };
75
+ snapshot: () => Object.freeze({ busy: !!physical || cleanup.length > 0, phase: active ? active.reply ? "running" : "starting" : "idle", reason: active ? null : endedReason, offer: active ? active.offer.offer : endedOffer }), dispose };
71
76
  if (!identity || owners.has(host) || typeof current !== "function" || typeof allowed !== "function" || typeof onStart !== "function"
72
77
  || typeof host.AgendAuth?.trialSession !== "function" || typeof host.AgendAuth?.onSessionEnd !== "function") return Object.freeze(api);
73
78
  owners.add(host);
@@ -75,7 +80,7 @@ export function createEffectTrialController({ window: host, current, allowed, on
75
80
  const off = host.AgendAuth.onSessionEnd(dispose);
76
81
  if (dead) { safe(off); return Object.freeze(api); }
77
82
  releases.push(off);
78
- for (const [target, type, fn] of [[host, "blur", () => end(active)], [host, "pagehide", dispose], [host.document, "visibilitychange", () => { if (host.document.visibilityState !== "visible") end(active); }]]) {
83
+ for (const [target, type, fn] of [[host, "blur", () => end(active, "trial_window_blur")], [host, "pagehide", dispose], [host.document, "visibilitychange", () => { if (host.document.visibilityState !== "visible") end(active, "hidden"); }]]) {
79
84
  target.addEventListener(type, fn); releases.push(() => target.removeEventListener(type, fn));
80
85
  }
81
86
  } catch { dispose(); return Object.freeze(api); }
@@ -86,7 +91,7 @@ export function createEffectTrialController({ window: host, current, allowed, on
86
91
  const offer = safe(() => effectTrialMessage(message)), instance = safe(() => message.instance), began = clock();
87
92
  if (!offer || began === null || began + 10000 > Number.MAX_SAFE_INTEGER || safe(() => host.Date.now() < offer.expiresAt) !== true) return false;
88
93
  const op = { instance, offer, began, until: began + 10000, controller: new host.AbortController(), timer: null, reply: null, dead: false, stopQueued: false, dispose: null };
89
- active = op; const token = {}; physical = token;
94
+ endedReason = null; endedOffer = null; active = op; const token = {}; physical = token;
90
95
  const cancelled = new Promise(resolve => { op.resolve = resolve; });
91
96
  const work = Promise.resolve().then(async () => {
92
97
  if (!eligible(op)) return false;
@@ -105,12 +110,12 @@ export function createEffectTrialController({ window: host, current, allowed, on
105
110
  op.until = began + reply.remainingMs;
106
111
  if (op.until > Number.MAX_SAFE_INTEGER || !eligible(op)) return false;
107
112
  arm(op);
108
- const handle = Object.freeze({ reply, startedAt: began, instance, current: () => eligible(op), stop: () => end(op), });
113
+ const handle = Object.freeze({ reply, startedAt: began, instance, current: () => eligible(op), stop: (reason = "stopped") => end(op, reason), });
109
114
  const disposer = onStart(handle);
110
115
  if (typeof disposer !== "function") return false;
111
116
  if (!eligible(op)) { safe(disposer); return false; }
112
117
  op.dispose = disposer; tell(); return eligible(op);
113
- }).catch(() => false).then(ok => { if (!ok) end(op); return ok; }).finally(() => { if (physical === token) physical = null; tell(); drain(); });
118
+ }).catch(() => false).then(ok => { if (!ok) end(op, "trial_start_failed"); return ok; }).finally(() => { if (physical === token) physical = null; tell(); drain(); });
114
119
  // The logical caller may stop immediately, while the physical request/cancel still owns the lane.
115
120
  tell();
116
121
  return Promise.race([work, cancelled]);
@@ -1,15 +1,16 @@
1
1
  /** #1629: fixed host producer. Creates no frame or request; the admitted app controller owns its lease. */
2
2
  import { html, useLayoutEffect, useRef } from "/assets/app-html.js";
3
3
  import { register, t } from "/assets/app-i18n.js";
4
+ import { createEffectTrialShadow } from "/assets/effect-trial-shadow.js";
4
5
  import { createEffectTrialStage } from "/assets/effect-trial-stage.js";
5
6
  register("effectTrial", {
6
7
  en: { label: "Trial", stop: "Stop trial", close: "Close", note: "A bounded sample. Nothing is applied or saved." },
7
8
  "zh-TW": { label: "試播", stop: "停止試播", close: "關閉", note: "僅在此區塊試播,不會套用或儲存。" },
8
9
  });
9
10
  /** The caller owns authenticated admission, lease/deadline and shared pool. No script/HTML is rendered here. */
10
- export function EffectTrialPanel({ window: host = globalThis.window, binding, title, current, ownerCss, onStage, onStop, onClose }) {
11
- const panel = useRef(null), stage = useRef(null), life = useRef(null), latest = useRef(null);
12
- latest.current = { current, ownerCss, onStage, onStop, onClose };
11
+ export function EffectTrialPanel({ window: host = globalThis.window, binding, title, current, onStage, onStop, onClose }) {
12
+ const panel = useRef(null), life = useRef(null), latest = useRef(null);
13
+ latest.current = { current, onStage, onStop, onClose };
13
14
  const stop = close => {
14
15
  const entry = life.current; if (!entry || (close ? entry.closed : entry.dead)) return;
15
16
  if (close) entry.closed = true;
@@ -17,21 +18,23 @@ export function EffectTrialPanel({ window: host = globalThis.window, binding, ti
17
18
  try { (close ? entry.onClose : entry.onStop)?.("stopped"); } catch { /* lease already ended */ }
18
19
  };
19
20
  useLayoutEffect(() => {
20
- const entry = { dead: false, closed: false, api: null, onStop, onClose }; life.current = entry;
21
+ const entry = { dead: false, closed: false, api: null, shadow: null, onStop, onClose }; life.current = entry;
21
22
  const ended = reason => { if (entry.dead) return; entry.dead = true; try { entry.onStop?.(reason); } catch { /* never revive */ } };
22
23
  try {
23
- entry.api = createEffectTrialStage({ window: host, panel: panel.current, stage: stage.current, binding,
24
- current: own => !entry.dead && life.current === entry && latest.current.current?.(own) === true,
25
- ownerCss: () => latest.current.ownerCss?.(), onStop: ended });
26
- if (!entry.api) ended("stage_unavailable");
27
- else { latest.current.onStage?.(entry.api); }
24
+ entry.shadow = createEffectTrialShadow({ window: host, panel: panel.current, title,
25
+ labels: { label: t("effectTrial.label"), close: t("effectTrial.close"), stop: t("effectTrial.stop"), note: t("effectTrial.note") },
26
+ onStop: () => stop(false), onClose: () => stop(true), onError: ended,
27
+ onReady(shell) {
28
+ if (entry.dead || life.current !== entry) return;
29
+ entry.api = createEffectTrialStage({ window: host, panel: panel.current, stage: shell.stage, binding,
30
+ current: own => !entry.dead && life.current === entry && latest.current.current?.(own) === true, onStop: ended });
31
+ if (!entry.api) ended("stage_unavailable");
32
+ else { try { latest.current.onStage?.(entry.api); } catch { entry.api.dispose(); ended("stage_unavailable"); } }
33
+ } });
34
+ if (!entry.shadow) ended("stage_unavailable");
28
35
  } catch { entry.api?.dispose(); ended("stage_unavailable"); }
29
- return () => { entry.api?.dispose(); entry.dead = true; if (life.current === entry) life.current = null; };
36
+ return () => { entry.api?.dispose(); ended("stopped"); entry.shadow?.dispose(); entry.dead = true; if (life.current === entry) life.current = null; };
30
37
  }, [host, binding]);
31
- return html`<aside ref=${panel} data-trust="preview" class="pv-panel effect-trial-panel" aria-label=${t("effectTrial.label")}>
32
- <header class="effect-trial-head"><strong>${t("effectTrial.label")}</strong><span>${typeof title === "string" ? title : ""}</span><button type="button" class="btn" onClick=${() => stop(true)}>${t("effectTrial.close")}</button></header>
33
- <div class="effect-trial-controls"><button type="button" class="btn" onClick=${() => stop(false)}>${t("effectTrial.stop")}</button></div>
34
- <div class="effect-trial-body"><div ref=${stage} class="effect-trial-stage" aria-hidden="true"></div></div>
35
- <p class="effect-trial-note">${t("effectTrial.note")}</p>
36
- </aside>`;
38
+ useLayoutEffect(() => { life.current?.shadow?.text(title, { label: t("effectTrial.label"), close: t("effectTrial.close"), stop: t("effectTrial.stop"), note: t("effectTrial.note") }); });
39
+ return html`<aside ref=${panel} data-trust="preview" class="pv-panel effect-trial-panel" aria-label=${t("effectTrial.label")}></aside>`;
37
40
  }
@@ -0,0 +1,70 @@
1
+ // #2193: the web effects runtime stays unloaded until there is something it could play for this viewer. Before that,
2
+ // this module is all there is: a few store subscriptions comparing fields the page already has (no observer, listener,
3
+ // timer, layer or request). The runtime itself (app-effects.js and everything it imports) is the reviewed code,
4
+ // unchanged; it is imported and started here, once, the first time one of these holds:
5
+ // - the open chat has Canvas Worker placements: its status row's effect_chat_v (the hash of its placements, taken
6
+ // before the server's region filtering — so it can only wake for nothing, never miss something) is not the hash of
7
+ // an empty list;
8
+ // - the signed-in person has a personal effect slot chosen (fx.page/header/sidebar/box/cursor — app-prefs);
9
+ // - an agent's "Try" offer is shown (wakeEffects() from the chat), or Settings saved a personal effect.
10
+ // Once started it stays for the page: the runtime is one episode per window (a disposed one cannot start again).
11
+ // Owner CSS is not the runtime's: owner-css.js is installed by the app shell and works with the runtime never loaded.
12
+ import { appStore } from "/assets/app-store.js";
13
+ import { navStore } from "/assets/app-nav.js";
14
+ import { ownerEffectsChosen, onOwnerEffectsChanged } from "/assets/app-prefs.js";
15
+
16
+ /** effect_chat_v of a chat with no placements: sha256(JSON.stringify([])), first 32 hex (fleet-manager's status row). */
17
+ export const EMPTY_CHAT_EFFECTS = "4f53cda18c2baa0c0354bb5f9a3ecbe5";
18
+
19
+ let mod = null, waking = null, chatStore = null, started = false;
20
+ const awakeListeners = new Set();
21
+
22
+ /** Whether `name`'s chat has Canvas Worker placements (as the status rows say). */
23
+ export function chatHasEffects(name, app = appStore.get()) {
24
+ const v = name ? app.instances?.find(i => i.name === name)?.effect_chat_v : undefined;
25
+ return typeof v === "string" && /^[a-f0-9]{32}$/.test(v) && v !== EMPTY_CHAT_EFFECTS;
26
+ }
27
+
28
+ /** The running effects owner, or null while dormant (or when it could not start: as getAppEffects always was). */
29
+ export function getAppEffects(host) { return mod ? mod.getAppEffects(host) : null; }
30
+
31
+ /** Whether the runtime has been asked to start on this page. */
32
+ export function effectsAwake() { return waking !== null; }
33
+ /** Whether its module has loaded (started, or found it could not: getAppEffects() then says which). */
34
+ export function effectsLoaded() { return mod !== null; }
35
+
36
+ /** Called once the runtime has started (or immediately when it already has). Returns the unsubscribe. */
37
+ export function onEffectsAwake(fn) {
38
+ if (typeof fn !== "function") return () => {};
39
+ if (mod) { try { fn(); } catch { /* a listener's error is its own */ } return () => {}; }
40
+ awakeListeners.add(fn); return () => awakeListeners.delete(fn);
41
+ }
42
+
43
+ /** Load and start the runtime (once per page). Resolves to the owner, or null when it could not start. */
44
+ export function wakeEffects() {
45
+ if (waking) return waking;
46
+ waking = import("./app-effects.js").then(m => {
47
+ mod = m;
48
+ const owner = chatStore ? m.startAppEffects({ store: chatStore }) : null;
49
+ for (const fn of [...awakeListeners]) { try { fn(); } catch { /* the rest still hear */ } }
50
+ awakeListeners.clear();
51
+ return owner;
52
+ }, () => null);
53
+ return waking;
54
+ }
55
+
56
+ /** Wake when the open chat has placements or the person has a personal effect. */
57
+ function check() {
58
+ if (waking) return;
59
+ const route = navStore.get().route;
60
+ if ((route?.panel === "chat" && chatHasEffects(route.instance)) || ownerEffectsChosen()) void wakeEffects();
61
+ }
62
+
63
+ /** Boot (once per page, with the Chat store the runtime binds to). Starts nothing but the watch. */
64
+ export function startEffectsWaker({ store }) {
65
+ chatStore = store;
66
+ if (started) return;
67
+ started = true;
68
+ appStore.subscribe(check); navStore.subscribe(check); onOwnerEffectsChanged(check);
69
+ check();
70
+ }