@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
package/dist/types.d.ts CHANGED
@@ -345,6 +345,14 @@ export interface WebConfig {
345
345
  echo_to_channel?: boolean;
346
346
  /** Show the AI subscription usage panel on /view and serve /api/ai-usage (default true). */
347
347
  usage_panel?: boolean;
348
+ /**
349
+ * #1937: the largest file the web chat takes, in MB (whole number, 1–500; default 50). Never more than
350
+ * upload_max_total_mb; over the public link a file is at most 95 MB whatever this says (the quick tunnel's limit).
351
+ * Read on each upload: `agend reload` applies a change; an open page uses the new limits after it reloads.
352
+ */
353
+ upload_max_file_mb?: number;
354
+ /** #1937: all files on one web chat message together, in MB (whole number, 1–500; default 100). At most 5 files. */
355
+ upload_max_total_mb?: number;
348
356
  /**
349
357
  * #1218: the instance Log tab (紀錄) — each agent's steps (prompts, replies, tool calls and results, errors) read on
350
358
  * demand from its own transcript, secrets masked. Default false: off, it does no work at all. Turning it on is an
@@ -396,6 +404,19 @@ export interface WebConfig {
396
404
  * instead of it; web.preview: false turns previews off everywhere.
397
405
  */
398
406
  preview_on_public_link?: boolean;
407
+ /**
408
+ * #1629: agents' own web themes and effects. `agent_edit` (default true): false refuses every agent appearance write,
409
+ * fleet-wide; what is stored stays, and people can still reset and lock.
410
+ */
411
+ themes?: {
412
+ agent_edit?: boolean;
413
+ };
414
+ /**
415
+ * #1629 r4.1 Q3 (b): the identity card's upload limits and each agent's picture quota. Defaults: portrait
416
+ * 1024×1536 / 1536 KiB, banner 1600×600 / 768 KiB, quota 8 files / 4 MiB. A value outside its hard bound is clamped
417
+ * (and warned about); a change applies to new uploads only.
418
+ */
419
+ appearance?: import("./web-appearance-limits.js").AppearanceLimitsConfig;
399
420
  }
400
421
  /** `login:` section — remote /login behaviour. */
