@songsid/agend 2.3.0-beta.1 → 2.3.0-beta.11

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 (357) hide show
  1. package/dist/agent-cli-instructions.md +5 -2
  2. package/dist/agent-cli.js +43 -7
  3. package/dist/agent-cli.js.map +1 -1
  4. package/dist/agent-endpoint.js +4 -0
  5. package/dist/agent-endpoint.js.map +1 -1
  6. package/dist/agent-file-read.d.ts +14 -6
  7. package/dist/agent-file-read.js +207 -19
  8. package/dist/agent-file-read.js.map +1 -1
  9. package/dist/agent-whats-new.d.ts +11 -0
  10. package/dist/agent-whats-new.js +29 -15
  11. package/dist/agent-whats-new.js.map +1 -1
  12. package/dist/auth-api.d.ts +30 -1
  13. package/dist/auth-api.js +45 -13
  14. package/dist/auth-api.js.map +1 -1
  15. package/dist/authority/authority-state.d.ts +38 -0
  16. package/dist/authority/authority-state.js +114 -0
  17. package/dist/authority/authority-state.js.map +1 -0
  18. package/dist/authority/session-authorizations.d.ts +43 -0
  19. package/dist/authority/session-authorizations.js +102 -0
  20. package/dist/authority/session-authorizations.js.map +1 -0
  21. package/dist/authority/web-authority.d.ts +120 -0
  22. package/dist/authority/web-authority.js +157 -0
  23. package/dist/authority/web-authority.js.map +1 -0
  24. package/dist/backend/claude-code.d.ts +2 -2
  25. package/dist/backend/claude-code.js +10 -5
  26. package/dist/backend/claude-code.js.map +1 -1
  27. package/dist/backend/types.d.ts +14 -0
  28. package/dist/backend/types.js +25 -0
  29. package/dist/backend/types.js.map +1 -1
  30. package/dist/backend-availability.d.ts +120 -0
  31. package/dist/backend-availability.js +396 -0
  32. package/dist/backend-availability.js.map +1 -0
  33. package/dist/backend-command.d.ts +21 -3
  34. package/dist/backend-command.js +23 -4
  35. package/dist/backend-command.js.map +1 -1
  36. package/dist/bundled-skills.d.ts +14 -0
  37. package/dist/bundled-skills.js +29 -0
  38. package/dist/bundled-skills.js.map +1 -0
  39. package/dist/cache-analysis.d.ts +47 -1
  40. package/dist/cache-analysis.js +68 -8
  41. package/dist/cache-analysis.js.map +1 -1
  42. package/dist/cache-ledger.d.ts +16 -3
  43. package/dist/cache-ledger.js +33 -13
  44. package/dist/cache-ledger.js.map +1 -1
  45. package/dist/cache-report-tool.d.ts +17 -0
  46. package/dist/cache-report-tool.js +40 -0
  47. package/dist/cache-report-tool.js.map +1 -0
  48. package/dist/cache-service.d.ts +70 -3
  49. package/dist/cache-service.js +178 -17
  50. package/dist/cache-service.js.map +1 -1
  51. package/dist/channel/adapters/discord.d.ts +7 -0
  52. package/dist/channel/adapters/discord.js +35 -0
  53. package/dist/channel/adapters/discord.js.map +1 -1
  54. package/dist/channel/mcp-tools.js +22 -10
  55. package/dist/channel/mcp-tools.js.map +1 -1
  56. package/dist/channel/types.d.ts +6 -0
  57. package/dist/classic-channel-manager.d.ts +10 -5
  58. package/dist/classic-channel-manager.js +12 -9
  59. package/dist/classic-channel-manager.js.map +1 -1
  60. package/dist/cli.js +130 -31
  61. package/dist/cli.js.map +1 -1
  62. package/dist/config-validator.js +103 -4
  63. package/dist/config-validator.js.map +1 -1
  64. package/dist/daemon.d.ts +7 -0
  65. package/dist/daemon.js +63 -16
  66. package/dist/daemon.js.map +1 -1
  67. package/dist/decision-list.d.ts +25 -0
  68. package/dist/decision-list.js +30 -0
  69. package/dist/decision-list.js.map +1 -0
  70. package/dist/delivery-outbox.d.ts +10 -0
  71. package/dist/delivery-outbox.js +12 -1
  72. package/dist/delivery-outbox.js.map +1 -1
  73. package/dist/fleet-context.d.ts +10 -0
  74. package/dist/fleet-manager.d.ts +417 -11
  75. package/dist/fleet-manager.js +1387 -109
  76. package/dist/fleet-manager.js.map +1 -1
  77. package/dist/fleet-traffic.d.ts +74 -0
  78. package/dist/fleet-traffic.js +196 -0
  79. package/dist/fleet-traffic.js.map +1 -0
  80. package/dist/general-knowledge/skills/credential-profiles/SKILL.md +20 -5
  81. package/dist/general-knowledge/skills/cross-instance-messaging/SKILL.md +6 -1
  82. package/dist/general-knowledge/skills/multi-channel/SKILL.md +3 -2
  83. package/dist/general-knowledge/skills/persona-emoji/SKILL.md +1 -1
  84. package/dist/general-knowledge/skills/web-appearance/SKILL.md +257 -23
  85. package/dist/general-knowledge/skills/web-dashboard/SKILL.md +478 -0
  86. package/dist/general-knowledge/skills/worker-collaboration/SKILL.md +1 -1
  87. package/dist/human-delivery-receipts.d.ts +24 -1
  88. package/dist/human-delivery-receipts.js +64 -11
  89. package/dist/human-delivery-receipts.js.map +1 -1
  90. package/dist/instance-config-impact.js +3 -1
  91. package/dist/instance-config-impact.js.map +1 -1
  92. package/dist/instance-names.d.ts +4 -0
  93. package/dist/instance-names.js +12 -0
  94. package/dist/instance-names.js.map +1 -0
  95. package/dist/instructions.d.ts +12 -1
  96. package/dist/instructions.js +13 -2
  97. package/dist/instructions.js.map +1 -1
  98. package/dist/locale.js +100 -8
  99. package/dist/locale.js.map +1 -1
  100. package/dist/ls-attach-resolve.d.ts +95 -0
  101. package/dist/ls-attach-resolve.js +174 -0
  102. package/dist/ls-attach-resolve.js.map +1 -0
  103. package/dist/memory-hogs.d.ts +27 -0
  104. package/dist/memory-hogs.js +124 -0
  105. package/dist/memory-hogs.js.map +1 -0
  106. package/dist/outbound-handlers.d.ts +10 -0
  107. package/dist/outbound-handlers.js +122 -2
  108. package/dist/outbound-handlers.js.map +1 -1
  109. package/dist/outbound-schemas.d.ts +164 -4
  110. package/dist/outbound-schemas.js +99 -3
  111. package/dist/outbound-schemas.js.map +1 -1
  112. package/dist/public-web-gateway.js +8 -2
  113. package/dist/public-web-gateway.js.map +1 -1
  114. package/dist/public-web-link.d.ts +9 -0
  115. package/dist/public-web-link.js +8 -0
  116. package/dist/public-web-link.js.map +1 -1
  117. package/dist/quickstart-api.d.ts +10 -1
  118. package/dist/quickstart-api.js +12 -4
  119. package/dist/quickstart-api.js.map +1 -1
  120. package/dist/reply-buttons-controller.d.ts +4 -0
  121. package/dist/reply-buttons-controller.js +2 -2
  122. package/dist/reply-buttons-controller.js.map +1 -1
  123. package/dist/restart-progress.d.ts +65 -1
  124. package/dist/restart-progress.js +236 -10
  125. package/dist/restart-progress.js.map +1 -1
  126. package/dist/rollout-index.d.ts +5 -0
  127. package/dist/rollout-index.js +26 -5
  128. package/dist/rollout-index.js.map +1 -1
  129. package/dist/scheduler/scheduler.d.ts +12 -0
  130. package/dist/scheduler/scheduler.js +83 -10
  131. package/dist/scheduler/scheduler.js.map +1 -1
  132. package/dist/service-installer.d.ts +2 -1
  133. package/dist/service-installer.js +18 -3
  134. package/dist/service-installer.js.map +1 -1
  135. package/dist/settings-api.d.ts +19 -0
  136. package/dist/settings-api.js +137 -2
  137. package/dist/settings-api.js.map +1 -1
  138. package/dist/settings-change.js +104 -0
  139. package/dist/settings-change.js.map +1 -1
  140. package/dist/settings-effect.js +17 -1
  141. package/dist/settings-effect.js.map +1 -1
  142. package/dist/tmux-manager.d.ts +5 -0
  143. package/dist/tmux-manager.js +16 -0
  144. package/dist/tmux-manager.js.map +1 -1
  145. package/dist/tool-permissions.d.ts +4 -6
  146. package/dist/tool-permissions.js +53 -5
  147. package/dist/tool-permissions.js.map +1 -1
  148. package/dist/topic-commands.d.ts +7 -1
  149. package/dist/topic-commands.js +18 -4
  150. package/dist/topic-commands.js.map +1 -1
  151. package/dist/types.d.ts +48 -1
  152. package/dist/ui/app-effects.js +77 -31
  153. package/dist/ui/chat-ambient.js +307 -0
  154. package/dist/ui/chat-effect-posters.js +8 -8
  155. package/dist/ui/chat-effects.js +101 -13
  156. package/dist/ui/chat-render.js +21 -3
  157. package/dist/ui/chat-strings.js +59 -6
  158. package/dist/ui/chat-thread.js +159 -21
  159. package/dist/ui/effect-trial-controller.js +13 -8
  160. package/dist/ui/effect-trial-panel.js +19 -16
  161. package/dist/ui/fleet-cache.js +23 -1
  162. package/dist/ui/fleet-canvas-layout.js +111 -0
  163. package/dist/ui/fleet-canvas.js +525 -0
  164. package/dist/ui/fleet-org.js +27 -12
  165. package/dist/ui/fleet-traffic.js +343 -0
  166. package/dist/ui/fleet-usage.js +401 -0
  167. package/dist/ui/panel-chat.js +154 -49
  168. package/dist/ui/panel-details.js +2 -1
  169. package/dist/ui/panel-fleet.js +43 -17
  170. package/dist/ui/panel-settings.js +26 -8
  171. package/dist/ui/preview.js +3 -2
  172. package/dist/ui/settings-appearance.js +42 -18
  173. package/dist/ui/settings-catalog.js +92 -0
  174. package/dist/ui/settings-dialogs.js +40 -12
  175. package/dist/ui/settings-effects.js +4 -3
  176. package/dist/ui/settings-guests.js +117 -0
  177. package/dist/ui/settings-learn.js +25 -0
  178. package/dist/ui/settings-model.js +15 -6
  179. package/dist/ui/settings-mods.js +23 -9
  180. package/dist/ui/settings-owner-effects.js +9 -9
  181. package/dist/ui/settings-strings.js +120 -42
  182. package/dist/ui/settings-web-authority.js +91 -0
  183. package/dist/ui/shared/agend-auth.js +1 -1
  184. package/dist/ui/shared/app-i18n.js +4 -2
  185. package/dist/ui/shared/app-prefs.js +5 -4
  186. package/dist/ui/shared/app-route.js +19 -1
  187. package/dist/ui/shared/app-session.js +2 -0
  188. package/dist/ui/shared/app-shell.js +92 -27
  189. package/dist/ui/shared/app-store.js +26 -1
  190. package/dist/ui/shared/app-stream.js +3 -1
  191. package/dist/ui/shared/app-themes.js +24 -6
  192. package/dist/ui/shared/app.css +365 -17
  193. package/dist/ui/shared/app.js +21 -14
  194. package/dist/ui/shared/card-strings.js +4 -0
  195. package/dist/ui/shared/effect-app-runtime.js +68 -19
  196. package/dist/ui/shared/effect-chat-runtime.js +16 -3
  197. package/dist/ui/shared/effect-frame.js +26 -7
  198. package/dist/ui/shared/effect-geometry.js +24 -9
  199. package/dist/ui/shared/effect-policy.js +21 -7
  200. package/dist/ui/shared/effect-profiler.js +31 -9
  201. package/dist/ui/shared/effect-registry.js +87 -48
  202. package/dist/ui/shared/effect-stock-snapshot.js +9 -0
  203. package/dist/ui/shared/effect-stock-trust.js +62 -10
  204. package/dist/ui/shared/effect-trial-shadow.js +62 -0
  205. package/dist/ui/shared/effect-trial-source.js +1 -1
  206. package/dist/ui/shared/effect-trial-stage.js +54 -31
  207. package/dist/ui/shared/effect-trial.css +40 -0
  208. package/dist/ui/shared/fx-motion.js +67 -0
  209. package/dist/ui/shared/identity-card.js +75 -4
  210. package/dist/ui/shared/identity-color.js +3 -3
  211. package/dist/ui/shared/instance-nav.js +194 -28
  212. package/dist/ui/shared/panel-view.js +24 -4
  213. package/dist/ui/shared/tokens.css +9 -3
  214. package/dist/ui/shared/ui-icons.js +1 -0
  215. package/dist/ui/shared/usage-dialog.js +22 -2
  216. package/dist/ui/shared/view-strings.js +68 -2
  217. package/dist/ui/shared/web-authorize.js +151 -0
  218. package/dist/ui/signin.html +1 -1
  219. package/dist/ui/web-terminal/terminal.css +4 -4
  220. package/dist/usage/providers.d.ts +28 -0
  221. package/dist/usage/providers.js +126 -36
  222. package/dist/usage/providers.js.map +1 -1
  223. package/dist/usage-report.d.ts +158 -0
  224. package/dist/usage-report.js +226 -0
  225. package/dist/usage-report.js.map +1 -0
  226. package/dist/usage-rollup.d.ts +51 -0
  227. package/dist/usage-rollup.js +168 -0
  228. package/dist/usage-rollup.js.map +1 -0
  229. package/dist/view-api.d.ts +39 -0
  230. package/dist/view-api.js +65 -4
  231. package/dist/view-api.js.map +1 -1
  232. package/dist/web-ambient.d.ts +72 -0
  233. package/dist/web-ambient.js +161 -0
  234. package/dist/web-ambient.js.map +1 -0
  235. package/dist/web-api.d.ts +69 -5
  236. package/dist/web-api.js +362 -45
  237. package/dist/web-api.js.map +1 -1
  238. package/dist/web-appearance-policy.d.ts +4 -6
  239. package/dist/web-appearance-policy.js +5 -12
  240. package/dist/web-appearance-policy.js.map +1 -1
  241. package/dist/web-appearance-tools.d.ts +3 -0
  242. package/dist/web-appearance-tools.js +22 -3
  243. package/dist/web-appearance-tools.js.map +1 -1
  244. package/dist/web-appearance-view.d.ts +27 -2
  245. package/dist/web-appearance-view.js +129 -14
  246. package/dist/web-appearance-view.js.map +1 -1
  247. package/dist/web-appearance.d.ts +17 -3
  248. package/dist/web-appearance.js +82 -7
  249. package/dist/web-appearance.js.map +1 -1
  250. package/dist/web-asset-parts.d.ts +5 -10
  251. package/dist/web-asset-parts.js +3 -3
  252. package/dist/web-asset-parts.js.map +1 -1
  253. package/dist/web-auth.js +3 -1
  254. package/dist/web-auth.js.map +1 -1
  255. package/dist/web-card.d.ts +14 -0
  256. package/dist/web-card.js +40 -6
  257. package/dist/web-card.js.map +1 -1
  258. package/dist/web-channel-echo.d.ts +7 -1
  259. package/dist/web-channel-echo.js +6 -2
  260. package/dist/web-channel-echo.js.map +1 -1
  261. package/dist/web-chat-history.d.ts +50 -0
  262. package/dist/web-chat-history.js +87 -5
  263. package/dist/web-chat-history.js.map +1 -1
  264. package/dist/web-commands.d.ts +1 -0
  265. package/dist/web-effect-bootstrap.d.ts +12 -0
  266. package/dist/web-effect-bootstrap.js +49 -0
  267. package/dist/web-effect-bootstrap.js.map +1 -0
  268. package/dist/web-effect-bundle.js +46 -1
  269. package/dist/web-effect-bundle.js.map +1 -1
  270. package/dist/web-effect-playback.d.ts +30 -0
  271. package/dist/web-effect-playback.js +71 -0
  272. package/dist/web-effect-playback.js.map +1 -0
  273. package/dist/web-effect-profiles.d.ts +4 -4
  274. package/dist/web-effect-service.d.ts +11 -4
  275. package/dist/web-effect-service.js +28 -5
  276. package/dist/web-effect-service.js.map +1 -1
  277. package/dist/web-effect-tools.d.ts +1 -0
  278. package/dist/web-effect-tools.js +8 -5
  279. package/dist/web-effect-tools.js.map +1 -1
  280. package/dist/web-effect-trials.d.ts +4 -4
  281. package/dist/web-effect-trials.js +6 -6
  282. package/dist/web-effect-trials.js.map +1 -1
  283. package/dist/web-effect-upload.d.ts +2 -4
  284. package/dist/web-effect-upload.js +3 -8
  285. package/dist/web-effect-upload.js.map +1 -1
  286. package/dist/web-effect-worker.d.ts +1 -1
  287. package/dist/web-effect-worker.js +2 -2
  288. package/dist/web-effect-worker.js.map +1 -1
  289. package/dist/web-effect.d.ts +14 -0
  290. package/dist/web-effect.js +21 -0
  291. package/dist/web-effect.js.map +1 -1
  292. package/dist/web-guest-access.d.ts +125 -0
  293. package/dist/web-guest-access.js +234 -0
  294. package/dist/web-guest-access.js.map +1 -0
  295. package/dist/web-guest-rooms.d.ts +82 -0
  296. package/dist/web-guest-rooms.js +135 -0
  297. package/dist/web-guest-rooms.js.map +1 -0
  298. package/dist/web-identity-color.d.ts +1 -1
  299. package/dist/web-identity-color.js +2 -2
  300. package/dist/web-identity-color.js.map +1 -1
  301. package/dist/web-identity-palette.d.ts +1 -1
  302. package/dist/web-identity-palette.js +2 -2
  303. package/dist/web-identity-palette.js.map +1 -1
  304. package/dist/web-image-meta.d.ts +13 -0
  305. package/dist/web-image-meta.js +62 -0
  306. package/dist/web-image-meta.js.map +1 -1
  307. package/dist/web-login.d.ts +47 -0
  308. package/dist/web-login.js +115 -0
  309. package/dist/web-login.js.map +1 -1
  310. package/dist/web-mod-agent.d.ts +1 -1
  311. package/dist/web-mod-agent.js +12 -8
  312. package/dist/web-mod-agent.js.map +1 -1
  313. package/dist/web-mod-bundle.d.ts +14 -1
  314. package/dist/web-mod-bundle.js +68 -3
  315. package/dist/web-mod-bundle.js.map +1 -1
  316. package/dist/web-mod-export.d.ts +9 -0
  317. package/dist/web-mod-export.js +21 -0
  318. package/dist/web-mod-export.js.map +1 -1
  319. package/dist/web-mod-store.d.ts +7 -1
  320. package/dist/web-mod-store.js +21 -7
  321. package/dist/web-mod-store.js.map +1 -1
  322. package/dist/web-prefs.d.ts +14 -0
  323. package/dist/web-prefs.js +24 -4
  324. package/dist/web-prefs.js.map +1 -1
  325. package/dist/web-preview.d.ts +6 -2
  326. package/dist/web-preview.js +4 -2
  327. package/dist/web-preview.js.map +1 -1
  328. package/dist/web-returning-device.d.ts +41 -0
  329. package/dist/web-returning-device.js +57 -5
  330. package/dist/web-returning-device.js.map +1 -1
  331. package/dist/web-session-authorization.d.ts +132 -0
  332. package/dist/web-session-authorization.js +193 -0
  333. package/dist/web-session-authorization.js.map +1 -0
  334. package/dist/web-session.d.ts +70 -10
  335. package/dist/web-session.js +137 -22
  336. package/dist/web-session.js.map +1 -1
  337. package/dist/web-settings-patch.d.ts +49 -2
  338. package/dist/web-settings-patch.js +145 -1
  339. package/dist/web-settings-patch.js.map +1 -1
  340. package/dist/web-settings-tools.d.ts +89 -0
  341. package/dist/web-settings-tools.js +270 -0
  342. package/dist/web-settings-tools.js.map +1 -0
  343. package/dist/web-shell-routes.d.ts +1 -1
  344. package/dist/web-shell-routes.js +1 -1
  345. package/dist/web-shell-routes.js.map +1 -1
  346. package/dist/web-theme-store.d.ts +39 -0
  347. package/dist/web-theme-store.js +122 -11
  348. package/dist/web-theme-store.js.map +1 -1
  349. package/dist/web-theme.d.ts +2 -0
  350. package/dist/web-theme.js +10 -1
  351. package/dist/web-theme.js.map +1 -1
  352. package/dist/web-upload.d.ts +13 -0
  353. package/dist/web-upload.js +39 -3
  354. package/dist/web-upload.js.map +1 -1
  355. package/dist/workflow-templates/default.md +1 -1
  356. package/package.json +1 -1
  357. package/templates/systemd.service.ejs +8 -0
