@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,103 @@
1
+ /** #1629: one private Trial contribution. No frame, pool, Start/Stop HTTP or durable profile writes. */
2
+ import { checkEffectTrialStart } from "./effect-runtime-loader.js";
3
+ import { effectTrialStageIdentity, effectTrialStage, watchEffectTrialStage, disposeEffectTrialStage, constrainEffectTrialStage } from "./effect-trial-stage.js";
4
+ const safe = fn => { try { return fn(); } catch { return null; } };
5
+ const plain = x => !!x && [Object.prototype, null].includes(Object.getPrototypeOf(x));
6
+ function fields(value, required, optional = []) {
7
+ try {
8
+ if (!plain(value)) return null;
9
+ const keys = Reflect.ownKeys(value), out = Object.create(null);
10
+ if (keys.some(k => typeof k !== "string" || !required.includes(k) && !optional.includes(k)) || required.some(k => !keys.includes(k))) return null;
11
+ for (const k of keys) { const d = Object.getOwnPropertyDescriptor(value, k); if (!d || !("value" in d) || !d.enumerable) return null; out[k] = d.value; }
12
+ return out;
13
+ } catch { return null; }
14
+ }
15
+ const canonical = x => JSON.stringify(x, function(_key, value) {
16
+ return value && !Array.isArray(value) && typeof value === "object" ? Object.fromEntries(Object.keys(value).sort().map(k => [k, value[k]])) : value;
17
+ });
18
+ export function createEffectTrialSource({ window: host, current, ownerCurrent, stock, readResource, onChange = () => {} }) {
19
+ let dead = false, active = null, previous = null, clockBroken = false, clockBusy = false;
20
+ const used = new WeakSet();
21
+ function now() {
22
+ if (clockBroken || clockBusy) { clockBroken = true; return null; }
23
+ clockBusy = true; let value; try { value = host.performance.now(); } catch { value = null; } finally { clockBusy = false; }
24
+ if (clockBroken || typeof value !== "number" || !Number.isFinite(value) || value < 0 || value > Number.MAX_SAFE_INTEGER || previous !== null && value < previous) { clockBroken = true; return null; }
25
+ previous = value; return value;
26
+ }
27
+ function stop(lease, reason = "stopped") {
28
+ if (!lease || lease.dead) return false;
29
+ lease.dead = true; if (active === lease) active = null;
30
+ safe(() => host.clearTimeout(lease.timer)); safe(() => lease.off?.()); lease.off = null;
31
+ for (const controller of lease.pending) safe(() => controller.abort());
32
+ safe(() => disposeEffectTrialStage(lease.stage.receipt)); // Remove physical paint before callbacks can replace this contribution.
33
+ safe(onChange); safe(() => lease.onStop(reason)); return true;
34
+ }
35
+ function valid(lease) {
36
+ if (dead || !lease || active !== lease || lease.dead || lease.checking) return false;
37
+ lease.checking = true; let yes = false;
38
+ try {
39
+ const time = now();
40
+ yes = time !== null && time >= lease.startedAt && time < lease.until && current() === true && ownerCurrent(lease.owner, lease.identity.instance) === true && lease.current() === true;
41
+ const accepted = now(); yes = yes && accepted !== null && accepted >= lease.startedAt && accepted < lease.until;
42
+ } catch { /* unknown is not a lease */ } finally { lease.checking = false; }
43
+ return !dead && active === lease && !lease.dead && yes;
44
+ }
45
+ const checked = lease => { if (valid(lease)) return true; stop(lease, "trial_ended"); return false; };
46
+ function arm(lease) {
47
+ if (!checked(lease)) return;
48
+ const time = now(); if (time === null || time >= lease.until) { stop(lease, "trial_expired"); return; }
49
+ lease.timer = host.setTimeout(() => { if (active === lease && !lease.dead) arm(lease); }, Math.max(1, Math.ceil(lease.until - time)));
50
+ }
51
+ function attach(input, owner) {
52
+ if (dead || clockBroken) return null;
53
+ const x = fields(input, ["reply", "offer", "startedAt", "stage", "current", "onStop"]), stage = x && fields(x.stage, ["receipt", "dispose", "hide"]);
54
+ const identity = stage && effectTrialStageIdentity(stage.receipt), reply = identity && checkEffectTrialStart(x.reply, x.offer, identity.id);
55
+ const time = now(), until = reply && typeof x.startedAt === "number" ? x.startedAt + reply.remainingMs : null;
56
+ if (dead || !x || !stage || !identity || !reply || used.has(stage.receipt) || typeof stage.dispose !== "function" || typeof stage.hide !== "function"
57
+ || typeof x.current !== "function" || typeof x.onStop !== "function" || typeof x.startedAt !== "number" || !Number.isFinite(x.startedAt) || x.startedAt < 0
58
+ || time === null || x.startedAt > time || typeof until !== "number" || !Number.isFinite(until) || until > Number.MAX_SAFE_INTEGER || time >= until) return null;
59
+ const candidate = active;
60
+ if (safe(() => current() === true && ownerCurrent(owner, identity.instance) === true && x.current() === true) !== true || active !== candidate || dead) return null;
61
+ stop(candidate, "replaced");
62
+ // A stop callback may have installed a newer contribution. Never replace it from this older operation.
63
+ if (dead || active !== null) return null;
64
+ const lease = { identity, reply, stage, owner, current: x.current, onStop: x.onStop, startedAt: x.startedAt, until, dead: false, checking: false, pending: new Set(), timer: null, off: null, binding: null };
65
+ lease.binding = Object.freeze({ kind: "trial", key: identity.key, id: identity.id, source: lease, generation: identity.generation });
66
+ active = lease; used.add(stage.receipt);
67
+ lease.off = watchEffectTrialStage(stage.receipt, () => stop(lease, "stage_ended"));
68
+ if (typeof lease.off !== "function" || constrainEffectTrialStage(stage.receipt, () => checked(lease)) !== true || !checked(lease)) { stop(lease, "stage_unavailable"); return null; }
69
+ try { arm(lease); } catch { stop(lease, "trial_unavailable"); return null; }
70
+ if (!checked(lease)) return null;
71
+ safe(onChange);
72
+ return checked(lease) ? Object.freeze({ current: () => checked(lease), stop: () => stop(lease), snapshot: () => { if (!checked(lease)) return null; const t = now(); return t !== null && t < lease.until && active === lease ? { id: identity.id, key: identity.key, trial: reply.trial, remainingMs: lease.until - t } : null; } }) : null;
73
+ }
74
+ function descriptor() {
75
+ const lease = active; if (!lease || !checked(lease)) return [];
76
+ const proof = safe(stock), local = safe(() => effectTrialStage(lease.stage.receipt, lease.identity, proof));
77
+ if (!checked(lease)) return [];
78
+ if (!local?.root || local.root.isConnected !== true) { stop(lease, "stage_unavailable"); return []; }
79
+ return [{ key: lease.identity.key, id: lease.identity.id, by: "agent", region: "chat", layer: lease.reply.manifest.layer, order: 0, paused: false, visible: true,
80
+ instance: lease.identity.instance, generation: lease.identity.generation, trialStage: lease.stage.receipt, params: lease.reply.params, element: local.root }];
81
+ }
82
+ function binding(key, id) { const lease = active; return lease && lease.binding.key === key && lease.binding.id === id && checked(lease) ? lease.binding : null; }
83
+ function bindingCurrent(b) { return !!b && active?.binding === b && checked(b.source); }
84
+ async function load(b, signal) {
85
+ if (!bindingCurrent(b) || signal?.aborted) return null;
86
+ const lease = b.source; if (lease.pending.size) return null;
87
+ const controller = new host.AbortController(), abort = () => controller.abort(); lease.pending.add(controller);
88
+ signal?.addEventListener("abort", abort, { once: true });
89
+ try {
90
+ if (!bindingCurrent(b) || signal?.aborted) { abort(); return null; }
91
+ const value = await readResource({ trial: lease.reply.trial, id: b.id, instance: lease.identity.instance }, controller.signal);
92
+ if (!bindingCurrent(b) || signal?.aborted || controller.signal.aborted) return null;
93
+ if (!value || value.id !== b.id || canonical(value.manifest) !== canonical(lease.reply.manifest) || canonical(value.params) !== canonical(lease.reply.params)) { stop(lease, "resource_unavailable"); return null; }
94
+ return bindingCurrent(b) && !signal?.aborted && !controller.signal.aborted ? value : null;
95
+ } catch { if (active === lease) stop(lease, "resource_unavailable"); return null; }
96
+ finally { signal?.removeEventListener("abort", abort); lease.pending.delete(controller); }
97
+ }
98
+ return Object.freeze({ attach, descriptors: descriptor, binding, current: bindingCurrent, load,
99
+ stopFor(owner) { const lease = active; return lease?.owner === owner ? stop(lease, "chat_ended") : false; },
100
+ stop() { return stop(active); },
101
+ dispose() { if (dead) return; dead = true; stop(active, "disposed"); },
102
+ });
103
+ }
@@ -0,0 +1,147 @@
1
+ /** #1629: host-issued LOCAL trial paint receipt. No authority, loader, frame factory or pool is created here. */
2
+ import { effectHostStyle } from "./effect-host-style.js";
3
+ import { effectStockSnapshot } from "./effect-stock-snapshot.js";
4
+ import { STOCK_EFFECT_BARRIERS } from "./effect-stock-trust.js";
5
+ const receipts = new WeakMap(), owners = new WeakMap();
6
+ let serial = 0;
7
+ const none = v => v === undefined || v === "" || v === "none";
8
+ const number = n => typeof n === "number" && Number.isFinite(n) && Math.abs(n) <= 1e7;
9
+ function binding(raw) {
10
+ try {
11
+ if (!raw || ![Object.prototype, null].includes(Object.getPrototypeOf(raw))) return null;
12
+ const fields = ["key", "id", "run", "instance", "generation"], keys = Reflect.ownKeys(raw), out = {};
13
+ if (keys.length !== fields.length || keys.some(k => typeof k !== "string" || !fields.includes(k))) return null;
14
+ for (const k of fields) { const d = Object.getOwnPropertyDescriptor(raw, k); if (!d || !Object.hasOwn(d, "value") || !d.enumerable) return null; out[k] = d.value; }
15
+ if (typeof out.key !== "string" || !out.key.length || out.key.length > 256 || typeof out.id !== "string" || !/^[0-9a-f]{32}$/.test(out.id)
16
+ || typeof out.run !== "string" || !/^[0-9a-f]{32}$/.test(out.run) || typeof out.instance !== "string" || !out.instance.length || out.instance.length > 128
17
+ || !Number.isSafeInteger(out.generation) || out.generation < 1) return null;
18
+ return Object.freeze(out);
19
+ } catch { return null; }
20
+ }
21
+ function rect(el) {
22
+ try { const r = el.getBoundingClientRect(); return [r.x, r.y, r.width, r.height].every(number) && r.width > 0 && r.height > 0 ? { x: r.x, y: r.y, width: r.width, height: r.height } : null; } catch { return null; }
23
+ }
24
+ function visible(host, el) { const s = host.getComputedStyle(el); return s.display !== "none" && s.visibility === "visible" && !!rect(el); }
25
+ function neutral(s) {
26
+ return ["transform", "perspective", "filter", "backdropFilter", "rotate", "scale", "translate", "offsetPath", "clipPath", "maskImage"].every(k => none(s[k]))
27
+ && s.isolation === "auto" && s.mixBlendMode === "normal" && s.willChange === "auto" && (s.zoom === undefined || ["", "normal", "1"].includes(s.zoom)) && [undefined, "", "visible"].includes(s.contentVisibility);
28
+ }
29
+ /** Inspect immutable identity only; this is NOT current paint permission or a serialized grant. */
30
+ export function effectTrialStageIdentity(receipt) { return receipts.get(receipt)?.identity ?? null; }
31
+ /** Host-only lifetime observation. Physical removal precedes notification; subscribers never grant paint. */
32
+ export function watchEffectTrialStage(receipt, listener) {
33
+ const owner = receipts.get(receipt);
34
+ if (!owner || typeof listener !== "function") return null;
35
+ owner.listeners.add(listener);
36
+ return () => owner.listeners.delete(listener);
37
+ }
38
+ /** Add one host lease veto before a frame exists. It can only reduce the original owner's permission. */
39
+ export function constrainEffectTrialStage(receipt, current) {
40
+ const owner = receipts.get(receipt); return !!owner && owner.constrain(current) === true;
41
+ }
42
+ /** Retire through the issued owner; caller-supplied cleanup functions are never the physical boundary. */
43
+ export function disposeEffectTrialStage(receipt) {
44
+ const owner = receipts.get(receipt); if (!owner) return false;
45
+ owner.invalidate("stopped"); return true;
46
+ }
47
+ /** Missing/forged/old receipts never turn the ordinary stock front flag on. */
48
+ export function effectTrialStage(receipt, expected, stock) {
49
+ try {
50
+ const owner = receipts.get(receipt);
51
+ if (!owner || !expected || ![Object.prototype, null].includes(Object.getPrototypeOf(expected))) return null;
52
+ for (const key of ["key", "id", "instance", "generation"]) {
53
+ const d = Object.getOwnPropertyDescriptor(expected, key);
54
+ if (!d || !Object.hasOwn(d, "value") || d.value !== owner.identity[key]) return null;
55
+ }
56
+ return owner.project(stock);
57
+ } catch { return null; }
58
+ }
59
+
60
+ /** Only the fixed host TrialPanel supplies these elements and a current server-run lease. Not an agent manifest. */
61
+ export function createEffectTrialStage({ window: host, panel, stage, binding: raw, current, ownerCss, onStop = () => {} } = {}) {
62
+ const own = binding(raw);
63
+ if (!own || typeof current !== "function" || typeof ownerCss !== "function" || typeof onStop !== "function" || !host?.document || !panel || !stage || owners.has(stage)) return null;
64
+ let sheet; try { sheet = effectHostStyle(host); if (!sheet || host.CSS?.supports("contain", "paint") !== true || host.CSS.supports("overflow", "clip") !== true) return null; } catch { return null; }
65
+ const receipt = Object.freeze(Object.create(null)), frames = new Set(), listeners = new Set(); let dead = false, constraint = null;
66
+ const identity = Object.freeze({ key: own.key, id: own.id, instance: own.instance, generation: own.generation, serial: ++serial });
67
+ const invalidate = reason => {
68
+ if (dead) return; dead = true; receipts.delete(receipt); if (owners.get(stage) === receipt) owners.delete(stage);
69
+ try { host.removeEventListener("blur", blur); } catch { /* no revival */ }
70
+ for (const f of [...frames]) f.remove();
71
+ const notify = [...listeners]; listeners.clear();
72
+ for (const fn of notify) { try { fn(reason); } catch { /* one host owner cannot block another cleanup */ } }
73
+ try { onStop(reason); } catch { /* cleanup already won */ }
74
+ };
75
+ const blur = event => { if (event.target === host) invalidate("blur"); };
76
+ function check(stock) {
77
+ try {
78
+ // This callback may reenter disposal or replace the owner. Recheck identity after it returns.
79
+ if (dead || current(own) !== true || constraint !== null && constraint() !== true || dead || owners.get(stage) !== receipt || ownerCss() !== "off" || dead) return null;
80
+ const proof = effectStockSnapshot(stock), doc = host.document;
81
+ if (effectHostStyle(host) !== sheet) return null;
82
+ // The own panel MUST still suppress ordinary foreground effects. Never synthesize front:true.
83
+ if (!proof || proof.front !== false || doc.visibilityState !== "visible" || doc.hasFocus?.() !== true || doc.fullscreenElement || doc.pictureInPictureElement) return null;
84
+ const split = doc.querySelector(".main > .p-chat > .chat-split");
85
+ if (!split || doc.querySelectorAll(".main > .p-chat").length !== 1 || doc.querySelectorAll(".chat-split > .pv-panel").length !== 1
86
+ || panel.parentElement !== split || panel.ownerDocument !== doc || panel.isConnected !== true || panel.className !== "pv-panel effect-trial-panel" || panel.getAttribute("data-trust") !== "preview") return null;
87
+ const children = [...panel.children], names = ["effect-trial-head", "effect-trial-controls", "effect-trial-body", "effect-trial-note"];
88
+ if (children.length !== names.length || children.some((el, i) => el.className !== names[i]) || children[2].children.length !== 1 || children[2].firstElementChild !== stage
89
+ || stage.className !== "effect-trial-stage" || stage.ownerDocument !== doc || stage.isConnected !== true || stage.hasAttribute("tabindex") || stage.hasAttribute("contenteditable")) return null;
90
+ if ([...doc.querySelectorAll(STOCK_EFFECT_BARRIERS)].some(el => el !== panel && visible(host, el))
91
+ || [...doc.querySelectorAll(".pv-panel")].some(el => el !== panel && visible(host, el))) return null;
92
+ const active = doc.activeElement;
93
+ if (active && active !== doc.body && active !== doc.documentElement && (active.matches("input,select,textarea,[contenteditable]") || active.isContentEditable)) return null;
94
+ let el = stage, depth = 0;
95
+ while (el) { const s = host.getComputedStyle(el); if (++depth > 128 || el.ownerDocument !== doc || !neutral(s) || s.opacity !== "1" || s.visibility !== "visible" || s.display === "none" || s.contain !== (el === stage ? "paint" : "none")) return null; if (el === doc.documentElement) break; el = el.parentElement; }
96
+ if (el !== doc.documentElement) return null;
97
+ const s = host.getComputedStyle(stage), r = rect(stage), p = rect(panel);
98
+ if (!r || !p || s.position !== "relative" || s.zIndex !== "0" || s.overflowX !== "clip" || s.overflowY !== "clip"
99
+ || s.paddingLeft !== "0px" || s.paddingRight !== "0px" || s.paddingTop !== "0px" || s.paddingBottom !== "0px"
100
+ || ["borderLeftWidth", "borderRightWidth", "borderTopWidth", "borderBottomWidth"].some(k => s[k] !== "0px") || !none(s.boxShadow) || s.outlineStyle !== "none"
101
+ || r.x < p.x || r.y < p.y || r.x + r.width > p.x + p.width || r.y + r.height > p.y + p.height
102
+ || r.x < 0 || r.y < 0 || r.x + r.width > host.innerWidth || r.y + r.height > host.innerHeight) return null;
103
+ if ([...stage.children].some(child => ![...frames].some(f => f.element === child))) return null;
104
+ const width = Math.floor(r.width), height = Math.floor(r.height);
105
+ if (width < 1 || height < 1 || width > 4096 || height > 4096 || width * height > 8388608 || dead || owners.get(stage) !== receipt) return null;
106
+ if (current(own) !== true || constraint !== null && constraint() !== true || dead || owners.get(stage) !== receipt || ownerCss() !== "off" || dead || stage.isConnected !== true || panel.parentElement !== split) return null;
107
+ return { x: r.x, y: r.y, width, height };
108
+ } catch { return null; }
109
+ }
110
+ function project(stock) {
111
+ const r = check(stock); if (!r) { invalidate("stage_unavailable"); return null; }
112
+ return Object.freeze({ root: stage, rect: Object.freeze(r), identity, bind });
113
+ }
114
+ function bind(frame, { layer } = {}, stock) {
115
+ if (frames.size || !["front", "behind"].includes(layer) || frame?.ownerDocument !== host.document || frame.parentElement !== stage || frame.tagName !== "IFRAME") { frame?.remove?.(); return { ok: false }; }
116
+ // The new frame is temporarily known to this owner before the topology check; it remains hidden until sync.
117
+ const token = `agend-effect-trial-${identity.serial}`; let rule, removed = false;
118
+ const remove = () => {
119
+ if (removed) return; removed = true; frame.remove(); frames.delete(handle);
120
+ try { frame.classList.remove(token); const i = [...sheet.cssRules].indexOf(rule); if (i >= 0) sheet.deleteRule(i); } catch { /* physically removed first */ }
121
+ };
122
+ const hide = () => { try { rule.style.setProperty("visibility", "hidden", "important"); if (frame.isConnected && host.getComputedStyle(frame).visibility !== "hidden") remove(); } catch { remove(); } };
123
+ const handle = { ok: true, element: frame, remove, hide,
124
+ sync(proof) {
125
+ hide(); const r = check(proof); if (!r || removed) { invalidate("stage_unavailable"); return false; }
126
+ try {
127
+ for (const [k, v] of Object.entries({ left: "0px", top: "0px", width: `${r.width}px`, height: `${r.height}px` })) rule.style.setProperty(k, v, "important");
128
+ const s = host.getComputedStyle(frame), actual = rect(frame);
129
+ if (!actual || s.position !== "absolute" || s.visibility !== "hidden" || s.pointerEvents !== "none" || s.zIndex !== "0" || s.opacity !== (layer === "front" ? "0.85" : "1") || !neutral(s)
130
+ || !["x", "y", "width", "height"].every(k => Math.abs(actual[k] - r[k]) < 0.02) || actual.x < r.x || actual.y < r.y || actual.x + actual.width > r.x + r.width || actual.y + actual.height > r.y + r.height) throw Error("local geometry lost");
131
+ rule.style.setProperty("visibility", "visible", "important"); return true;
132
+ } catch { invalidate("stage_unavailable"); return false; }
133
+ },
134
+ };
135
+ frames.add(handle);
136
+ try {
137
+ const i = sheet.insertRule(`.${token}{}`, sheet.cssRules.length); rule = sheet.cssRules[i];
138
+ for (const [k, v] of Object.entries({ position: "absolute", visibility: "hidden", "pointer-events": "none", "z-index": "0", opacity: layer === "front" ? "0.85" : "1", border: "0px", margin: "0px", padding: "0px", "box-sizing": "border-box", "min-width": "0px", "min-height": "0px", "max-width": "none", "max-height": "none", outline: "none", "box-shadow": "none", "background-color": "transparent", animation: "none", transition: "none" })) rule.style.setProperty(k, v, "important");
139
+ frame.classList.add(token);
140
+ if (host.getComputedStyle(frame).visibility !== "hidden" || !check(stock)) throw Error("initial local hiding lost");
141
+ } catch { remove(); invalidate("stage_unavailable"); return { ok: false }; }
142
+ return handle;
143
+ }
144
+ receipts.set(receipt, { identity, project, listeners, invalidate, constrain(fn) { if (dead || frames.size || constraint !== null || typeof fn !== "function") return false; constraint = fn; return true; } }); owners.set(stage, receipt);
145
+ try { host.addEventListener("blur", blur); } catch { invalidate("unsupported"); return null; }
146
+ return Object.freeze({ receipt, dispose: () => invalidate("stopped"), hide: () => { for (const f of frames) f.hide(); } });
147
+ }
@@ -13,6 +13,8 @@ import { UsageBody, UsageDialog } from "./usage-dialog.js";
13
13
 