401
422
  export interface LoginConfig {
@@ -0,0 +1,187 @@
1
+ /** #1629: one session-only app effect owner. Importing this module starts nothing. */
2
+ import { appStore } from "/assets/app-store.js";
3
+ import { navStore } from "/assets/app-nav.js";
4
+ import { onPrefsChanged, prefInForce } from "/assets/app-prefs.js";
5
+ import { resolvedMode, themesStore } from "/assets/app-themes.js";
6
+ import { ownerCssLifecycle, safeMode } from "/assets/owner-css.js";
7
+ import { constrainedDevice } from "/assets/effect-policy.js";
8
+ import { createAppEffectRuntime } from "/assets/effect-app-runtime.js";
9
+ import { createEffectCursor } from "/assets/effect-cursor.js";
10
+ import { createOwnerEffectRegions } from "/assets/effect-owner-regions.js";
11
+ import { chatEffectIdentity } from "/assets/effect-chat-runtime.js";
12
+ import { createEffectTrialController } from "./effect-trial-controller.js";
13
+ import { effectTrialMessage } from "./effect-trial-offer.js";
14
+ import { claimPanel, panelVersion, panelStore, openTrialPanel, closePanel } from "./preview-panel.js";
15
+ const owners = new WeakMap();
16
+ export function getAppEffects(host = window) { const value = owners.get(host); return value && !value.dead && value.api?.ok ? value : null; }
17
+
18
+ /** Boot once with the page's Chat store, including when the first visible panel is Fleet. */
19
+ export function startAppEffects({ store, window: host = window, factories = {} } = {}) {
20
+ const existing = owners.get(host);
21
+ if (existing) { if (!existing.dead) existing.bindStore(store); return getAppEffects(host); }
22
+ if (!chatEffectIdentity(host) || typeof host.AgendAuth?.onSessionEnd !== "function" || typeof host.AgendAuth?.sessionEnded !== "function") return null;
23
+ const doc = host.document, releases = [], ended = new Set(), updates = new Set(), trialUpdates = new Set(); let replyOff = null, replyStore = null, regions = null, cursor = null, cursorStamp = null, trialControl = null, trialIntent = null, trialHint = false, trialSerial = 0, trialStamp = null, chatBinding = null;
24
+ const safe = fn => { try { return fn(); } catch { return null; } };
25
+ const hold = off => { if (entry.dead) safe(() => off?.()); else releases.push(off); };
26
+ const entry = { dead: false, api: null,
27
+ cursorSnapshot: () => cursor?.snapshot() ?? { status: "unavailable", id: null },
28
+ gates: () => context(),
29
+ subscribe(fn) { if (typeof fn !== "function" || entry.dead) return () => {}; updates.add(fn); return () => updates.delete(fn); },
30
+ subscribeTrial(fn) { if (typeof fn !== "function" || entry.dead) return () => {}; trialUpdates.add(fn); return () => trialUpdates.delete(fn); },
31
+ onEnd(fn) { if (typeof fn !== "function") return () => {}; if (entry.dead) { safe(fn); return () => {}; } ended.add(fn); return () => ended.delete(fn); },
32
+ bindStore(value) {
33
+ if (entry.dead || value === replyStore) return;
34
+ safe(() => replyOff?.()); replyOff = null; replyStore = value;
35
+ trialControl?.stop(); chatBinding = null; entry.api?.detachChat();
36
+ if (typeof value?.onAgentReply === "function") {
37
+ const off = value.onAgentReply(event => { if (!entry.dead && replyStore === value) entry.api?.reply(event); });
38
+ if (entry.dead || replyStore !== value) safe(() => off?.()); else replyOff = off;
39
+ }
40
+ },
41
+ canTry(message) {
42
+ const offer = safe(() => effectTrialMessage(message)), nav = navStore.get();
43
+ return !entry.dead && !!chatBinding && safe(() => chatBinding.current()) === true && trialHint && trialControl?.ok === true && !!offer && message.instance === nav.route?.instance && nav.route?.panel === "chat"
44
+ && trialControl.snapshot().phase === "idle" && !trialControl.snapshot().busy && trialAllowed();
45
+ },
46
+ startTrial(message) {
47
+ if (!entry.canTry(message)) return Promise.resolve(false);
48
+ const nav = navStore.get(); trialIntent = { name: message.instance, seq: nav.seq, claim: claimPanel(), handle: null, chat: chatBinding };
49
+ return trialControl.start(message);
50
+ },
51
+ attachChat(options) {
52
+ const nav = navStore.get(), name = options?.name;
53
+ if (entry.dead || !entry.api?.ok || nav.route?.panel !== "chat" || nav.route.instance !== name || typeof options.current !== "function") return null;
54
+ const binding = { current: () => !entry.dead && host.document === doc && navStore.get().seq === nav.seq && navStore.get().route?.panel === "chat" && navStore.get().route?.instance === name && options.current() === true };
55
+ chatBinding = binding; trialControl?.stop();
56
+ if (entry.dead || chatBinding !== binding) return null;
57
+ const handle = entry.api.attachChat({ ...options, current: () => chatBinding === binding && binding.current() });
58
+ if (entry.dead || chatBinding !== binding) { safe(() => handle?.dispose()); return null; }
59
+ if (!handle) chatBinding = null;
60
+ return handle;
61
+ },
62
+ dispose() {
63
+ if (entry.dead) return; entry.dead = true;
64
+ safe(() => trialControl?.dispose()); safe(() => entry.api?.dispose()); safe(() => replyOff?.()); replyOff = null; replyStore = null;
65
+ for (const off of releases.splice(0)) safe(off);
66
+ updates.clear(); trialUpdates.clear(); const listeners = [...ended]; ended.clear(); for (const fn of listeners) safe(fn);
67
+ },
68
+ };
69
+ owners.set(host, entry); // Reentrant setup never constructs another owner.
70
+ let media, coarse, preview;
71
+ function context() {
72
+ let ready = false; try { ready = preview?.availability().ok === true; } catch { /* unavailable */ }
73
+ return { safe: safeMode(), publicLink: doc.body.dataset.publicLink === "1", reducedMotion: media?.matches !== false, saveData: host.navigator?.connection?.saveData === true,
74
+ hidden: doc.visibilityState !== "visible", enabled: typeof host.OffscreenCanvas === "function" && typeof host.Worker === "function" && typeof host.MutationObserver === "function" && typeof host.ResizeObserver === "function",
75
+ show: prefInForce("fx.show")?.value !== false, previewReady: ready,
76
+ optIn: prefInForce("preview.optIn")?.value === true && preview?.optedIn() === true && preview?.never() !== true,
77
+ constrained: constrainedDevice({ memory: host.navigator?.deviceMemory, cores: host.navigator?.hardwareConcurrency, coarse: coarse?.matches === true, width: host.innerWidth }),
78
+ modal: !!doc.querySelector("dialog[open],.menu-list,.pop,[role=alertdialog],.oc-keep,.shell.sb-open"), openInstance: null };
79
+ }
80
+ function trialCurrent(name) {
81
+ const intent = trialIntent, nav = navStore.get();
82
+ return !entry.dead && host.document === doc && !!intent && !!intent.chat && chatBinding === intent.chat && intent.chat.current() === true && intent.name === name && nav.seq === intent.seq && nav.route?.panel === "chat" && nav.route.instance === name
83
+ && (panelVersion() === intent.claim || intent.handle !== null && panelStore.get().open?.handle === intent.handle);
84
+ }
85
+ function trialAllowed() {
86
+ const c = context();
87
+ return !entry.dead && doc.visibilityState === "visible" && doc.hasFocus?.() === true && ownerCssLifecycle.get().state === "off"
88
+ && c.enabled && c.show && c.previewReady && c.optIn && !c.safe && !c.publicLink && !c.reducedMotion && !c.saveData && !c.hidden && !c.modal;
89
+ }
90
+ function mountTrial(handle) {
91
+ const intent = trialIntent; if (!intent || !trialCurrent(handle.instance) || !handle.current() || !Number.isSafeInteger(++trialSerial)) throw Error("trial_unavailable");
92
+ intent.handle = handle;
93
+ const binding = Object.freeze({ key: `trial:${trialSerial}:${handle.reply.trial}`, id: handle.reply.offer.id, run: handle.reply.trial, instance: handle.instance, generation: trialSerial });
94
+ let stage = null, source = null;
95
+ const cleanup = () => {
96
+ safe(() => source?.stop()); safe(() => stage?.dispose());
97
+ if (panelStore.get().open?.handle === handle) closePanel();
98
+ };
99
+ try {
100
+ openTrialPanel({ key: binding.key, instance: handle.instance, binding, handle, ownerCss: () => ownerCssLifecycle.get().state,
101
+ onStage(value) {
102
+ stage = value;
103
+ if (!handle.current()) { cleanup(); handle.stop(); return; }
104
+ source = entry.api.attachTrial({ reply: handle.reply, offer: handle.reply.offer.offer, startedAt: handle.startedAt, stage,
105
+ current: handle.current, onStop: handle.stop });
106
+ if (!source) { cleanup(); handle.stop(); }
107
+ },
108
+ });
109
+ } catch (error) { cleanup(); throw error; }
110
+ return cleanup;
111
+ }
112
+ function notifyTrial() {
113
+ if (entry.dead) return;
114
+ const nav = navStore.get(), next = JSON.stringify([trialHint, safe(() => chatBinding?.current()) === true, trialAllowed(), nav.route?.panel, nav.route?.instance, trialControl?.snapshot()]);
115
+ if (next === trialStamp) return; trialStamp = next;
116
+ for (const fn of [...trialUpdates]) safe(fn);
117
+ }
118
+ const notify = () => { if (!entry.dead) { trialControl?.sync(); notifyTrial(); for (const fn of [...updates]) safe(fn); } };
119
+ const cursorDragging = () => doc.body.classList?.contains("side-resizing") === true || !!doc.querySelector(".chat-split.resizing");
120
+ function syncCursor() {
121
+ if (entry.dead || !cursor) return;
122
+ const c = context();
123
+ cursor.update(entry.api?.cursorSelection?.() ?? null, c.enabled && c.show && c.previewReady && c.optIn && !c.safe && !c.publicLink && !c.reducedMotion && !c.saveData && !c.hidden && !c.modal && !cursorDragging());
124
+ }
125
+ try {
126
+ if (appStore.get().connection === "ended") { entry.dispose(); return null; }
127
+ preview = host.AgendPreview;
128
+ media = host.matchMedia?.("(prefers-reduced-motion: reduce)") ?? { matches: true };
129
+ coarse = host.matchMedia?.("(pointer: coarse)") ?? { matches: false };
130
+ entry.api = (factories.runtime ?? createAppEffectRuntime)({ window: host, current: () => !entry.dead && host.document === doc,
131
+ observe: () => ({ context: context(), ownerCss: ownerCssLifecycle.get().state, mode: resolvedMode() }), onDispose: () => entry.dispose(),
132
+ resolveOwnerTarget: (slot, manifest, proof) => !entry.dead ? regions?.resolve?.(slot, manifest.box, proof) ?? null : null,
133
+ onChange: () => { syncCursor(); notify(); } });
134
+ if (entry.dead || !entry.api.ok) { entry.dispose(); return null; }
135
+ trialControl = (factories.trialController ?? createEffectTrialController)({ window: host, current: trialCurrent, allowed: trialAllowed, onStart: mountTrial, onChange: notify });
136
+ hold(() => trialControl?.dispose());
137
+ if (typeof host.AgendAuth.trialSession === "function") Promise.resolve().then(() => host.AgendAuth.trialSession()).then(value => {
138
+ if (entry.dead) return; trialHint = value?.localAdmin === true && value.epoch === chatEffectIdentity(host)?.epoch; notify();
139
+ }).catch(() => {});
140
+ hold(panelStore.subscribe(() => { trialControl?.sync(); notifyTrial(); }));
141
+ const ownRegions = (factories.regions ?? createOwnerEffectRegions)({ window: host, current: () => !entry.dead && host.document === doc });
142
+ regions = ownRegions?.ok ? ownRegions : null; hold(() => ownRegions?.dispose?.());
143
+ if (entry.dead) return null;
144
+ const ownCursor = safe(() => (factories.cursor ?? createEffectCursor)({ window: host, current: () => !entry.dead && host.document === doc,
145
+ readImage: (selection, signal) => entry.api?.cursorImageSettled?.(selection, signal) ?? Promise.resolve(null),
146
+ onChange(value) { const next = JSON.stringify(value); if (next !== cursorStamp) { cursorStamp = next; notify(); } },
147
+ }));
148
+ cursor = ownCursor?.ok ? ownCursor : null; hold(() => ownCursor?.dispose?.());
149
+ if (entry.dead) return null;
150
+ const sync = () => { if (!entry.dead) { trialControl?.sync(); entry.api.sync(); syncCursor(); notifyTrial(); } };
151
+ let revision = appStore.get().themesRev, prefsRevision = appStore.get().prefsRev, navSeq = navStore.get().seq;
152
+ hold(navStore.subscribe(value => { if (entry.dead || value.seq === navSeq) return; navSeq = value.seq; trialControl?.stop(); chatBinding = null; entry.api.detachChat(); syncCursor(); }));
153
+ hold(appStore.subscribe(value => {
154
+ if (entry.dead) return;
155
+ if (value.connection === "ended") { entry.dispose(); return; }
156
+ const chatChanged = value.themesRev !== revision, ownerChanged = value.prefsRev !== prefsRevision;
157
+ revision = value.themesRev; prefsRevision = value.prefsRev;
158
+ if (chatChanged) entry.api.changedChat();
159
+ if (ownerChanged) entry.api.changedOwner();
160
+ if (!chatChanged && !ownerChanged) sync();
161
+ }));
162
+ hold(ownerCssLifecycle.subscribe(sync)); hold(themesStore.subscribe(sync)); hold(onPrefsChanged(sync));
163
+ if (typeof preview?.onChange === "function") hold(preview.onChange(sync));
164
+ if (typeof host.AgendTheme?.onChange === "function") hold(host.AgendTheme.onChange(sync));
165
+ for (const [target, type] of [[media, "change"], [coarse, "change"], [doc, "visibilitychange"], [host, "resize"], [host.navigator?.connection, "change"]]) {
166
+ if (target?.addEventListener) { target.addEventListener(type, sync); hold(() => target.removeEventListener(type, sync)); }
167
+ }
168
+ if (entry.dead) return null;
169
+ let modal = context().modal, dragging = cursorDragging(), queuedSurface = false;
170
+ // Panel commits can happen after navigation and contain no Chat lease. Reproject fixed owner targets
171
+ // once after stock DOM changes. Body-level effect roots/frames/rules do not schedule themselves.
172
+ const surfaceChanged = () => {
173
+ if (entry.dead || queuedSurface) return; queuedSurface = true;
174
+ Promise.resolve().then(() => { queuedSurface = false; sync(); });
175
+ };
176
+ const barriers = typeof host.MutationObserver === "function" ? new host.MutationObserver(records => {
177
+ if (entry.dead) return;
178
+ const value = context().modal, drag = cursorDragging(); if (value !== modal || drag !== dragging) { modal = value; dragging = drag; sync(); }
179
+ if (Array.isArray(records) && records.some(r => r.target?.closest?.("#app") || r.target === doc.getElementById?.("app"))) surfaceChanged();
180
+ }) : null;
181
+ hold(() => barriers?.disconnect());
182
+ if (entry.dead) return null;
183
+ barriers?.observe(doc.documentElement, { subtree: true, childList: true, attributes: true, attributeFilter: ["class", "open", "hidden", "role"] });
184
+ entry.bindStore(store); sync();
185
+ return getAppEffects(host);
186
+ } catch { entry.dispose(); return null; }
187
+ }
@@ -9,6 +9,7 @@ export const CHAT_COMMANDS = [
9
9
  { name: "compact", arg: "[instructions]" },
10
10
  { name: "clear", arg: "" },
11
11
  { name: "model", arg: "[name]" },
12
+ { name: "backend", arg: "[backend] [model] [effort]" },
12
13
  { name: "effort", arg: "[level]" },
13
14
  { name: "cancel", arg: "" },
14
15
  { name: "btw", arg: "<question>" },
@@ -0,0 +1,42 @@
1
+ /** #1629: static Chat fallback. It requires no preview origin, device consent, Worker or personal identity. */
2
+ import { useLayoutEffect } from "/assets/app-html.js";
3
+ import { appStore } from "/assets/app-store.js";
4
+ import { onPrefsChanged, prefInForce } from "/assets/app-prefs.js";
5
+ import { themesShown, themesStore } from "/assets/app-themes.js";
6
+ import { ownerCssLifecycle } from "/assets/owner-css.js";
7
+ import { chatEffectIdentity } from "/assets/effect-chat-runtime.js";
8
+ import { createChatPosters } from "/assets/effect-posters.js";
9
+
10
+ export function ChatEffectPosters({ name, lease, element }) {
11
+ useLayoutEffect(() => {
12
+ let live = true, api = null;
13
+ const releases = [], host = window;
14
+ const current = () => live && lease.current();
15
+ const cleanup = () => { if (!live) return; live = false; try { api?.dispose?.(); } catch { /* optional */ } for (const off of releases) { try { off?.(); } catch { /* release all */ } } };
16
+ lease.hold(cleanup);
17
+ try {
18
+ const preview = host.AgendPreview, motion = host.matchMedia?.("(prefers-reduced-motion: reduce)") ?? { matches: true };
19
+ const observe = () => {
20
+ let previewReady = false; try { previewReady = preview?.availability().ok === true; } catch { /* fallback */ }
21
+ // This is a surface fallback, not a replacement for an explicit Pause, queued effect or failed live read.
22
+ const playback = chatEffectIdentity(host) && typeof host.Worker === "function" && typeof host.OffscreenCanvas === "function" && previewReady &&
23
+ prefInForce("preview.optIn")?.value === true && preview?.optedIn() === true && preview?.never() !== true && !motion.matches && host.navigator?.connection?.saveData !== true;
24
+ return { shown: themesShown(), fallback: !playback, ownerCss: ownerCssLifecycle.get().state };
25
+ };
26
+ api = createChatPosters({ window: host, name, element: element.current, current, observe });
27
+ if (!api.ok) return cleanup;
28
+ const sync = () => { if (current()) api.sync(); };
29
+ let revision = appStore.get().themesRev;
30
+ releases.push(appStore.subscribe(s => {
31
+ if (!current()) return;
32
+ if (s.connection === "ended") { api.dispose(); return; }
33
+ if (s.themesRev !== revision) { revision = s.themesRev; api.changed(); } else sync();
34
+ }));
35
+ releases.push(ownerCssLifecycle.subscribe(sync)); releases.push(themesStore.subscribe(sync)); releases.push(onPrefsChanged(sync));
36
+ if (typeof preview?.onChange === "function") releases.push(preview.onChange(sync));
37
+ for (const target of [motion, host.navigator?.connection]) if (target?.addEventListener) { target.addEventListener("change", sync); releases.push(() => target.removeEventListener("change", sync)); }
38
+ sync(); return cleanup;
39
+ } catch { cleanup(); return cleanup; }
40
+ }, [name, lease]);
41
+ return null;
42
+ }
@@ -0,0 +1,72 @@
1
+ // #1629: Chat contributes a committed lease to the one app owner; no private pool or server polling.
2
+ import { html, useLayoutEffect, useRef, useState } from "/assets/app-html.js";
3
+ import { register, t } from "/assets/app-i18n.js";
4
+ import { appStore, useStore } from "/assets/app-store.js";
5
+ import { ownerCssLifecycle } from "/assets/owner-css.js";
6
+ import { chatEffectState, chatEffectIdentity } from "/assets/effect-chat-runtime.js";
7
+ import { Dialog } from "/assets/ui-dialog.js";
8
+ import { navigate } from "/assets/app-nav.js";
9
+ import { toast } from "/assets/ui-toast.js";
10
+ import { getAppEffects } from "./app-effects.js";
11
+
12
+ register("chatFx", {
13
+ en: { title: "Effects", loading: "Checking effects…", empty: "No effects in this chat.", failed: "Effects stopped because their resources could not be verified. Reload the page to try again.", reload: "Reload page", pause: "Pause", resume: "Resume", remove: "Remove from chat", removeFailed: "Could not remove this effect. Reload the page to check its state.", css: "Agent foreground effects are paused while your custom CSS is on or cannot be verified.", setting: "Open Appearance settings", running: "Running", starting: "Starting", off: "Stopped", resumeFailed: "This effect cannot resume: the page or its safety history is not ready. Reload the page or choose another effect.", queued: "Waiting for a slot", unknown: "Playback is off or unavailable here.", budget: "Paused to protect page responsiveness. Resume keeps the three-pauses safety limit.", paused: "Paused", low: "Reduced quality" },
14
+ "zh-TW": { title: "效果", loading: "正在確認效果…", empty: "這個聊天沒有放置效果。", failed: "無法確認效果資源,已停止播放。重新載入頁面後再試。", reload: "重新載入頁面", pause: "暫停", resume: "恢復", remove: "從聊天移除", removeFailed: "無法移除效果。請重新載入頁面確認狀態。", css: "自訂 CSS 開啟或無法確認時,代理的前景效果會暫停。", setting: "開啟外觀設定", running: "播放中", starting: "啟動中", off: "已停止", resumeFailed: "頁面或安全紀錄尚未就緒,無法恢復此效果。請重新載入頁面,或選擇其他效果。", queued: "等待空位", unknown: "這裡已關閉播放,或無法播放效果。", budget: "為維持頁面順暢,已暫停效果。恢復仍保留三次暫停的安全上限。", paused: "已暫停", low: "降低畫質" },
15
+ });
16
+
17
+ /** Local read/admin sessions keep controls; public, View, poll and header-only pages have no runtime identity. */
18
+ export function chatEffectsMenuAvailable() { return chatEffectIdentity(window) !== null; }
19
+
20
+ export function ChatEffects({ name, lease, element, store, open, onClose }) {
21
+ const cssLife = useStore(ownerCssLifecycle);
22
+ const runtime = useRef(null), [state, setState] = useState(null), [canWrite, setCanWrite] = useState(false), [busy, setBusy] = useState(false);
23
+ useLayoutEffect(() => {
24
+ let live = true, api = null;
25
+ const releases = [];
26
+ // Effects are optional: a failed setup must release its partial ownership without breaking Chat.
27
+ const cleanup = () => {
28
+ if (!live) return;
29
+ live = false;
30
+ try { api?.dispose?.(); } catch { /* continue releasing partial setup */ }
31
+ for (const off of releases) { try { if (typeof off === "function") off(); } catch { /* release the rest */ } }
32
+ if (runtime.current === api) runtime.current = null;
33
+ };
34
+ lease.hold(cleanup);
35
+ try {
36
+ setBusy(false); onClose(); setCanWrite(false); setState(null);
37
+ const host = window, owner = getAppEffects(host);
38
+ const current = () => live && lease.current();
39
+ if (!owner) { setState({ error: null, rows: [], effects: [], unavailable: true }); return cleanup; }
40
+ releases.push(owner.onEnd(() => { if (current()) setState({ error: "resource_unavailable", rows: [], effects: [], unavailable: true }); }));
41
+ const changed = value => {
42
+ if (!current()) return;
43
+ const g = owner.gates();
44
+ setState({ ...value, unavailable: !g.enabled || !g.show || !g.previewReady || !g.optIn || g.safe || g.publicLink || g.reducedMotion || g.saveData });
45
+ };
46
+ api = owner.attachChat({ name, element: element.current, current,
47
+ observe: () => ({ state: chatEffectState(appStore.get(), name) }), onChange: changed });
48
+ runtime.current = api;
49
+ if (!api?.ok) { setState({ error: null, rows: [], effects: [], unavailable: true }); return cleanup; }
50
+ host.AgendAuth?.canWrite?.().then(value => { if (current()) setCanWrite(value === true); }).catch(() => {});
51
+ api.sync();
52
+ return cleanup;
53
+ } catch {
54
+ cleanup();
55
+ if (lease.current()) setState({ error: "runtime_unavailable", rows: [], effects: [] });
56
+ return cleanup;
57
+ }
58
+ }, [name, lease]);
59
+ const rows = state?.rows ?? [], effects = state?.effects ?? [];
60
+ const css = cssLife.state !== "off" && effects.some(p => p.by !== "human" && p.manifest.layer === "front");
61
+ const remove = async id => { if (busy) return; setBusy(true); const api = runtime.current; const ok = await api?.remove?.(id); if (!lease.current() || runtime.current !== api) return; setBusy(false); if (!ok) toast(t("chatFx.removeFailed"), false); };
62
+ return html`${open ? html`<${Dialog} title=${t("chatFx.title")} onClose=${onClose} busy=${busy}>
63
+ ${state?.error ? html`<p>${t("chatFx.failed")}</p><button type="button" class="btn" onClick=${() => location.reload()}>${t("chatFx.reload")}</button>` : null}
64
+ ${css ? html`<p>${t("chatFx.css")}</p><button type="button" class="btn" onClick=${() => { onClose(); navigate("/settings/appearance"); }}>${t("chatFx.setting")}</button>` : null}
65
+ ${!effects.length && !state?.error ? html`<p>${t(state?.unavailable ? "chatFx.unknown" : state?.loading || !state ? "chatFx.loading" : "chatFx.empty")}</p>` : null}
66
+ ${effects.map(p => { const row = rows.find(r => r.id === p.id), phase = row?.phase; return html`<section key=${p.id}><h3>${p.manifest.title}</h3><p>${t(row?.reason === "paused" || row?.reason === "host_budget" ? "chatFx.paused" : phase === "running" ? "chatFx.running" : phase ? "chatFx.starting" : row?.verdict === "queued" ? "chatFx.queued" : "chatFx.off")}${row?.quality === "low" ? ` · ${t("chatFx.low")}` : ""}</p>
67
+ ${row?.reason === "host_budget" ? html`<p>${t("chatFx.budget")}</p>` : null}
68
+ <button type="button" class="btn" disabled=${busy} onClick=${() => runtime.current?.pause?.(p.id)}>${t("chatFx.pause")}</button>
69
+ <button type="button" class="btn" disabled=${busy} onClick=${() => { if (!runtime.current?.resume?.(p.id)) toast(t("chatFx.resumeFailed"), false); }}>${t("chatFx.resume")}</button>
70
+ ${canWrite ? html`<button type="button" class="btn" disabled=${busy} onClick=${() => remove(p.id)}>${t("chatFx.remove")}</button>` : null}</section>`; })}
71
+ </${Dialog}>` : null}`;
72
+ }
@@ -0,0 +1,58 @@
1
+ // #1629: transient host notifications, never message content or replay/history authority.
2
+ const own = (o, k) => Object.prototype.hasOwnProperty.call(o, k);
3
+ function fields(value, keys, closed = false) {
4
+ try {
5
+ if (!value || typeof value !== "object" || ![Object.prototype, null].includes(Object.getPrototypeOf(value))) return null;
6
+ if (closed && Reflect.ownKeys(value).length !== keys.length) return null;
7
+ const out = {};
8
+ for (const k of keys) { const d = Object.getOwnPropertyDescriptor(value, k); if (!d || !own(d, "value")) return null; out[k] = d.value; }
9
+ return out;
10
+ } catch { return null; }
11
+ }
12
+ const identity = x => !!x && typeof x.boot === "string" && /^[0-9a-f]{1,32}$/.test(x.boot)
13
+ && Number.isSafeInteger(x.id) && x.id > 0 && typeof x.instance === "string" && x.instance.length >= 1 && x.instance.length <= 128;
14
+ const MAX_AGE = 5000, MAX_BOOTS = 64, MAX_PENDING = 64;
15
+ export function createLiveAgentReplies(now) {
16
+ const seen = new Map(), pending = new Map(), listeners = new Set();
17
+ let last = null, revoked = false;
18
+ function time() {
19
+ if (revoked) return null;
20
+ let t; try { t = now(); } catch { t = null; }
21
+ if (typeof t !== "number" || !Number.isFinite(t) || t < 0 || last !== null && t < last) { revoked = true; pending.clear(); return null; }
22
+ last = t;
23
+ for (const [key, item] of pending) if (t - item.at > MAX_AGE) pending.delete(key);
24
+ return t;
25
+ }
26
+ function ingress(extra, t) {
27
+ const e = fields(extra, ["source", "receivedAt"], true);
28
+ return e && e.source === "sse" && typeof e.receivedAt === "number" && Number.isFinite(e.receivedAt) && e.receivedAt >= 0 && e.receivedAt <= t && t - e.receivedAt <= MAX_AGE ? e.receivedAt : null;
29
+ }
30
+ return {
31
+ // Record every accepted ingress, including history/poll, before its message-array merge/eviction.
32
+ message(value, extra) {
33
+ const m = fields(value, ["boot", "id", "instance"]); if (!identity(m)) return;
34
+ const role = fields(value, ["role"])?.role;
35
+ const t = time(); if (t === null) return;
36
+ const key = `${m.boot}:${m.id}`, previous = seen.get(m.boot) || 0;
37
+ pending.delete(key);
38
+ if (!seen.has(m.boot) && seen.size >= MAX_BOOTS) { revoked = true; pending.clear(); return; }
39
+ seen.set(m.boot, Math.max(previous, m.id));
40
+ const at = ingress(extra, t);
41
+ if (m.id <= previous || role !== "agent" || at === null) return;
42
+ // Reconnect replay has no receipts: discard old candidates, never the durable-in-page watermark.
43
+ if (pending.size >= MAX_PENDING) pending.delete(pending.keys().next().value);
44
+ pending.set(key, { instance: m.instance, at });
45
+ },
46
+ receipt(value, extra) {
47
+ const r = fields(value, ["v", "boot", "id", "instance"], true); if (!identity(r) || r.v !== 1) return false;
48
+ const t = time(); if (t === null) return false;
49
+ const at = ingress(extra, t); if (at === null) return false;
50
+ const key = `${r.boot}:${r.id}`, candidate = pending.get(key); pending.delete(key);
51
+ if (!candidate || candidate.instance !== r.instance || at < candidate.at || t - candidate.at > MAX_AGE) return false;
52
+ const event = Object.freeze({ v: 1, boot: r.boot, id: r.id, instance: r.instance });
53
+ for (const fn of [...listeners]) { try { fn(event); } catch { /* one host observer cannot suppress another */ } }
54
+ return true;
55
+ },
56
+ subscribe(fn) { if (typeof fn !== "function") return () => {}; listeners.add(fn); return () => listeners.delete(fn); },
57
+ };
58
+ }
@@ -36,7 +36,7 @@
36
36
  }
37
37
 
38
38
  function link(href, label) {
39
- return '<a href="' + href + '" target="_blank" rel="noopener noreferrer">' + label + "</a>";
39
+ return '<a href="' + href + '" target="_blank" rel="noopener noreferrer" data-trust="link">' + label + "</a>";
40
40
  }
41
41
 
42
42
  // Placeholders survive the later passes untouched (they contain no Markdown characters).
@@ -78,7 +78,9 @@
78
78
  function inline(escaped, store) {
79
79
  var s = escaped;
80
80
  // `code` first: nothing inside it is Markdown.
81
- s = s.replace(/`([^`\n]+)`/g, function (_m, code) { return stash(store, "<code>" + code + "</code>"); });
81
+ // #1812: in a table cell a path may wrap after its separators — the cell keeps words whole, so without these a long
82
+ // path held its whole column wide.
83
+ s = s.replace(/`([^`\n]+)`/g, function (_m, code) { return stash(store, "<code>" + (store.inCell ? code.replace(/\//g, "/<wbr>") : code) + "</code>"); });
82
84
  var known = store.emojis;
83
85
  if (known) s = s.replace(EMOJI_RE, function (m, _a, name, id) { return known(id) ? stash(store, emojiImg(id, name)) : m; });
84
86
  var names = store.mentions;
@@ -180,7 +182,8 @@
180
182
  function renderTable(header, aligns, rows, store) {
181
183
  function cell(tag, text, i) {
182
184
  var a = aligns[i] ? ' class="al-' + aligns[i] + '"' : "";
183
- return "<" + tag + a + ">" + inline(text, store) + "</" + tag + ">";
185
+ store.inCell = true;
186
+ try { return "<" + tag + a + ">" + inline(text, store) + "</" + tag + ">"; } finally { store.inCell = false; }
184
187
  }
185
188
  function row(tag, cells) {
186
189
  var out = "";
@@ -338,6 +341,22 @@
338
341
  * stream — and must show once.
339
342
  */
340
343
  function buttonsRank(b) { return b && b.state !== "open" ? 1 : 0; }
344
+ /**
345
+ * Whether `next` is an older view than `had` of the same buttons: they only go forward (open → ended), and a poll's
346
+ * views (#1823), which change while it is open, by their `rev`.
347
+ */
348
+ function olderButtons(next, had) {
349
+ var d = buttonsRank(next) - buttonsRank(had);
350
+ if (d) return d < 0;
351
+ return !!(next && had && next.poll && had.poll && next.poll.rev < had.poll.rev);
352
+ }
353
+ /** #1823: a poll's time left, counted down by the page every second: m:ss, or h:mm:ss. */
354
+ function pollRemaining(ms) {
355
+ var s = Math.max(0, Math.ceil(ms / 1000));
356
+ var h = Math.floor(s / 3600), m = Math.floor((s % 3600) / 60), r = s % 60;
357
+ var mm = h ? String(m).padStart(2, "0") : String(m);
358
+ return (h ? h + ":" : "") + mm + ":" + String(r).padStart(2, "0");
359
+ }
341
360
  /**
342
361
  * #1266: a reply's buttons changed (one was chosen, or they expired): the message carrying them, by its set id.
343
362
  * Returns the list unchanged when no message carries that set.
@@ -346,7 +365,7 @@
346
365
  if (!Array.isArray(list) || !buttons || typeof buttons.id !== "string") return list;
347
366
  var i = -1;
348
367
  for (var j = list.length - 1; j >= 0; j--) if (list[j] && list[j].buttons && list[j].buttons.id === buttons.id) { i = j; break; }
349
- if (i < 0 || buttonsRank(list[i].buttons) > buttonsRank(buttons)) return list;
368
+ if (i < 0 || olderButtons(buttons, list[i].buttons)) return list;
350
369
  var out = list.slice();
351
370
  out[i] = Object.assign({}, list[i], { buttons: buttons });
352
371
  return out;
@@ -366,7 +385,7 @@
366
385
  var next = nextDeliveryState(had.delivery, m.delivery);
367
386
  if (next !== had.delivery) out[seen[k]] = had = Object.assign({}, had, { delivery: next });
368
387
  // #1266: …and the copy whose buttons got further (open → chosen / expired) shows them.
369
- if (m.buttons && (!had.buttons || buttonsRank(m.buttons) > buttonsRank(had.buttons))) out[seen[k]] = Object.assign({}, had, { buttons: m.buttons });
388
+ if (m.buttons && (!had.buttons || buttonsRank(m.buttons) > buttonsRank(had.buttons) || (buttonsRank(m.buttons) === buttonsRank(had.buttons) && m.buttons.poll && had.buttons.poll && m.buttons.poll.rev > had.buttons.poll.rev))) out[seen[k]] = Object.assign({}, had, { buttons: m.buttons });
370
389
  return;
371
390
  }
372
391
  seen[k] = out.length;
@@ -470,8 +489,32 @@
470
489
  return draft ? failedText + "\n" + draft : failedText;
471
490
  }
472
491
 
473
- /** The upload limits the server enforces (web-upload.ts UPLOAD_LIMITS); checked here first only to say so early. */
474
- var FILE_LIMITS = { maxFileBytes: 10 * 1024 * 1024, maxFiles: 5, maxTotalBytes: 25 * 1024 * 1024 };
492
+ /** The defaults of the upload limits the server enforces (web-upload.ts UPLOAD_LIMITS), for a page that was given none. */
493
+ var FILE_LIMITS = { maxFileBytes: 50 * 1024 * 1024, maxFiles: 5, maxTotalBytes: 100 * 1024 * 1024 };
494
+ /**
495
+ * #1937: the limits in force for this page — the server's (web.upload_max_file_mb / _total_mb, and the public link's
496
+ * own cap), on <body data-upload-limits>. Checked here first only to say so early: the server checks every upload.
497
+ */
498
+ function fileLimits() {
499
+ try {
500
+ var raw = JSON.parse(document.body.dataset.uploadLimits || "null");
501
+ var ok = function (n) { return typeof n === "number" && isFinite(n) && n > 0; };
502
+ if (raw && ok(raw.maxFileBytes) && ok(raw.maxFiles) && ok(raw.maxTotalBytes)) {
503
+ return { maxFileBytes: raw.maxFileBytes, maxFiles: raw.maxFiles, maxTotalBytes: raw.maxTotalBytes, publicLinkCapped: raw.publicLinkCapped === true };
504
+ }
505
+ } catch (e) { /* no page, or no limits on it */ }
506
+ return { maxFileBytes: FILE_LIMITS.maxFileBytes, maxFiles: FILE_LIMITS.maxFiles, maxTotalBytes: FILE_LIMITS.maxTotalBytes, publicLinkCapped: false };
507
+ }
508
+ /** A size in MB as the limits are worded ("50", "2.5") — web-upload.ts mbText. */
509
+ function mbText(bytes) { return String(Math.round((bytes / 1024 / 1024) * 10) / 10); }
510
+ /** #1937: the words for a file checkFiles() refused — a chat string key and the numbers it says, from fileLimits(). */
511
+ function fileRejection(reason) {
512
+ var l = fileLimits();
513
+ if (reason === "too-large") return { key: l.publicLinkCapped ? "chat.file_too_large_public" : "chat.file_too_large", values: [mbText(l.maxFileBytes)] };
514
+ if (reason === "too-many") return { key: "chat.file_too_many", values: [l.maxFiles] };
515
+ if (reason === "too-much") return { key: "chat.file_too_much", values: [mbText(l.maxTotalBytes)] };
516
+ return { key: "chat.file_" + String(reason).replace(/-/g, "_"), values: [] };
517
+ }
475
518
 
476
519
  function formatSize(n) {
477
520
  var b = Number(n) || 0;
@@ -485,6 +528,7 @@
485
528
  * Returns the files kept (pending first, then the accepted new ones) and, for each refused one, why.
486
529
  */
487
530
  function checkFiles(pending, adding) {
531
+ var limits = fileLimits();
488
532
  var kept = (pending || []).slice();
489
533
  var total = 0;
490
534
  kept.forEach(function (f) { total += Number(f.size) || 0; });
@@ -492,9 +536,9 @@
492
536
  (adding || []).forEach(function (f) {
493
537
  var size = Number(f.size) || 0;
494
538
  if (size === 0) rejected.push({ name: f.name, reason: "empty" });
495
- else if (size > FILE_LIMITS.maxFileBytes) rejected.push({ name: f.name, reason: "too-large" });
496
- else if (kept.length >= FILE_LIMITS.maxFiles) rejected.push({ name: f.name, reason: "too-many" });
497
- else if (total + size > FILE_LIMITS.maxTotalBytes) rejected.push({ name: f.name, reason: "too-much" });
539
+ else if (size > limits.maxFileBytes) rejected.push({ name: f.name, reason: "too-large" });
540
+ else if (kept.length >= limits.maxFiles) rejected.push({ name: f.name, reason: "too-many" });
541
+ else if (total + size > limits.maxTotalBytes) rejected.push({ name: f.name, reason: "too-much" });
498
542
  else { kept.push(f); total += size; }
499
543
  });
500
544
  return { kept: kept, rejected: rejected };
@@ -550,8 +594,8 @@
550
594
  if (cards && isHtmlAttachment(a)) after.push('<div class="html-card" data-att="' + a.id + '"></div>');
551
595
  var url = "/ui/file/" + a.id;
552
596
  var kind = inline ? attachmentPreviewType(a) : null;
553
- if (kind === "video") after.push('<div class="att-media"><video class="att-video" controls preload="metadata" src="' + url + '"></video></div>');
554
- else if (kind === "audio") after.push('<div class="att-media"><audio class="att-audio" controls preload="metadata" src="' + url + '"></audio></div>');
597
+ if (kind === "video") after.push('<div data-trust="media" class="att-media"><video class="att-video" controls preload="metadata" src="' + url + '"></video></div>');
598
+ else if (kind === "audio") after.push('<div data-trust="media" class="att-media"><audio class="att-audio" controls preload="metadata" src="' + url + '"></audio></div>');
555
599
  else if (kind) after.push('<div class="text-card" data-att="' + a.id + '" data-type="' + kind + '"></div>');
556
600
  var name = escapeHtml(a.name || "file");
557
601
  if (a.kind === "photo") {
@@ -560,13 +604,14 @@
560
604
  out.push('<a class="att-file" href="' + url + '" download="' + name + '">📎 ' + name + ' <span class="att-size">' + escapeHtml(formatSize(a.size)) + "</span></a>");
561
605
  }
562
606
  });
563
- return (out.length ? '<div class="atts">' + out.join("") + "</div>" : "") + after.join("");
607
+ return (out.length ? '<div data-trust="attachment" class="atts">' + out.join("") + "</div>" : "") + after.join("");
564
608
  }
565
609
 
566
610
  return {
567
611
  renderMarkdown: renderMarkdown, escapeHtml: escapeHtml, mergeMessages: mergeMessages, applyReplyButtons: applyReplyButtons, composerKey: composerKey,
612
+ pollRemaining: pollRemaining,
568
613
  settleFailedSend: settleFailedSend, putBack: putBack,
569
- FILE_LIMITS: FILE_LIMITS, formatSize: formatSize, checkFiles: checkFiles, attachmentsHtml: attachmentsHtml, isHtmlAttachment: isHtmlAttachment,
614
+ FILE_LIMITS: FILE_LIMITS, fileLimits: fileLimits, fileRejection: fileRejection, formatSize: formatSize, checkFiles: checkFiles, attachmentsHtml: attachmentsHtml, isHtmlAttachment: isHtmlAttachment,
570
615
  nextDeliveryState: nextDeliveryState, applyDelivery: applyDelivery, deliveryHtml: deliveryHtml, isBusy: isBusy,
571
616
  isNearBottom: isNearBottom, CODE_FOLD_LINES: CODE_FOLD_LINES, lineCount: lineCount,
572
617
  LONG_PASTE_CHARS: LONG_PASTE_CHARS, isLongPaste: isLongPaste, formatElapsed: formatElapsed,