@@ -1,7 +1,7 @@
1
1
  // #1408 §3: the app's routes on the client. The same table as the server's classifier (src/web-shell-routes.ts;
2
2
  // a test runs both over one list of cases). Pure: no DOM, no history.
3
3
 
4
- export const FLEET_TABS = ["tasks", "schedules", "teams", "org", "cache", "config"];
4
+ export const FLEET_TABS = ["tasks", "schedules", "teams", "org", "cache", "usage", "config"];
5
5
  export const SETTINGS_SECTIONS = ["device", "appearance", "agents", "bots", "classic", "general", "web", "advanced"]; // #1604: device first
6
6
  const MAX_INSTANCE_NAME = 128;
7
7
  const PATH_SEPARATOR_OR_CONTROL = /[/\\\u0000-\u001f\u007f]/;
@@ -50,6 +50,24 @@ export function viewPath(instance) { return instance ? `/view/${encodeURICompone
50
50
  export function detailsPath(instance) { return `/ui/fleet/agent/${encodeURIComponent(instance)}`; }
51
51
  export function logPath(instance) { return `/ui/log/${encodeURIComponent(instance)}`; }
52
52
  export function fleetPath(tab) { return tab && tab !== "tasks" ? `/ui/fleet/${tab}` : "/ui/fleet"; }
53
+ /** #2183: this device's last Fleet tab (Fleet remembers it; the Org chart the first time). */
54
+ export const LAST_FLEET_TAB_KEY = "agend_last_fleet_tab";
55
+ export function lastFleetTab() {
56
+ let tab = null;
57
+ try { tab = localStorage.getItem(LAST_FLEET_TAB_KEY); } catch { /* storage blocked: the default */ }
58
+ return FLEET_TABS.includes(tab) ? tab : "org";
59
+ }
60
+ /**
61
+ * #2183: where Fleet (the sidebar's entry, the phone's tab) goes — on a Fleet tab, that tab (the route, not storage: the
62
+ * panel stores it in an effect after this render, and a storage write re-renders nothing); anywhere else, the last one.
63
+ */
64
+ export function fleetEntryPath(route) {
65
+ return fleetPath(route && route.panel === "fleet" ? route.tab : lastFleetTab()); // parseRoute names only FLEET_TABS
66
+ }
67
+ export function rememberFleetTab(tab) {
68
+ if (!FLEET_TABS.includes(tab)) return;
69
+ try { localStorage.setItem(LAST_FLEET_TAB_KEY, tab); } catch { /* this page only */ }
70
+ }
53
71
  export const NEEDS_PATH = "/ui/needs";
54
72
  export function settingsPath(section) { return section && section !== "agents" ? `/settings/${section}` : "/settings"; }
55
73
  export function routePath(route) {
@@ -4,6 +4,7 @@
4
4
  import { html, useEffect, useRef, useState } from "./app-html.js";
5
5
  import { Icon } from "./ui-icons.js";
6
6
  import { t, lang } from "./app-i18n.js";
7
+ import { AuthorizeChip } from "./web-authorize.js";
7
8
 
8
9
  async function getJson(url) {
9
10
  try {
@@ -53,6 +54,7 @@ export function SessionMenu() {
53
54
  ${!me ? html`<h3>${t("app.signedOut")}</h3><a class="btn" href=${`/signin?next=${encodeURIComponent(location.pathname)}`}>${t("app.signIn")}</a>` : html`
54
55
  <h3>${t("app.signedIn")} · ${me.label}</h3>
55
56
  <p class="sub">${t("app.expires", clock(me.expiresAt), span(me.idleMs))}</p>
57
+ <${AuthorizeChip} />
56
58
  ${rows.length > 1 ? html`<h3>${t("app.devices")}</h3><ul class="devices">${rows.map(s => html`<li key=${s.handle}>
57
59
  <span class="grow"><span class="name">${s.label}${s.current ? ` · ${t("app.thisDevice")}` : ""}</span><span class="when">${t("app.lastActive", ago(s.lastSeen))}</span></span>
58
60
  ${s.current ? null : html`<button type="button" class="btn btn-sm danger" onClick=${() => act("DELETE", `/auth/sessions/${encodeURIComponent(s.handle)}`, refresh)}>${t("app.signOut")}</button>`}</li>`)}</ul>` : null}
@@ -7,9 +7,9 @@ import { Toasts } from "./ui-toast.js";
7
7
  import { ConfirmHost } from "./ui-confirm.js";
8
8
  import { ColorDialogHost } from "./identity-color.js";
9
9
  import { t, lang, setLang, onLang } from "./app-i18n.js";
10
- import { appStore, createStore, useStore } from "./app-store.js";
10
+ import { appStore, createStore, guestBadge, guestPage, useStore } from "./app-store.js";
11
11
  import { navStore } from "./app-nav.js";
12
- import { chatPath, viewPath, settingsPath, routeKey, NEEDS_PATH, detailsPath, fleetPath } from "./app-route.js";
12
+ import { chatPath, viewPath, settingsPath, routeKey, NEEDS_PATH, fleetEntryPath } from "./app-route.js";
13
13
  import { SessionMenu } from "./app-session.js";
14
14
  import { savePref } from "./app-prefs.js";
15
15
  import { ErrorState, Skeleton } from "./ui-states.js";
@@ -39,6 +39,25 @@ let newInstanceOpener = null;
39
39
  export function setNewInstanceOpener(fn) { newInstanceOpener = fn; }
40
40
  export function requestNewInstance() { if (newInstanceOpener) newInstanceOpener(); }
41
41
 
42
+ /**
43
+ * #2074: whether this page's session may make an agent — an admin's (/auth/session's tier); a guest's create is refused.
44
+ * Asked once per page; a session that ends takes the buttons away. A display hint only: the create route decides.
45
+ */
46
+ export const canCreateStore = createStore({ value: false });
47
+ let canCreateOf = null; // the AgendAuth asked (one per page)
48
+ export function useCanCreate() {
49
+ const s = useStore(canCreateStore);
50
+ useEffect(() => {
51
+ const auth = typeof window !== "undefined" ? window.AgendAuth : undefined;
52
+ if (!auth || typeof auth.canWrite !== "function" || canCreateOf === auth) return;
53
+ canCreateOf = auth;
54
+ canCreateStore.set({ value: false });
55
+ Promise.resolve(auth.canWrite()).then((v) => { if (canCreateOf === auth && !auth.sessionEnded?.()) canCreateStore.set({ value: v === true }); }, () => {});
56
+ auth.onSessionEnd?.(() => { if (canCreateOf === auth) canCreateStore.set({ value: false }); });
57
+ }, []);
58
+ return s.value;
59
+ }
60
+
42
61
  /** An app-level dialog (New instance): rendered by the shell until closed, whatever panel is showing. */
43
62
  export function showDialog(Component, props = {}) { shellStore.set({ dialog: { Component, props } }); }
44
63
  export function closeDialog() { shellStore.set({ dialog: null }); }
@@ -247,9 +266,8 @@ export function signInHref() {
247
266
  function Sidebar({ route, onNewInstance, viewOnly }) {
248
267
  const app = useStore(appStore);
249
268
  const shell = useStore(shellStore);
250
- // #1675: the Fleet panel is "Fleet overview"'s (one current row); the "Fleet" row is current on an instance's Details.
251
- const on = (panel) => !!route && (panel === "fleet" ? route.panel === "details" : route.panel === panel);
252
- const openInstance = openInstanceOf(route);
269
+ const canCreate = useCanCreate();
270
+ const on = (panel) => !!route && route.panel === panel;
253
271
  // A count badge is hidden at 0; its number is also in the link's accessible name.
254
272
  const navLink = (panel, href, icon, label, count = 0) => html`<a class=${`side-row${on(panel) ? " active" : ""}`} href=${href}
255
273
  aria-current=${on(panel) ? "page" : undefined} aria-label=${count ? `${label} (${t("app.needsCount", count)})` : undefined} onClick=${closeDrawer}>
@@ -271,22 +289,37 @@ function Sidebar({ route, onNewInstance, viewOnly }) {
271
289
  <${SidebarResize} />
272
290
  </aside>`;
273
291
  }
274
- // #1675: the whole fleet, on its Org chart (#1389), from anywhere — top left, so at the top of a phone's drawer too. The
275
- // "Fleet" row under it goes to the open instance's Details (#1523 N2); this one is the Fleet panel, any of its tabs.
292
+ // #1920 phase 3: a guest's sidebar — its own agents to pick from, who it is signed in as, and its session; no fleet,
293
+ // Needs you, View, Settings, new agent or widget (each is an admin's, and the server refuses a guest them anyway).
294
+ if (guestPage()) {
295
+ return html`<aside id="sidebar" class="sidebar" aria-label=${t("app.menu")}>
296
+ <div class="side-head" data-trust="navigation">
297
+ <a class="brand" href="/ui" onClick=${closeDrawer}>${t("app.brand")}</a>
298
+ <button type="button" class="icon-btn side-collapse" onClick=${toggleSidebar} aria-label=${narrow() ? t("app.closeMenu") : t("app.collapse")} title=${narrow() ? t("app.closeMenu") : t("app.collapse")} aria-controls="sidebar"><${Icon} name="sidebar" /></button>
299
+ </div>
300
+ <${InstanceNav} route=${route} items=${app.instances} loaded=${app.ready !== false} exec=${app.exec} awaiting=${app.awaiting} onPick=${closeDrawer} />
301
+ <div class="side-foot" data-trust="navigation">
302
+ <${GuestChip} />
303
+ <${SessionMenu} />
304
+ </div>
305
+ <${SidebarResize} />
306
+ </aside>`;
307
+ }
308
+ // #1675: the whole fleet, from anywhere — top left, so at the top of a phone's drawer too. #2183 (the user's call): the
309
+ // one Fleet entry ("Fleet" / "Fleet 概覽"), on the tab this device used last (the Org chart the first time); an agent's
310
+ // View, Log and Details are its header's tabs, so the sidebar has no "Fleet" (Details) or "View" row of its own.
276
311
  const onFleet = !!route && route.panel === "fleet";
277
- const overview = html`<a class=${`side-row side-overview${onFleet ? " active" : ""}`} href=${fleetPath("org")}
312
+ const overview = html`<a class=${`side-row side-overview${onFleet ? " active" : ""}`} href=${fleetEntryPath(route)}
278
313
  aria-current=${onFleet ? "page" : undefined} onClick=${closeDrawer}><${Icon} name="org" /><span class="grow">${t("app.fleetOverview")}</span></a>`;
279
314
  return html`<aside id="sidebar" class="sidebar" aria-label=${t("app.menu")}>
280
315
  <div class="side-head" data-trust="navigation">
281
316
  <a class="brand" href="/ui" onClick=${closeDrawer}>${t("app.brand")}</a>
282
317
  <button type="button" class="icon-btn side-collapse" onClick=${toggleSidebar} aria-label=${narrow() ? t("app.closeMenu") : t("app.collapse")} title=${narrow() ? t("app.closeMenu") : t("app.collapse")} aria-controls="sidebar"><${Icon} name="sidebar" /></button>
283
- <button type="button" class="icon-btn" onClick=${onNewInstance} aria-label=${t("app.newInstance")} title=${t("app.newInstance")}><${Icon} name="plus" /></button>
284
318
  </div>
285
319
  <nav data-trust="navigation" class="side-nav" aria-label=${t("app.menu")}>
286
320
  ${overview}
287
321
  ${navLink("needs", NEEDS_PATH, "inbox", t("app.needsNav"), needsCount)}
288
- ${navLink("fleet", openInstance ? detailsPath(openInstance) : "/ui/fleet", "fleet", t("app.fleet"))}
289
- ${navLink("view", "/view", "view", t("app.view"))}
322
+ ${canCreate && onNewInstance ? html`<button type="button" class="side-row side-new" onClick=${onNewInstance}><${Icon} name="plus" /><span class="grow">${t("app.newAgent")}</span></button>` : null}
290
323
  </nav>
291
324
  ${shell.side ? html`<${shell.side.Component} />` : html`<${InstanceNav} route=${route} items=${app.instances} loaded=${app.ready !== false}
292
325
  exec=${app.exec} awaiting=${app.awaiting} onPick=${closeDrawer} />`}
@@ -299,6 +332,17 @@ function Sidebar({ route, onNewInstance, viewOnly }) {
299
332
  </aside>`;
300
333
  }
301
334
 
335
+ /** #1920 phase 3: "Guest · <name> (<platform>)" — who this guest's page is signed in as; what a guest sees, in its title. */
336
+ const PLATFORM_NAMES = { discord: "Discord", telegram: "Telegram" };
337
+ function GuestChip() {
338
+ const { name, platform } = guestBadge();
339
+ const where = PLATFORM_NAMES[platform] || platform;
340
+ const label = name ? (where ? t("app.guestAsOn", name, where) : t("app.guestAs", name)) : t("app.guestChip");
341
+ // Inside .side-foot (a marked, stock-trusted element): no trust marker of its own — effect-stock-trust's closed inventory
342
+ // refuses any element it does not know, which would hide a guest's static effect posters (#2101 review).
343
+ return html`<p class="guest-chip" title=${t("app.guestHint")}><${Icon} name="user" size=${14} /><span class="grow">${label}</span></p>`;
344
+ }
345
+
302
346
  /** #1629 r4-4: the person's own sidebar widget (their synced preference), with the fleet's counts. */
303
347
  function SidebarWidget({ app }) {
304
348
  useStore(sidebarWidgetStore);
@@ -347,20 +391,23 @@ function PrefsPopover() {
347
391
  * The header every panel shows: ☰ (phone, or a collapsed sidebar), its title and status, then its actions. `nav`
348
392
  * (#1523 N2): an instance page's view switch — beside the title on a wide screen, a row of tabs under it on a phone.
349
393
  */
350
- export function PanelHeader({ title, sub, children, headingRef, nav }) {
394
+ export function PanelHeader({ title, sub, children, headingRef, nav, notice }) {
351
395
  const shell = useStore(shellStore);
396
+ const canCreate = useCanCreate() && !!newInstanceOpener;
352
397
  return html`<header data-trust="header" class=${`panel-head${shell.collapsed ? " collapsed" : ""}`}>
353
398
  <button type="button" id="sbOpen" class="icon-btn sb-open" onClick=${() => (narrow() ? openDrawer() : toggleSidebar())}
354
399
  aria-label=${narrow() ? t("app.openMenu") : t("app.expand")} title=${narrow() ? t("app.openMenu") : t("app.expand")} aria-controls="sidebar" aria-expanded=${shell.drawer ? "true" : "false"}><${Icon} name="menu" /></button>
400
+ ${canCreate && shell.collapsed ? html`<button type="button" class="icon-btn sb-new" onClick=${requestNewInstance} aria-label=${t("app.newAgent")} title=${t("app.newAgent")}><${Icon} name="plus" /></button>` : null}
355
401
  <div class="panel-title"><h1 ref=${headingRef} tabindex="-1">${title}</h1>${sub ? html`<div class="panel-sub">${sub}</div>` : null}</div>
356
402
  ${nav || null}
357
403
  <div class="panel-actions">${children}</div>
404
+ ${notice || null}
358
405
  </header>`;
359
406
  }
360
407
 
361
408
  /**
362
- * #1523 N2: the instance a page is about — Chat, View, Log (#1218) or Details of it — or null. With one open, Fleet goes to its
363
- * Details (the user's complaint: Fleet always landed on Tasks), and Chat and View keep it; with none, as before.
409
+ * #1523 N2: the instance a page is about — Chat, View, Log (#1218) or Details of it — or null. #2183: with one open, the
410
+ * phone's Chat tab is current and goes to its chat.
364
411
  */
365
412
  export function openInstanceOf(route) {
366
413
  return route && (route.panel === "chat" || route.panel === "view" || route.panel === "log" || route.panel === "details") && route.instance ? route.instance : null;
@@ -371,24 +418,28 @@ function BottomTabs({ route, viewOnly }) {
371
418
  let last = null;
372
419
  try { last = localStorage.getItem("agend_last_instance"); } catch { /* none */ }
373
420
  const open = openInstanceOf(route);
374
- const chatHref = open && route.panel !== "view" ? chatPath(open) : route && route.panel === "chat" && route.instance ? chatPath(route.instance) : last ? chatPath(last) : "/ui";
421
+ // #2183: Chat stands for the open agent's pages (its View, Log and Details are the tabs under its header), so it goes to that agent's chat.
422
+ const chatHref = open ? chatPath(open) : last ? chatPath(last) : "/ui";
375
423
  const anyAwaiting = Object.keys(app.awaiting).length;
376
424
  const tab = (href, icon, label, active, badge, badgeLabel) => html`<a class=${`tab${active ? " active" : ""}`} href=${href} aria-current=${active ? "page" : undefined}>
377
425
  <${Icon} name=${icon} size=${20} /><span>${label}</span>${badge ? html`<span class="tab-badge" aria-label=${badgeLabel || t("app.needsYou")}>${badge}</span>` : null}</a>`;
378
- let lastView = null;
379
- try { lastView = localStorage.getItem("agend_last_view"); } catch { /* none */ }
380
- const viewHref = route && (route.panel === "view" || route.panel === "log" || route.panel === "details") && route.instance ? viewPath(route.instance) : viewPath(lastView);
381
426
  if (viewOnly) {
427
+ let lastView = null;
428
+ try { lastView = localStorage.getItem("agend_last_view"); } catch { /* none */ }
429
+ const viewHref = route && route.panel === "view" && route.instance ? viewPath(route.instance) : viewPath(lastView);
382
430
  return html`<nav data-trust="navigation" class="tabs" aria-label=${t("app.menu")}>
383
431
  ${tab(viewHref, "view", t("app.view"), route && route.panel === "view")}
384
432
  ${tab(signInHref(), "user", t("app.signIn"), false)}
385
433
  </nav>`;
386
434
  }
435
+ // #1920: a guest has only its chats (the drawer lists them): no tabs to switch between.
436
+ if (guestPage()) return null;
437
+ // #2183 (the user's call): four tabs — Chat (current on /ui and on any of an agent's pages), Needs you, Fleet (on its
438
+ // last tab), Settings.
387
439
  return html`<nav data-trust="navigation" class="tabs" aria-label=${t("app.menu")}>
388
- ${tab(chatHref, "chat", t("app.chat"), route && route.panel === "chat", anyAwaiting)}
440
+ ${tab(chatHref, "chat", t("app.chat"), !!open || (!!route && route.panel === "chat"), anyAwaiting)}
389
441
  ${tab(NEEDS_PATH, "inbox", t("app.needsTab"), route && route.panel === "needs", (app.needs || []).length, t("app.needsCount", (app.needs || []).length))}
390
- ${tab(open ? detailsPath(open) : "/ui/fleet", "fleet", t("app.fleet"), route && (route.panel === "fleet" || route.panel === "details"))}
391
- ${tab(viewHref, "view", t("app.view"), route && route.panel === "view")}
442
+ ${tab(fleetEntryPath(route), "fleet", t("app.fleet"), route && route.panel === "fleet")}
392
443
  ${tab(settingsPath(), "settings", t("app.settings"), route && route.panel === "settings")}
393
444
  </nav>`;
394
445
  }
@@ -435,8 +486,21 @@ export function PendingChanges({ inline = false } = {}) {
435
486
  const { pendingChanges } = useStore(appStore);
436
487
  const list = pendingChanges || [];
437
488
  useTick(list.some(p => p.state === "pending"));
489
+ // #2168: inline (Settings' own column), a request that has just started waiting is brought into view — on a phone the
490
+ // person applied far down the list, and the card that says where to confirm it sat above the screen. Once per request.
491
+ const box = useRef(null), shown = useRef(new Set());
492
+ useEffect(() => {
493
+ if (!inline || !box.current) return;
494
+ for (const p of list) {
495
+ if (p.state !== "pending" || shown.current.has(p.id)) continue;
496
+ shown.current.add(p.id);
497
+ const card = box.current.querySelector(`[data-pending="${p.id}"]`);
498
+ const still = typeof matchMedia === "function" && matchMedia("(prefers-reduced-motion: reduce)").matches;
499
+ if (card && typeof card.scrollIntoView === "function") card.scrollIntoView({ block: "nearest", behavior: still ? "auto" : "smooth" });
500
+ }
501
+ });
438
502
  if (!list.length) return null;
439
- return html`<div data-trust="confirmation" class=${`pending-stack${inline ? " inline" : ""}`} role="region" aria-label=${t("app.pendingTitle")}>${list.map(p => html`<section key=${p.id} class=${`pending-card ${p.state}`} role="status">
503
+ return html`<div data-trust="confirmation" ref=${box} class=${`pending-stack${inline ? " inline" : ""}`} role="region" aria-label=${t("app.pendingTitle")}>${list.map(p => html`<section key=${p.id} data-pending=${p.id} class=${`pending-card ${p.state}`} role="status">
440
504
  <div class="pending-head"><${Icon} name=${p.state === "applied" ? "check" : p.state === "pending" || p.state === "applying" ? "clock" : "alert"} size=${16} />
441
505
  <strong>${t(`app.pending_${p.state}`)}</strong>
442
506
  ${p.state === "pending" ? html`<span class="pending-left">${t("app.pendingLeft", mmss(p.deadline - NOW()))}</span>` : null}
@@ -508,23 +572,24 @@ export function Shell({ panels, onNewInstance, viewOnly = false }) {
508
572
  vv.addEventListener("resize", onResize);
509
573
  return () => vv.removeEventListener("resize", onResize);
510
574
  }, []);
511
- const cls = ["shell", shell.collapsed ? "sb-collapsed" : "", shell.drawer ? "sb-open" : "", shell.keyboard ? "kb-open" : ""].filter(Boolean).join(" ");
575
+ const guest = guestPage(); // #1920: a guest's page — no operations, usage, pending changes or owner CSS
576
+ const cls = ["shell", shell.collapsed ? "sb-collapsed" : "", shell.drawer ? "sb-open" : "", shell.keyboard ? "kb-open" : "", guest ? "guest" : ""].filter(Boolean).join(" ");
512
577
  return html`<div class=${cls}>
513
578
  <a data-trust="navigation" class="skip" href="#main">${t("app.skip")}</a>
514
579
  <${Sidebar} route=${nav.route} onNewInstance=${onNewInstance} viewOnly=${viewOnly} />
515
580
  <div class="scrim" onClick=${closeDrawer} aria-hidden="true"></div>
516
581
  <main id="main" class="main" tabindex="-1" inert=${shell.drawer && narrow() ? true : undefined}>
517
582
  <${ConnectionLine} />
518
- ${viewOnly ? null : html`<${OperationLine} route=${nav.route} />`}
583
+ ${viewOnly || guest ? null : html`<${OperationLine} route=${nav.route} />`}
519
584
  <${Outlet} route=${nav.route} seq=${nav.seq} panels=${panels} />
520
585
  </main>
521
- <${UsageRail} route=${nav.route} />
586
+ ${guest ? null : html`<${UsageRail} route=${nav.route} />`}
522
587
  <${BottomTabs} route=${nav.route} viewOnly=${viewOnly} />
523
588
  ${shell.dialog ? html`<${shell.dialog.Component} ...${shell.dialog.props} onClose=${closeDialog} />` : null}
524
- ${viewOnly || (nav.route && nav.route.panel === "settings") ? null : html`<${PendingChanges} />`}
589
+ ${viewOnly || guest || (nav.route && nav.route.panel === "settings") ? null : html`<${PendingChanges} />`}
525
590
  <${ConfirmHost} />
526
591
  <${ColorDialogHost} />
527
- ${viewOnly ? null : html`<${OwnerCssKeepBar} />`}
592
+ ${viewOnly || guest ? null : html`<${OwnerCssKeepBar} />`}
528
593
  <${Toasts} />
529
594
  </div>`;
530
595
  }
@@ -26,6 +26,20 @@ export function createStore(initial) {
26
26
  export const appStore = createStore({ ready: false, instances: [], uptime: 0, exec: {}, awaiting: {}, connection: "connecting", needs: [], connections: null,
27
27
  hydration: "none", retryHydration: null });
28
28
 
29
+ /**
30
+ * #1920 phase 3: this page is a guest's — the server says so on <body> (data-tier="guest"). A guest sees only its own
31
+ * agents' chats: no fleet, Needs you, View, Settings, filters, commands or new agent, and nothing it may not read is asked
32
+ * for (each would be refused). The server checks every request regardless; this only keeps the page to what works.
33
+ */
34
+ export function guestPage() {
35
+ return typeof document !== "undefined" && !!document.body && document.body.dataset.tier === "guest";
36
+ }
37
+ /** Who a guest's page is signed in as: its web name and its platform (the server's, on <body>). */
38
+ export function guestBadge() {
39
+ const d = typeof document !== "undefined" && document.body ? document.body.dataset : {};
40
+ return { name: d.guestName || "", platform: d.guestPlatform || "" };
41
+ }
42
+
29
43
  /** Fold one status frame (stream or poll) into the store. */
30
44
  export function applyStatus(d) {
31
45
  if (!d || !Array.isArray(d.instances)) return;
@@ -42,7 +56,18 @@ export function applyStatus(d) {
42
56
  // #1629: the agents' themes' revision (app-themes.js re-reads them when it moves).
43
57
  themesRev: typeof d.themes_rev === "number" ? d.themes_rev : null,
44
58
  // #1685: host memory pressure slowing or holding new agents ("elevated" | "critical"), else null.
45
- hostMemory: d.host_memory === "elevated" || d.host_memory === "critical" ? d.host_memory : null });
59
+ hostMemory: d.host_memory === "elevated" || d.host_memory === "critical" ? d.host_memory : null,
60
+ // #2015: while it lasts, the largest processes in the fleet and whose they are (at most 3; anything else dropped).
61
+ hostMemoryTop: hostMemoryTop(d.host_memory_top) });
62
+ }
63
+
64
+ /** #2015: the status frame's host_memory_top, as the dashboard keeps it: well-formed entries only, at most 3. */
65
+ export function hostMemoryTop(raw) {
66
+ if (!Array.isArray(raw)) return [];
67
+ return raw.slice(0, 3).flatMap(p => p && typeof p.name === "string" && Number.isSafeInteger(p.pid) && Number.isSafeInteger(p.rssBytes) && p.rssBytes >= 0
68
+ && ["fleet", "tmux", "instance", "other"].includes(p.owner)
69
+ ? [{ name: p.name.slice(0, 64), pid: p.pid, rssBytes: p.rssBytes, owner: p.owner, ...(p.owner === "instance" && typeof p.instance === "string" ? { instance: p.instance.slice(0, 128) } : {}) }]
70
+ : []);
46
71
  }
47
72
 
48
73
  /** One "activity" event: an instance started or stopped working. */
@@ -30,7 +30,9 @@
30
30
  // - suspend()/resume(): the page is leaving (pagehide) / came back from the back-forward cache.
31
31
 
32
32
  // #1218: steps_tick — an instance's transcript moved while a Log watches it (SSE only: a poll page asks the Log itself).
33
- const EVENTS = ["status", "message", "agent_reply_live", "delivery", "deliveries", "prompt", "prompts", "prompt_resolved", "activity", "needs", "reply_buttons", "steps_tick", "clock"];
33
+ // #1918: traffic — a message between instances moved; delegation — a task opened, or its report closed it (SSE only: the
34
+ // org canvas reads /ui/traffic when it has no stream).
35
+ const EVENTS = ["status", "message", "agent_reply_live", "delivery", "deliveries", "prompt", "prompts", "prompt_resolved", "activity", "needs", "reply_buttons", "steps_tick", "clock", "traffic", "delegation"];
34
36
  const MAX_HELD = 1000; // live events held during one catch-up read; more → read again afterwards
35
37
  const RETRY_DELAYS = [1000, 2000, 4000]; // after the first failed catch-up read; then it waits for Retry
36
38
  const POLL_LIMIT_MS = 10000; // a poll that has not answered by then counts as failed
@@ -10,8 +10,11 @@ import { t } from "./app-i18n.js";
10
10
  import { toast } from "./ui-toast.js";
11
11
  import { confirmDialog } from "./ui-confirm.js";
12
12
 
13
- /** { rev, themes: { <name>: theme }, locked: [<name>], widgets: { <name>: { <slot>: widget } } } as GET /ui/themes said (rev -1: not read yet). */
14
- export const themesStore = createStore({ rev: -1, themes: {}, locked: [], widgets: {}, shown: true });
13
+ /**
14
+ * { rev, themes: { <name>: theme }, locked: [<name>], widgets: { <name>: { <slot>: widget } }, ambient: { <name>: { loop?, click? } } }
15
+ * as GET /ui/themes said (rev -1: not read yet).
16
+ */
17
+ export const themesStore = createStore({ rev: -1, themes: {}, locked: [], widgets: {}, ambient: {}, shown: true });
15
18
  const own = (o, k) => !!o && Object.prototype.hasOwnProperty.call(o, k);
16
19
 
17
20
  const COLOR = /^#[0-9a-f]{6}$/;
@@ -30,14 +33,15 @@ function labelOk(s) {
30
33
  return n >= 1 && n <= 24;
31
34
  }
32
35
 
33
- // Public sessions cannot write host prefs. Keep their explicit switch apart from boot-cached host defaults.
36
+ // Public/guest sessions cannot write host prefs. Keep their explicit browser switch apart from boot-cached defaults.
37
+ // Keep the existing public key for compatibility; both read-only viewer surfaces share this device choice.
34
38
  const PUBLIC_FX_SHOW = "agend_public_fx_show";
35
39
  let publicFxChoice = (() => { try { const v = localStorage.getItem(PUBLIC_FX_SHOW); return v === "on" ? true : v === "off" ? false : null; } catch { return null; } })();
36
40
 
37
41
  /** Whether this page shows agents' themes now: the viewer's switch (default on), and never in safe mode. */
38
42
  export function themesShown() {
39
43
  if (safeMode()) return false;
40
- if (publicLink() && publicFxChoice !== null) return publicFxChoice;
44
+ if (localAppearanceViewer() && publicFxChoice !== null) return publicFxChoice;
41
45
  try {
42
46
  const f = prefInForce("fx.show"); // this page's choice, else the host's, else the default
43
47
  if (f) return f.value !== false;
@@ -46,7 +50,7 @@ export function themesShown() {
46
50
  }
47
51
  /** The viewer's switch (Settings → Appearance): kept with the person's preferences, and in force on this page at once. */
48
52
  export function setThemesShown(on) {
49
- if (publicLink()) {
53
+ if (localAppearanceViewer()) {
50
54
  publicFxChoice = !!on;
51
55
  try { localStorage.setItem(PUBLIC_FX_SHOW, on ? "on" : "off"); } catch { /* the in-memory choice still applies */ }
52
56
  } else savePref("fx.show", on ? null : false);
@@ -122,7 +126,8 @@ async function read(rev) {
122
126
  if (!body || typeof body !== "object") break;
123
127
  themesStore.set({ rev: typeof body.rev === "number" ? body.rev : want, themes: body.themes && typeof body.themes === "object" ? body.themes : {},
124
128
  locked: Array.isArray(body.locked) ? body.locked.filter((n) => typeof n === "string") : [],
125
- widgets: body.widgets && typeof body.widgets === "object" && !Array.isArray(body.widgets) ? body.widgets : {} });
129
+ widgets: body.widgets && typeof body.widgets === "object" && !Array.isArray(body.widgets) ? body.widgets : {},
130
+ ambient: body.ambient && typeof body.ambient === "object" && !Array.isArray(body.ambient) ? body.ambient : {} });
126
131
  if (want === wanted) break;
127
132
  }
128
133
  } finally { reading = false; }
@@ -144,6 +149,7 @@ export function startThemes() {
144
149
  }
145
150
 
146
151
  const publicLink = () => typeof document !== "undefined" && !!document.body && document.body.dataset.publicLink === "1";
152
+ const localAppearanceViewer = () => publicLink() || typeof document !== "undefined" && document.body?.dataset.tier === "guest";
147
153
 
148
154
  /** A person's Reset or Lock of an agent's theme (an admin session; never over the public link). */
149
155
  async function themeAction(name, what, body) {
@@ -169,6 +175,18 @@ async function removeWidget(name, slot) {
169
175
  else toast(t("chat.widgetRemoved", name));
170
176
  }
171
177
 
178
+ /** #2117: a person's Clear of an agent's ambient, one kind ("loop" or "click"). */
179
+ export async function clearAmbient(name, which) {
180
+ let r;
181
+ try {
182
+ const res = await fetch(`/ui/themes/${encodeURIComponent(name)}/ambient`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ which }) });
183
+ r = await res.json().catch(() => ({}));
184
+ if (!res.ok) r = { error: r && r.error ? r.error : `HTTP ${res.status}` };
185
+ } catch (err) { r = { error: err && err.message ? err.message : "network" }; }
186
+ if (r && r.error) toast(t("chat.themeFailed", r.error), false);
187
+ else toast(t("chat.ambientCleared", name));
188
+ }
189
+
172
190
  /** The chat's and Details' ⋯ entries for an agent's theme: Lock/Unlock, Reset… and its widgets' Remove — none over the public link. */
173
191
  export function themeMenuItems(name) {
174
192
  if (publicLink()) return [];