14
14
  export const TEXT_SIZE_KEY = "agend_text_size";
15
15
  export const VIEW_FIT_KEY = "agend_view_fit";
16
+ /** #1869: set once this device's View Fit was put back (a size chosen on another page used to turn it off). */
17
+ export const VIEW_FIT_RESTORED_KEY = "agend_view_fit_1869";
16
18
  const LEGACY_DENSITY_KEY = "agend_view_density";
17
19
  export const TEXT_SIZES = ["s", "m", "l"];
18
20
  /** View's terminal: its fitted size times this (S / M / L are View's old compact / – / comfortable). */
@@ -24,6 +26,7 @@ const write = (k, v) => { try { localStorage.setItem(k, v); } catch { /* this pa
24
26
  /**
25
27
  * This device's choice: `agend_text_size` (+ View's Fit), else — once — from View's `agend_view_density`:
26
28
  * comfortable → L, compact → S (Fit off); fit, or nothing → M with View on Fit (View's default, as before).
29
+ * #1869: until then any page's S / M / L also turned View's Fit off, so a Fit off from before is put back once.
27
30
  */
28
31
  export function loadTextSize() {
29
32
  let size = read(TEXT_SIZE_KEY), fit = read(VIEW_FIT_KEY);
@@ -31,7 +34,11 @@ export function loadTextSize() {
31
34
  const legacy = read(LEGACY_DENSITY_KEY);
32
35
  size = legacy === "comfortable" ? "l" : legacy === "compact" ? "s" : "m";
33
36
  fit = legacy === "comfortable" || legacy === "compact" ? "0" : "1";
34
- write(TEXT_SIZE_KEY, size); write(VIEW_FIT_KEY, fit);
37
+ write(TEXT_SIZE_KEY, size); write(VIEW_FIT_KEY, fit); write(VIEW_FIT_RESTORED_KEY, "1");
38
+ }
39
+ if (read(VIEW_FIT_RESTORED_KEY) !== "1") {
40
+ if (fit === "0") { fit = "1"; write(VIEW_FIT_KEY, fit); }
41
+ write(VIEW_FIT_RESTORED_KEY, "1");
35
42
  }
36
43
  return { size, viewFit: fit !== "0" };
37
44
  }
@@ -50,15 +57,22 @@ export function initTextSize() {
50
57
  window.addEventListener("storage", (e) => { if (!e || e.key === TEXT_SIZE_KEY || e.key === VIEW_FIT_KEY || e.key === null) textSizeStore.set(loadTextSize()); });
51
58
  }
52
59
  }
53
- /** A size chosen anywhere is the size everywhere (View's Fit included: the last choice wins). */
54
- export function setTextSize(size) {
60
+ /**
61
+ * A size chosen anywhere is the size everywhere. View's Fit is left only from View's own Aa (`view`): a size chosen on
62
+ * another page keeps View on Fit (#1869).
63
+ */
64
+ export function setTextSize(size, { view = false } = {}) {
55
65
  if (!TEXT_SIZES.includes(size)) return;
56
- write(TEXT_SIZE_KEY, size); write(VIEW_FIT_KEY, "0");
57
- textSizeStore.set({ size, viewFit: false });
66
+ write(TEXT_SIZE_KEY, size);
67
+ if (view) write(VIEW_FIT_KEY, "0");
68
+ textSizeStore.set((s) => ({ size, viewFit: view ? false : s.viewFit }));
58
69
  }
59
70
  export function setViewFit() { write(VIEW_FIT_KEY, "1"); textSizeStore.set((s) => ({ ...s, viewFit: true })); }
60
71
 
61
- /** Aa: S → M → L → S; on View, Fit → S → M → L → Fit. */
72
+ /**
73
+ * Aa: S → M → L → S; on View, Fit → S → M → L → Fit. Every page's button is as wide as its widest name (Fit's
74
+ * included): switching pages or sizes never moves the controls beside it.
75
+ */
62
76
  export function TextSizeButton({ view = false }) {
63
77
  const st = useStore(textSizeStore);
64
78
  const cur = view && st.viewFit ? "fit" : st.size;
@@ -66,8 +80,9 @@ export function TextSizeButton({ view = false }) {
66
80
  const next = order[(order.indexOf(cur) + 1) % order.length];
67
81
  const name = t(`app.textSize_${cur}`);
68
82
  const label = t("app.textSize", name);
69
- return html`<button type="button" class="btn btn-ghost btn-sm hd-text" onClick=${() => (next === "fit" ? setViewFit() : setTextSize(next))}
70
- title=${label} aria-label=${label}><${Icon} name="type" size=${16} /><span class="hide-narrow" aria-hidden="true">${name}</span></button>`;
83
+ return html`<button type="button" class="btn btn-ghost btn-sm hd-text" onClick=${() => (next === "fit" ? setViewFit() : setTextSize(next, { view }))}
84
+ title=${label} aria-label=${label}><${Icon} name="type" size=${16} /><span class="hide-narrow" aria-hidden="true"><span class="hd-text-names">${
85
+ ["fit", ...TEXT_SIZES].map(k => html`<span key=${k} class=${k === cur ? "on" : undefined}>${t(`app.textSize_${k}`)}</span>`)}</span></span></button>`;
71
86
  }
72
87
 
73
88
  /** Whether the fleet serves the usage panel: the page says so (data-usage-panel); "0" only when turned off. */
@@ -76,14 +91,15 @@ export function usageOffered() {
76
91
  }
77
92
 
78
93
  // #1673: the rail. Wide enough for it beside the sidebar and the page: RAIL_MEDIA (followed as it changes). Shown or
79
- // hidden is this device's choice (agend_usage_rail; "0" = hidden, else shown) — a layout choice, not synced (#1665).
94
+ // hidden is this device's choice (agend_usage_rail; "1" = shown, "0" = hidden) — a layout choice, not synced (#1665).
95
+ // #1854 (rank 7): a device that never chose starts with it hidden — a quiet first page; ◔ shows it (and remembers).
80
96
  export const RAIL_MEDIA = "(min-width: 1200px)";
81
97
  export const RAIL_KEY = "agend_usage_rail";
82
- /** The pages whose header has ◔ — the rail is theirs. */
83
- export const RAIL_PANELS = ["chat", "view", "details", "fleet"];
84
- export const usageRailStore = createStore({ open: true });
85
- /** A page load: this device's choice. */
86
- export function initUsageRail() { usageRailStore.set({ open: read(RAIL_KEY) !== "0" }); }
98
+ /** The pages whose header has ◔ — the rail is theirs (Log's too: one frame on every tab of an instance). */
99
+ export const RAIL_PANELS = ["chat", "view", "log", "details", "fleet"];
100
+ export const usageRailStore = createStore({ open: false });
101
+ /** A page load: this device's choice; none made yet = hidden. */
102
+ export function initUsageRail() { usageRailStore.set({ open: read(RAIL_KEY) === "1" }); }
87
103
  export function setUsageRail(open) { write(RAIL_KEY, open ? "1" : "0"); usageRailStore.set({ open: !!open }); }
88
104
  const railQuery = () => { try { return typeof matchMedia === "function" ? matchMedia(RAIL_MEDIA) : null; } catch { return null; } };
89
105
  /** Whether the screen is wide enough for the rail, now and as it changes. */
@@ -109,7 +125,7 @@ export function UsageRail({ route }) {
109
125
  const wide = useRailWidth();
110
126
  const { open } = useStore(usageRailStore);
111
127
  if (!wide || !open || !route || !RAIL_PANELS.includes(route.panel) || !usageOffered()) return null;
112
- return html`<aside class="u-rail" aria-label=${t("view.usage.title_plain")}>
128
+ return html`<aside data-trust="usage" class="u-rail" aria-label=${t("view.usage.title_plain")}>
113
129
  <div class="u-rail-head"><h2>${t("view.usage.title")}</h2>
114
130
  <button type="button" class="icon-btn" onClick=${() => setUsageRail(false)} aria-label=${t("app.usageHide")} title=${t("app.usageHide")}><${Icon} name="close" size=${16} /></button></div>
115
131
  <div class="u-rail-body"><${UsageBody} owner="usage-rail" /></div>
@@ -141,3 +157,7 @@ export function UsageButton({ navKey }) {
141
157
  export function HeaderTools({ view = false, navKey }) {
142
158
  return html`<${TextSizeButton} view=${view} /><${UsageButton} navKey=${navKey} />`;
143
159
  }
160
+ /** Where a page with no control of its own after the shared ones (Log) keeps the room Chat's ⋯ takes: the same box on every tab. */
161
+ export function HeaderSlot() {
162
+ return html`<span class="icon-btn hd-slot" aria-hidden="true"></span>`;
163
+ }
@@ -0,0 +1,202 @@
1
+ // #1629 r4 §1 / r4.1 Q3 (a): an instance's identity card — banner, portrait, tagline, bio — as the host resolves it
2
+ // (GET /api/card/<name>: the person's fields over the agent's). Shown on Details, in View, and folded into the agent's
3
+ // own chat header. Public (/assets/): View's anonymous reader sees cards too.
4
+ //
5
+ // Nothing the agent wrote is markup: the tagline and bio are text nodes (never linkified), the pictures are same-origin
6
+ // <img> elements whose URLs are checked here again, and sizes are class tokens only (card-s | card-m | card-l, heights in
7
+ // app.css) — no style attribute, no CSSOM size (#1300). The size is a viewer's choice: every card's default
8
+ // (`card.size`) and the cards set apart from it (`card.sizeByInstance`, at most 200), synced like the other preferences.
9
+ import { html, useEffect, useState } from "./app-html.js";
10
+ import { t } from "./app-i18n.js";
11
+ import { createStore, useStore } from "./app-store.js";
12
+ import { onPrefsChanged, prefInForce, savePref } from "./app-prefs.js";
13
+ import "./card-strings.js";
14
+
15
+ export const CARD_SIZES = ["s", "m", "l"];
16
+ export const MAX_CARD_SIZES = 200;
17
+ const own = (o, k) => !!o && Object.prototype.hasOwnProperty.call(o, k);
18
+ const readLocal = (k) => { try { return localStorage.getItem(k); } catch { return null; } };
19
+
20
+ /** Bumped whenever a size choice changes (here or on another device), so cards redraw. */
21
+ export const cardSizeStore = createStore({ n: 0 });
22
+ const bump = () => cardSizeStore.set((s) => ({ n: s.n + 1 }));
23
+ onPrefsChanged((changed) => { if (changed.has("card.size") || changed.has("card.sizeByInstance")) bump(); });
24
+
25
+ /** Every card's size unless set apart: the person's choice, else M. */
26
+ export function cardSizeDefault() {
27
+ const f = prefInForce("card.size");
28
+ const v = f ? f.value : readLocal("agend_card_size");
29
+ return CARD_SIZES.includes(v) ? v : "m";
30
+ }
31
+ function overrides() {
32
+ const f = prefInForce("card.sizeByInstance");
33
+ let v = f ? f.value : undefined;
34
+ if (!f) { try { v = JSON.parse(readLocal("agend_card_sizes") || "null"); } catch { v = null; } }
35
+ return v && typeof v === "object" && !Array.isArray(v) ? v : {};
36
+ }
37
+ /** The size `name`'s card is drawn at: its own, else the default. */
38
+ export function cardSizeFor(name) {
39
+ const o = overrides();
40
+ return own(o, name) && CARD_SIZES.includes(o[name]) ? o[name] : cardSizeDefault();
41
+ }
42
+ /**
43
+ * The size for one card (its own control). Choosing the default size removes its entry, so it follows the default
44
+ * again; a new or changed entry goes last, and past 200 the oldest go.
45
+ */
46
+ export function setCardSize(name, size) {
47
+ if (!CARD_SIZES.includes(size)) return;
48
+ const next = Object.create(null);
49
+ for (const [k, v] of Object.entries(overrides())) if (k !== name) Object.defineProperty(next, k, { value: v, enumerable: true, writable: true, configurable: true });
50
+ if (size !== cardSizeDefault()) Object.defineProperty(next, name, { value: size, enumerable: true, writable: true, configurable: true });
51
+ const keys = Object.keys(next);
52
+ for (const k of keys.slice(0, Math.max(0, keys.length - MAX_CARD_SIZES))) delete next[k];
53
+ const plain = {};
54
+ for (const k of Object.keys(next)) Object.defineProperty(plain, k, { value: next[k], enumerable: true, writable: true, configurable: true });
55
+ savePref("card.sizeByInstance", Object.keys(plain).length ? plain : null);
56
+ bump();
57
+ }
58
+ /** Every card's default size (Settings → Appearance). M is the default, so choosing it clears the choice. */
59
+ export function setCardSizeDefault(size) {
60
+ if (!CARD_SIZES.includes(size)) return;
61
+ savePref("card.size", size === "m" ? null : size);
62
+ bump();
63
+ }
64
+
65
+ // ── The card's data: read once per version (the status frame's / View roster's card_v) ──
66
+ /** { <name>: { v, epoch, card } } — card null until read; a failed read keeps the last one. */
67
+ export const cardsStore = createStore({});
68
+ const SRC = /^\/api\/card\/[^/?#\\]+\/(portrait|banner)\?v=[0-9a-f]{32}$/;
69
+ const ID = /^[0-9a-f]{32}$/;
70
+ /** What the host said, checked again: same-origin picture paths of this card, text as text. */
71
+ export function checkCard(name, body) {
72
+ if (!body || typeof body !== "object" || Array.isArray(body)) return null;
73
+ const out = {};
74
+ const base = `/api/card/${encodeURIComponent(name)}/`;
75
+ for (const slot of ["portrait", "banner"]) {
76
+ const p = body[slot];
77
+ if (p && typeof p === "object" && typeof p.src === "string" && SRC.test(p.src) && p.src.startsWith(base + slot + "?") && ID.test(p.id) && (p.by === "human" || p.by === "agent")) out[slot] = { src: p.src, by: p.by };
78
+ }
79
+ // The host's bounds (web-theme.ts TAGLINE_MAX_UNITS / BIO_MAX_UNITS): anything it stores is shown.
80
+ const MAX = { tagline: 600, bio: 2800 };
81
+ for (const k of ["tagline", "bio"]) {
82
+ const v = body[k];
83
+ if (v && typeof v === "object" && typeof v.text === "string" && v.text.length && v.text.length <= MAX[k] && (v.by === "human" || v.by === "agent")) out[k] = { text: v.text, by: v.by };
84
+ }
85
+ return Object.keys(out).length ? out : null;
86
+ }
87
+ // #1889 review: per instance, an epoch that moves whenever the version this page wants changes (a clear included).
88
+ // GET /api/card is not versioned — its body is whatever the host had then — so a read belongs to the epoch that started
89
+ // it: only a read of the current epoch is shared or applied, and an older read never touches its replacement.
90
+ const want = new Map(); // name → { epoch, v }
91
+ const inflight = new Map(); // name → { epoch, promise }
92
+ /** Read `name`'s card if `v` is newer than what this page has (no version: no card). */
93
+ export async function loadCard(name, v, fetcher = (u) => fetch(u, { credentials: "same-origin" })) {
94
+ if (!name) return;
95
+ const target = v || null;
96
+ const cur = want.get(name);
97
+ const epoch = cur && cur.v === target ? cur.epoch : (cur ? cur.epoch : 0) + 1;
98
+ if (!cur || cur.v !== target) want.set(name, { epoch, v: target });
99
+ const have = own(cardsStore.get(), name) ? cardsStore.get()[name] : null;
100
+ if (!target) {
101
+ if (have) cardsStore.set((s) => { const n = { ...s }; delete n[name]; return n; });
102
+ return;
103
+ }
104
+ if (have && have.v === target && have.epoch === epoch) return; // read in this epoch already
105
+ const out = inflight.get(name);
106
+ if (out && out.epoch === epoch) return out.promise; // this epoch's read is on its way
107
+ const current = () => want.get(name)?.epoch === epoch;
108
+ const entry = { epoch, promise: null };
109
+ entry.promise = (async () => {
110
+ try {
111
+ const r = await fetcher(`/api/card/${encodeURIComponent(name)}`);
112
+ if (!r.ok || !current()) return;
113
+ const card = checkCard(name, await r.json());
114
+ if (!current()) return; // superseded or cleared while it was read
115
+ cardsStore.set((s) => ({ ...s, [name]: { v: target, epoch, card } }));
116
+ } catch { /* the next version tries again */ } finally { if (inflight.get(name) === entry) inflight.delete(name); }
117
+ })();
118
+ inflight.set(name, entry);
119
+ return entry.promise;
120
+ }
121
+ /** `name`'s card for version `v` (null while none or not read yet). */
122
+ export function useCard(name, v) {
123
+ const cards = useStore(cardsStore);
124
+ useEffect(() => { loadCard(name, v); }, [name, v]);
125
+ const c = name && own(cards, name) ? cards[name] : null;
126
+ return c && c.v === v ? c.card : c ? c.card : null;
127
+ }
128
+
129
+ /** The S / M / L control (a segmented one): sets this card's size. */
130
+ function SizeControl({ name, size }) {
131
+ return html`<div class="card-size" role="group" aria-label=${t("card.size")}>
132
+ ${CARD_SIZES.map((s) => html`<button key=${s} type="button" class=${`seg-item${s === size ? " active" : ""}`} aria-pressed=${s === size ? "true" : "false"}
133
+ title=${t(`card.size${s.toUpperCase()}Name`)} onClick=${() => setCardSize(name, s)}>${t(`card.size${s.toUpperCase()}`)}</button>`)}
134
+ </div>`;
135
+ }
136
+
137
+ /**
138
+ * The card. `compact`: the chat header's own small size (no size control). Nothing when the card is empty. The
139
+ * tagline and bio say whose they are (title), since a person's field wins over the agent's.
140
+ */
141
+ export function IdentityCard({ name, card, compact = false, sizeControl = !compact }) {
142
+ useStore(cardSizeStore);
143
+ const [broken, setBroken] = useState({});
144
+ if (!card) return null;
145
+ const size = compact ? "c" : cardSizeFor(name);
146
+ const whose = (f) => t(f.by === "human" ? "card.byHuman" : "card.byAgent");
147
+ const pic = (slot, cls, alt) => card[slot] && broken[slot] !== card[slot].src
148
+ ? html`<img class=${cls} src=${card[slot].src} alt=${alt} title=${whose(card[slot])} decoding="async" onError=${() => setBroken({ ...broken, [slot]: card[slot].src })} />` : null;
149
+ return html`<section class=${`id-card card-${size}`} aria-label=${t("card.label", name)}>
150
+ ${pic("banner", "id-banner", "")}
151
+ <div class="id-row">
152
+ ${pic("portrait", "id-portrait", t("card.portraitAlt", name))}
153
+ <div class="id-text">
154
+ ${card.tagline ? html`<p class="id-tagline" title=${whose(card.tagline)}>${card.tagline.text}</p>` : null}
155
+ ${card.bio ? html`<p class="id-bio" title=${whose(card.bio)}>${card.bio.text}</p>` : null}
156
+ </div>
157
+ ${sizeControl ? html`<${SizeControl} name=${name} size=${size} />` : null}
158
+ </div>
159
+ </section>`;
160
+ }
161
+
162
+ // ── A person's picture for the card (View's Edit profile), fitted before it is sent (r4.1 Q3 (b)) ──
163
+ /**
164
+ * `file` fitted to `limit` ({ max_width, max_height, max_bytes }): as it is when it already fits; otherwise, once the
165
+ * person agrees (`ask(w, h, nw, nh)`), drawn smaller and re-encoded as WebP (which also drops its metadata). The server
166
+ * checks whatever is sent like any upload — this is a convenience, never the check. Returns the Blob to send, null when
167
+ * the person declined, or throws when the picture cannot be read or made small enough.
168
+ */
169
+ export async function fitPicture(file, limit, ask, io = browserImageIo) {
170
+ const okType = /^image\/(png|jpeg|webp)$/.test(file.type);
171
+ const bitmap = await io.decode(file);
172
+ const w = bitmap.width, h = bitmap.height;
173
+ if (okType && w <= limit.max_width && h <= limit.max_height && file.size <= limit.max_bytes) return file;
174
+ let scale = Math.min(1, limit.max_width / w, limit.max_height / h);
175
+ let nw = Math.max(1, Math.floor(w * scale)), nh = Math.max(1, Math.floor(h * scale));
176
+ if (!(await ask(w, h, nw, nh))) return null;
177
+ for (let tries = 0; tries < 6; tries++) {
178
+ for (const quality of [0.9, 0.8, 0.7]) {
179
+ const out = await io.encode(bitmap, nw, nh, quality);
180
+ if (out && out.size <= limit.max_bytes) return out;
181
+ }
182
+ scale *= 0.8;
183
+ nw = Math.max(1, Math.floor(w * scale)); nh = Math.max(1, Math.floor(h * scale));
184
+ }
185
+ throw new Error("too_large");
186
+ }
187
+ /** The browser's decoder and encoder: createImageBitmap (no URL, so img-src stays 'self') and a canvas to WebP (or JPEG). */
188
+ export const browserImageIo = {
189
+ decode: (file) => createImageBitmap(file),
190
+ encode: (bitmap, w, h, quality) => new Promise((resolve) => {
191
+ const canvas = document.createElement("canvas");
192
+ canvas.width = w; canvas.height = h;
193
+ const ctx = canvas.getContext("2d");
194
+ if (!ctx) { resolve(null); return; }
195
+ ctx.drawImage(bitmap, 0, 0, w, h);
196
+ // WebP where the browser encodes it; JPEG where it does not (it answers PNG then) — either drops the metadata.
197
+ canvas.toBlob((b) => {
198
+ if (b && b.type === "image/webp") { resolve(b); return; }
199
+ canvas.toBlob((j) => resolve(j && j.type === "image/jpeg" ? j : null), "image/jpeg", quality);
200
+ }, "image/webp", quality);
201
+ }),
202
+ };