@songsid/agend 2.2.1 → 2.2.5

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 (447) hide show
  1. package/dist/agent-cli-instructions.md +5 -2
  2. package/dist/agent-cli.js +284 -2
  3. package/dist/agent-cli.js.map +1 -1
  4. package/dist/agent-endpoint.d.ts +2 -0
  5. package/dist/agent-endpoint.js +29 -0
  6. package/dist/agent-endpoint.js.map +1 -1
  7. package/dist/agent-file-read.d.ts +38 -0
  8. package/dist/agent-file-read.js +317 -0
  9. package/dist/agent-file-read.js.map +1 -0
  10. package/dist/agent-whats-new.d.ts +11 -0
  11. package/dist/agent-whats-new.js +34 -2
  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 +30 -1
  16. package/dist/auth-api.js +61 -12
  17. package/dist/auth-api.js.map +1 -1
  18. package/dist/authority/authority-state.d.ts +38 -0
  19. package/dist/authority/authority-state.js +114 -0
  20. package/dist/authority/authority-state.js.map +1 -0
  21. package/dist/authority/session-authorizations.d.ts +43 -0
  22. package/dist/authority/session-authorizations.js +102 -0
  23. package/dist/authority/session-authorizations.js.map +1 -0
  24. package/dist/authority/web-authority.d.ts +120 -0
  25. package/dist/authority/web-authority.js +157 -0
  26. package/dist/authority/web-authority.js.map +1 -0
  27. package/dist/backend/antigravity.js +3 -3
  28. package/dist/backend/antigravity.js.map +1 -1
  29. package/dist/backend/claude-code.d.ts +2 -2
  30. package/dist/backend/claude-code.js +10 -5
  31. package/dist/backend/claude-code.js.map +1 -1
  32. package/dist/backend/launch-effort.js +1 -1
  33. package/dist/backend/launch-effort.js.map +1 -1
  34. package/dist/backend/muse.js +10 -2
  35. package/dist/backend/muse.js.map +1 -1
  36. package/dist/backend/types.d.ts +14 -0
  37. package/dist/backend/types.js +25 -0
  38. package/dist/backend/types.js.map +1 -1
  39. package/dist/backend-availability.d.ts +120 -0
  40. package/dist/backend-availability.js +396 -0
  41. package/dist/backend-availability.js.map +1 -0
  42. package/dist/backend-command.d.ts +21 -3
  43. package/dist/backend-command.js +23 -4
  44. package/dist/backend-command.js.map +1 -1
  45. package/dist/bundled-skills.d.ts +14 -0
  46. package/dist/bundled-skills.js +29 -0
  47. package/dist/bundled-skills.js.map +1 -0
  48. package/dist/cache-analysis.d.ts +47 -1
  49. package/dist/cache-analysis.js +68 -8
  50. package/dist/cache-analysis.js.map +1 -1
  51. package/dist/cache-ledger.d.ts +16 -3
  52. package/dist/cache-ledger.js +33 -13
  53. package/dist/cache-ledger.js.map +1 -1
  54. package/dist/cache-report-tool.d.ts +17 -0
  55. package/dist/cache-report-tool.js +40 -0
  56. package/dist/cache-report-tool.js.map +1 -0
  57. package/dist/cache-service.d.ts +70 -3
  58. package/dist/cache-service.js +178 -17
  59. package/dist/cache-service.js.map +1 -1
  60. package/dist/channel/adapters/discord-client-retirement.d.ts +5 -0
  61. package/dist/channel/adapters/discord-client-retirement.js +57 -0
  62. package/dist/channel/adapters/discord-client-retirement.js.map +1 -0
  63. package/dist/channel/adapters/discord.d.ts +11 -0
  64. package/dist/channel/adapters/discord.js +66 -6
  65. package/dist/channel/adapters/discord.js.map +1 -1
  66. package/dist/channel/adapters/telegram.d.ts +8 -1
  67. package/dist/channel/adapters/telegram.js +74 -4
  68. package/dist/channel/adapters/telegram.js.map +1 -1
  69. package/dist/channel/mcp-tools.js +48 -2
  70. package/dist/channel/mcp-tools.js.map +1 -1
  71. package/dist/channel/types.d.ts +21 -0
  72. package/dist/classic-channel-manager.d.ts +10 -5
  73. package/dist/classic-channel-manager.js +12 -9
  74. package/dist/classic-channel-manager.js.map +1 -1
  75. package/dist/cli.js +142 -110
  76. package/dist/cli.js.map +1 -1
  77. package/dist/config-validator.js +119 -4
  78. package/dist/config-validator.js.map +1 -1
  79. package/dist/daemon.d.ts +8 -0
  80. package/dist/daemon.js +298 -22
  81. package/dist/daemon.js.map +1 -1
  82. package/dist/decision-list.d.ts +25 -0
  83. package/dist/decision-list.js +30 -0
  84. package/dist/decision-list.js.map +1 -0
  85. package/dist/delivery-outbox.d.ts +10 -0
  86. package/dist/delivery-outbox.js +12 -1
  87. package/dist/delivery-outbox.js.map +1 -1
  88. package/dist/fleet-context.d.ts +15 -0
  89. package/dist/fleet-manager.d.ts +514 -12
  90. package/dist/fleet-manager.js +1885 -163
  91. package/dist/fleet-manager.js.map +1 -1
  92. package/dist/fleet-process-handlers.d.ts +18 -0
  93. package/dist/fleet-process-handlers.js +69 -0
  94. package/dist/fleet-process-handlers.js.map +1 -0
  95. package/dist/fleet-traffic.d.ts +74 -0
  96. package/dist/fleet-traffic.js +196 -0
  97. package/dist/fleet-traffic.js.map +1 -0
  98. package/dist/general-knowledge/skills/credential-profiles/SKILL.md +20 -5
  99. package/dist/general-knowledge/skills/cross-instance-messaging/SKILL.md +6 -1
  100. package/dist/general-knowledge/skills/multi-channel/SKILL.md +3 -2
  101. package/dist/general-knowledge/skills/persona-emoji/SKILL.md +1 -1
  102. package/dist/general-knowledge/skills/web-appearance/SKILL.md +492 -0
  103. package/dist/general-knowledge/skills/web-dashboard/SKILL.md +482 -0
  104. package/dist/general-knowledge/skills/worker-collaboration/SKILL.md +1 -1
  105. package/dist/human-delivery-receipts.d.ts +24 -1
  106. package/dist/human-delivery-receipts.js +64 -11
  107. package/dist/human-delivery-receipts.js.map +1 -1
  108. package/dist/instance-avatar.js +3 -0
  109. package/dist/instance-avatar.js.map +1 -1
  110. package/dist/instance-config-impact.js +6 -1
  111. package/dist/instance-config-impact.js.map +1 -1
  112. package/dist/instance-lifecycle.d.ts +2 -0
  113. package/dist/instance-lifecycle.js +20 -10
  114. package/dist/instance-lifecycle.js.map +1 -1
  115. package/dist/instance-names.d.ts +4 -0
  116. package/dist/instance-names.js +12 -0
  117. package/dist/instance-names.js.map +1 -0
  118. package/dist/instructions.d.ts +12 -1
  119. package/dist/instructions.js +13 -2
  120. package/dist/instructions.js.map +1 -1
  121. package/dist/locale.js +100 -8
  122. package/dist/locale.js.map +1 -1
  123. package/dist/ls-attach-resolve.d.ts +95 -0
  124. package/dist/ls-attach-resolve.js +174 -0
  125. package/dist/ls-attach-resolve.js.map +1 -0
  126. package/dist/memory-hogs.d.ts +27 -0
  127. package/dist/memory-hogs.js +124 -0
  128. package/dist/memory-hogs.js.map +1 -0
  129. package/dist/needs-you-hub.d.ts +4 -0
  130. package/dist/needs-you-hub.js +31 -6
  131. package/dist/needs-you-hub.js.map +1 -1
  132. package/dist/needs-you.d.ts +1 -13
  133. package/dist/needs-you.js +3 -37
  134. package/dist/needs-you.js.map +1 -1
  135. package/dist/outbound-handlers.d.ts +15 -0
  136. package/dist/outbound-handlers.js +152 -11
  137. package/dist/outbound-handlers.js.map +1 -1
  138. package/dist/outbound-schemas.d.ts +228 -4
  139. package/dist/outbound-schemas.js +147 -1
  140. package/dist/outbound-schemas.js.map +1 -1
  141. package/dist/public-web-gateway.js +21 -1
  142. package/dist/public-web-gateway.js.map +1 -1
  143. package/dist/public-web-link.d.ts +9 -0
  144. package/dist/public-web-link.js +8 -0
  145. package/dist/public-web-link.js.map +1 -1
  146. package/dist/quickstart-api.d.ts +10 -1
  147. package/dist/quickstart-api.js +12 -4
  148. package/dist/quickstart-api.js.map +1 -1
  149. package/dist/reply-buttons-controller.d.ts +4 -0
  150. package/dist/reply-buttons-controller.js +2 -2
  151. package/dist/reply-buttons-controller.js.map +1 -1
  152. package/dist/repo-worktree-guard.d.ts +36 -0
  153. package/dist/repo-worktree-guard.js +143 -0
  154. package/dist/repo-worktree-guard.js.map +1 -0
  155. package/dist/restart-progress.d.ts +65 -1
  156. package/dist/restart-progress.js +236 -10
  157. package/dist/restart-progress.js.map +1 -1
  158. package/dist/rollout-index.d.ts +5 -0
  159. package/dist/rollout-index.js +26 -5
  160. package/dist/rollout-index.js.map +1 -1
  161. package/dist/scheduler/scheduler.d.ts +70 -1
  162. package/dist/scheduler/scheduler.js +256 -17
  163. package/dist/scheduler/scheduler.js.map +1 -1
  164. package/dist/service-installer.d.ts +2 -1
  165. package/dist/service-installer.js +18 -3
  166. package/dist/service-installer.js.map +1 -1
  167. package/dist/settings-api.d.ts +23 -0
  168. package/dist/settings-api.js +233 -2
  169. package/dist/settings-api.js.map +1 -1
  170. package/dist/settings-change.js +112 -0
  171. package/dist/settings-change.js.map +1 -1
  172. package/dist/settings-effect.js +32 -2
  173. package/dist/settings-effect.js.map +1 -1
  174. package/dist/settings-http-confirmation.js +5 -0
  175. package/dist/settings-http-confirmation.js.map +1 -1
  176. package/dist/tmux-manager.d.ts +5 -0
  177. package/dist/tmux-manager.js +16 -0
  178. package/dist/tmux-manager.js.map +1 -1
  179. package/dist/tool-permissions.d.ts +4 -6
  180. package/dist/tool-permissions.js +100 -2
  181. package/dist/tool-permissions.js.map +1 -1
  182. package/dist/topic-commands.d.ts +17 -7
  183. package/dist/topic-commands.js +39 -21
  184. package/dist/topic-commands.js.map +1 -1
  185. package/dist/topic-link.d.ts +128 -0
  186. package/dist/topic-link.js +269 -0
  187. package/dist/topic-link.js.map +1 -0
  188. package/dist/types.d.ts +61 -1
  189. package/dist/ui/app-effects.js +245 -0
  190. package/dist/ui/chat-ambient.js +307 -0
  191. package/dist/ui/chat-effect-posters.js +42 -0
  192. package/dist/ui/chat-effects.js +160 -0
  193. package/dist/ui/chat-live-replies.js +58 -0
  194. package/dist/ui/chat-render.js +25 -7
  195. package/dist/ui/chat-store.js +41 -13
  196. package/dist/ui/chat-strings.js +86 -2
  197. package/dist/ui/chat-thread.js +204 -37
  198. package/dist/ui/chat-tour.js +1 -1
  199. package/dist/ui/effect-trial-controller.js +124 -0
  200. package/dist/ui/effect-trial-offer.js +31 -0
  201. package/dist/ui/effect-trial-panel.js +40 -0
  202. package/dist/ui/fleet-cache.js +23 -1
  203. package/dist/ui/fleet-canvas-layout.js +111 -0
  204. package/dist/ui/fleet-canvas.js +525 -0
  205. package/dist/ui/fleet-org.js +27 -12
  206. package/dist/ui/fleet-traffic.js +343 -0
  207. package/dist/ui/fleet-usage.js +401 -0
  208. package/dist/ui/image-lightbox.js +1 -1
  209. package/dist/ui/panel-chat.js +252 -58
  210. package/dist/ui/panel-details.js +20 -6
  211. package/dist/ui/panel-fleet.js +45 -19
  212. package/dist/ui/panel-log.js +6 -6
  213. package/dist/ui/panel-needs.js +3 -3
  214. package/dist/ui/panel-settings.js +58 -13
  215. package/dist/ui/preview-panel.js +25 -9
  216. package/dist/ui/preview.js +3 -2
  217. package/dist/ui/settings-appearance.js +349 -0
  218. package/dist/ui/settings-catalog.js +92 -0
  219. package/dist/ui/settings-dialogs.js +40 -12
  220. package/dist/ui/settings-effects.js +202 -0
  221. package/dist/ui/settings-guests.js +117 -0
  222. package/dist/ui/settings-learn.js +25 -0
  223. package/dist/ui/settings-model.js +15 -6
  224. package/dist/ui/settings-mods.js +271 -0
  225. package/dist/ui/settings-owner-effects.js +80 -0
  226. package/dist/ui/settings-strings.js +335 -10
  227. package/dist/ui/settings-web-authority.js +91 -0
  228. package/dist/ui/shared/agend-auth.js +28 -4
  229. package/dist/ui/shared/agent-widget.js +248 -0
  230. package/dist/ui/shared/app-i18n.js +6 -2
  231. package/dist/ui/shared/app-prefs.js +34 -13
  232. package/dist/ui/shared/app-route.js +42 -2
  233. package/dist/ui/shared/app-session.js +3 -1
  234. package/dist/ui/shared/app-shell.js +119 -41
  235. package/dist/ui/shared/app-store.js +28 -1
  236. package/dist/ui/shared/app-stream.js +14 -7
  237. package/dist/ui/shared/app-themes.js +206 -0
  238. package/dist/ui/shared/app.css +566 -5
  239. package/dist/ui/shared/app.js +23 -10
  240. package/dist/ui/shared/card-strings.js +32 -0
  241. package/dist/ui/shared/effect-app-runtime.js +297 -0
  242. package/dist/ui/shared/effect-baseline.js +64 -0
  243. package/dist/ui/shared/effect-chat-runtime.js +167 -0
  244. package/dist/ui/shared/effect-cursor.js +125 -0
  245. package/dist/ui/shared/effect-frame.js +146 -0
  246. package/dist/ui/shared/effect-geometry.js +137 -0
  247. package/dist/ui/shared/effect-host-style.js +10 -0
  248. package/dist/ui/shared/effect-owner-regions.js +74 -0
  249. package/dist/ui/shared/effect-owner-source.js +122 -0
  250. package/dist/ui/shared/effect-policy.js +151 -0
  251. package/dist/ui/shared/effect-poster-reader.js +98 -0
  252. package/dist/ui/shared/effect-posters.js +117 -0
  253. package/dist/ui/shared/effect-profiler.js +184 -0
  254. package/dist/ui/shared/effect-profiles.js +65 -0
  255. package/dist/ui/shared/effect-registry.js +543 -0
  256. package/dist/ui/shared/effect-runtime-loader.js +234 -0
  257. package/dist/ui/shared/effect-safety.js +153 -0
  258. package/dist/ui/shared/effect-stock-snapshot.js +43 -0
  259. package/dist/ui/shared/effect-stock-trust.js +217 -0
  260. package/dist/ui/shared/effect-trial-shadow.js +62 -0
  261. package/dist/ui/shared/effect-trial-source.js +103 -0
  262. package/dist/ui/shared/effect-trial-stage.js +170 -0
  263. package/dist/ui/shared/effect-trial.css +40 -0
  264. package/dist/ui/shared/fx-motion.js +67 -0
  265. package/dist/ui/shared/header-tools.js +1 -1
  266. package/dist/ui/shared/identity-card.js +273 -0
  267. package/dist/ui/shared/identity-color.js +261 -0
  268. package/dist/ui/shared/instance-nav.js +215 -41
  269. package/dist/ui/shared/owner-css.js +250 -0
  270. package/dist/ui/shared/owner-effect-prefs.js +117 -0
  271. package/dist/ui/shared/panel-view.js +103 -10
  272. package/dist/ui/shared/signin.js +2 -0
  273. package/dist/ui/shared/tokens.css +9 -3
  274. package/dist/ui/shared/ui-dialog.js +1 -1
  275. package/dist/ui/shared/ui-icons.js +3 -0
  276. package/dist/ui/shared/ui-menu.js +1 -1
  277. package/dist/ui/shared/ui-toast.js +1 -1
  278. package/dist/ui/shared/usage-dialog.js +22 -2
  279. package/dist/ui/shared/view-strings.js +98 -2
  280. package/dist/ui/shared/web-authorize.js +151 -0
  281. package/dist/ui/signin.html +1 -0
  282. package/dist/ui/web-terminal/terminal.css +4 -4
  283. package/dist/usage/providers.d.ts +28 -0
  284. package/dist/usage/providers.js +126 -36
  285. package/dist/usage/providers.js.map +1 -1
  286. package/dist/usage-report.d.ts +158 -0
  287. package/dist/usage-report.js +226 -0
  288. package/dist/usage-report.js.map +1 -0
  289. package/dist/usage-rollup.d.ts +51 -0
  290. package/dist/usage-rollup.js +168 -0
  291. package/dist/usage-rollup.js.map +1 -0
  292. package/dist/view-api.d.ts +68 -0
  293. package/dist/view-api.js +249 -8
  294. package/dist/view-api.js.map +1 -1
  295. package/dist/web-ambient.d.ts +72 -0
  296. package/dist/web-ambient.js +161 -0
  297. package/dist/web-ambient.js.map +1 -0
  298. package/dist/web-api.d.ts +117 -7
  299. package/dist/web-api.js +1283 -70
  300. package/dist/web-api.js.map +1 -1
  301. package/dist/web-appearance-limits.d.ts +82 -0
  302. package/dist/web-appearance-limits.js +83 -0
  303. package/dist/web-appearance-limits.js.map +1 -0
  304. package/dist/web-appearance-policy.d.ts +64 -0
  305. package/dist/web-appearance-policy.js +87 -0
  306. package/dist/web-appearance-policy.js.map +1 -0
  307. package/dist/web-appearance-tools.d.ts +53 -0
  308. package/dist/web-appearance-tools.js +124 -0
  309. package/dist/web-appearance-tools.js.map +1 -0
  310. package/dist/web-appearance-view.d.ts +124 -0
  311. package/dist/web-appearance-view.js +278 -0
  312. package/dist/web-appearance-view.js.map +1 -0
  313. package/dist/web-appearance.d.ts +93 -0
  314. package/dist/web-appearance.js +373 -0
  315. package/dist/web-appearance.js.map +1 -0
  316. package/dist/web-asset-parts.d.ts +93 -0
  317. package/dist/web-asset-parts.js +133 -0
  318. package/dist/web-asset-parts.js.map +1 -0
  319. package/dist/web-auth.js +20 -2
  320. package/dist/web-auth.js.map +1 -1
  321. package/dist/web-card.d.ts +110 -0
  322. package/dist/web-card.js +266 -0
  323. package/dist/web-card.js.map +1 -0
  324. package/dist/web-channel-echo.d.ts +7 -1
  325. package/dist/web-channel-echo.js +6 -2
  326. package/dist/web-channel-echo.js.map +1 -1
  327. package/dist/web-chat-history.d.ts +63 -0
  328. package/dist/web-chat-history.js +109 -6
  329. package/dist/web-chat-history.js.map +1 -1
  330. package/dist/web-commands.d.ts +1 -0
  331. package/dist/web-effect-bootstrap.d.ts +12 -0
  332. package/dist/web-effect-bootstrap.js +49 -0
  333. package/dist/web-effect-bootstrap.js.map +1 -0
  334. package/dist/web-effect-bundle.d.ts +28 -0
  335. package/dist/web-effect-bundle.js +171 -0
  336. package/dist/web-effect-bundle.js.map +1 -0
  337. package/dist/web-effect-canonical.d.ts +8 -0
  338. package/dist/web-effect-canonical.js +27 -0
  339. package/dist/web-effect-canonical.js.map +1 -0
  340. package/dist/web-effect-library.d.ts +114 -0
  341. package/dist/web-effect-library.js +530 -0
  342. package/dist/web-effect-library.js.map +1 -0
  343. package/dist/web-effect-playback.d.ts +30 -0
  344. package/dist/web-effect-playback.js +71 -0
  345. package/dist/web-effect-playback.js.map +1 -0
  346. package/dist/web-effect-poster.d.ts +8 -0
  347. package/dist/web-effect-poster.js +52 -0
  348. package/dist/web-effect-poster.js.map +1 -0
  349. package/dist/web-effect-profiles.d.ts +118 -0
  350. package/dist/web-effect-profiles.js +180 -0
  351. package/dist/web-effect-profiles.js.map +1 -0
  352. package/dist/web-effect-service.d.ts +93 -0
  353. package/dist/web-effect-service.js +335 -0
  354. package/dist/web-effect-service.js.map +1 -0
  355. package/dist/web-effect-tools.d.ts +48 -0
  356. package/dist/web-effect-tools.js +129 -0
  357. package/dist/web-effect-tools.js.map +1 -0
  358. package/dist/web-effect-trials.d.ts +86 -0
  359. package/dist/web-effect-trials.js +204 -0
  360. package/dist/web-effect-trials.js.map +1 -0
  361. package/dist/web-effect-upload.d.ts +27 -0
  362. package/dist/web-effect-upload.js +185 -0
  363. package/dist/web-effect-upload.js.map +1 -0
  364. package/dist/web-effect-worker.d.ts +8 -0
  365. package/dist/web-effect-worker.js +170 -0
  366. package/dist/web-effect-worker.js.map +1 -0
  367. package/dist/web-effect.d.ts +103 -0
  368. package/dist/web-effect.js +153 -0
  369. package/dist/web-effect.js.map +1 -0
  370. package/dist/web-guest-access.d.ts +125 -0
  371. package/dist/web-guest-access.js +234 -0
  372. package/dist/web-guest-access.js.map +1 -0
  373. package/dist/web-guest-rooms.d.ts +82 -0
  374. package/dist/web-guest-rooms.js +135 -0
  375. package/dist/web-guest-rooms.js.map +1 -0
  376. package/dist/web-identity-color.d.ts +87 -0
  377. package/dist/web-identity-color.js +209 -0
  378. package/dist/web-identity-color.js.map +1 -0
  379. package/dist/web-identity-palette.d.ts +113 -0
  380. package/dist/web-identity-palette.js +84 -0
  381. package/dist/web-identity-palette.js.map +1 -0
  382. package/dist/web-image-meta.d.ts +38 -0
  383. package/dist/web-image-meta.js +262 -0
  384. package/dist/web-image-meta.js.map +1 -0
  385. package/dist/web-login.d.ts +47 -0
  386. package/dist/web-login.js +115 -0
  387. package/dist/web-login.js.map +1 -1
  388. package/dist/web-mod-agent.d.ts +50 -0
  389. package/dist/web-mod-agent.js +207 -0
  390. package/dist/web-mod-agent.js.map +1 -0
  391. package/dist/web-mod-bundle.d.ts +157 -0
  392. package/dist/web-mod-bundle.js +529 -0
  393. package/dist/web-mod-bundle.js.map +1 -0
  394. package/dist/web-mod-export.d.ts +68 -0
  395. package/dist/web-mod-export.js +100 -0
  396. package/dist/web-mod-export.js.map +1 -0
  397. package/dist/web-mod-store.d.ts +289 -0
  398. package/dist/web-mod-store.js +638 -0
  399. package/dist/web-mod-store.js.map +1 -0
  400. package/dist/web-org.d.ts +3 -3
  401. package/dist/web-org.js +11 -17
  402. package/dist/web-org.js.map +1 -1
  403. package/dist/web-owner-css-store.d.ts +222 -0
  404. package/dist/web-owner-css-store.js +440 -0
  405. package/dist/web-owner-css-store.js.map +1 -0
  406. package/dist/web-owner-css.d.ts +58 -0
  407. package/dist/web-owner-css.js +552 -0
  408. package/dist/web-owner-css.js.map +1 -0
  409. package/dist/web-prefs.d.ts +62 -0
  410. package/dist/web-prefs.js +232 -36
  411. package/dist/web-prefs.js.map +1 -1
  412. package/dist/web-preview.d.ts +15 -3
  413. package/dist/web-preview.js +28 -9
  414. package/dist/web-preview.js.map +1 -1
  415. package/dist/web-returning-device.d.ts +41 -0
  416. package/dist/web-returning-device.js +57 -5
  417. package/dist/web-returning-device.js.map +1 -1
  418. package/dist/web-session-authorization.d.ts +132 -0
  419. package/dist/web-session-authorization.js +193 -0
  420. package/dist/web-session-authorization.js.map +1 -0
  421. package/dist/web-session.d.ts +70 -10
  422. package/dist/web-session.js +137 -22
  423. package/dist/web-session.js.map +1 -1
  424. package/dist/web-settings-patch.d.ts +67 -2
  425. package/dist/web-settings-patch.js +196 -1
  426. package/dist/web-settings-patch.js.map +1 -1
  427. package/dist/web-settings-tools.d.ts +89 -0
  428. package/dist/web-settings-tools.js +273 -0
  429. package/dist/web-settings-tools.js.map +1 -0
  430. package/dist/web-shell-routes.d.ts +2 -2
  431. package/dist/web-shell-routes.js +2 -2
  432. package/dist/web-shell-routes.js.map +1 -1
  433. package/dist/web-theme-store.d.ts +257 -0
  434. package/dist/web-theme-store.js +627 -0
  435. package/dist/web-theme-store.js.map +1 -0
  436. package/dist/web-theme.d.ts +123 -0
  437. package/dist/web-theme.js +416 -0
  438. package/dist/web-theme.js.map +1 -0
  439. package/dist/web-upload.d.ts +13 -0
  440. package/dist/web-upload.js +39 -3
  441. package/dist/web-upload.js.map +1 -1
  442. package/dist/web-widget.d.ts +72 -0
  443. package/dist/web-widget.js +245 -0
  444. package/dist/web-widget.js.map +1 -0
  445. package/dist/workflow-templates/default.md +1 -1
  446. package/package.json +1 -1
  447. package/templates/systemd.service.ejs +8 -0
@@ -0,0 +1,202 @@
1
+ // #1629 B2: library management only. This module never loads Worker code or creates a preview/frame.
2
+ import { html, useEffect, 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 { confirmDialog } from "/assets/ui-confirm.js";
6
+ import { OwnerEffects } from "./settings-owner-effects.js";
7
+ import { CardTitle } from "./settings-learn.js";
8
+
9
+ register("effectSettings", {
10
+ en: {
11
+ title: "Effects", lead: "Your effect files, and which one a chat uses.", folder: "Upload an effect folder", files: "effect.json + worker.js (and pictures), up to 1 MiB.",
12
+ inUse: "This effect is still in use and cannot be deleted.", quota: "Your library reached its 10-effect or 8 MiB limit. Delete unused copies first.", rateLimited: "Too many changes. Wait a moment, then try again.",
13
+ loading: "Loading effects…", retry: "Reload library", empty: "No effects in your library.", failed: "Couldn't load the library. Try again.", actionFailed: "Couldn't save this change. Try again.", savedRefresh: "Saved. Reload the library to see the result.",
14
+ uploadFailed: "Couldn't upload this folder. Check its files and size, then try again.", public: "Effect management isn't available on a temporary public link.", readOnly: "Read-only session: sign in as an admin to change effects.",
15
+ yours: "Your copy", shared: "Shared with you", share: "Share your copy with agents and people on this fleet", delete: "Delete your copy", deleteConfirm: "Delete your unused copy of this effect? Other people's copies are kept.",
16
+ chat: "Chat", chooseChat: "Choose a chat", apply: "Save for this chat", remove: "Remove from this chat", saved: "Saved.", history: "Profile history", unmeasured: "Not measured yet", unavailable: "Profile unavailable", reported: "Reported host observations; they don't prove an effect's cost.",
17
+ fps: "Host FPS p50 / p10", tasks: "Host long task p95", noTasks: "Not observed", standard: "Standard device", constrained: "Constrained device", params: "Parameters", badParams: "Check the parameter values.", more: "Show more", count: "{0} effects", one: "1 effect", profileFailed: "Couldn't read profile history. Try again.",
18
+ },
19
+ "zh-TW": {
20
+ title: "效果", lead: "你的效果檔案,以及每個聊天使用哪一個。", folder: "上傳效果資料夾", files: "effect.json + worker.js(可加圖片),最多 1 MiB。",
21
+ inUse: "這個效果仍在使用中,無法刪除。", quota: "你的程式庫已達 10 個效果或 8 MiB 上限,請先刪除未使用的副本。", rateLimited: "變更太頻繁,請稍候再試。",
22
+ loading: "正在載入效果…", retry: "重新載入程式庫", empty: "程式庫中還沒有效果。", failed: "無法載入程式庫,請重試。", actionFailed: "無法儲存變更,請重試。", savedRefresh: "已儲存,請重新載入程式庫查看結果。",
23
+ uploadFailed: "無法上傳這個資料夾,請檢查檔案與大小後重試。", public: "臨時公開連結不提供效果管理。", readOnly: "目前為唯讀登入,請以管理員身分登入才能修改效果。",
24
+ yours: "你持有的副本", shared: "他人與你共享", share: "將你的副本共享給這個 fleet 的 agent 與使用者", delete: "刪除你的副本", deleteConfirm: "刪除你未使用的效果副本?其他人的副本會保留。",
25
+ chat: "聊天", chooseChat: "選擇聊天", apply: "儲存到這個聊天", remove: "從這個聊天移除", saved: "已儲存。", history: "效能回報紀錄", unmeasured: "尚未測量", unavailable: "無法取得效能回報", reported: "這是主機觀測回報,不能證明效果本身的資源用量。",
26
+ fps: "主機 FPS p50/p10", tasks: "主機長任務 p95", noTasks: "未觀測到", standard: "一般裝置", constrained: "受限裝置", params: "參數", badParams: "請檢查參數值。", more: "顯示更多", count: "{0} 個效果", one: "1 個效果", profileFailed: "無法讀取效能回報紀錄,請重試。",
27
+ },
28
+ });
29
+ const tn = (key, ...args) => t(`effectSettings.${key}`, ...args);
30
+ const REFUSALS = Object.freeze({ effect_in_use: "inUse", effect_quota: "quota", rate_limited: "rateLimited" });
31
+ const refusal = (body, fallback) => tn(typeof body?.error === "string" && Object.hasOwn(REFUSALS, body.error) ? REFUSALS[body.error] : fallback);
32
+ const ID = /^[0-9a-f]{32}$/;
33
+ const PATH = /^(?:effect\.json|worker\.js|poster\.png|cursor\.png|assets\/[a-z0-9][a-z0-9_-]{0,63}\.(?:png|jpe?g|webp|gif))$/;
34
+ const RAW_MAX = 8 * 1024 * 1024, WIRE_MAX = 12 * 1024 * 1024;
35
+ const plain = (v) => !!v && typeof v === "object" && Object.getPrototypeOf(v) === Object.prototype;
36
+ const publicLink = () => typeof document !== "undefined" && document.body?.dataset.publicLink === "1";
37
+
38
+ /** File metadata bounds are checked before reading. No directory path reaches the server. */
39
+ export async function encodeEffectFolder(input, signal) {
40
+ const files = Array.from(input || []);
41
+ if (files.length < 2 || files.length > 20) throw Error("effect_files");
42
+ const nested = files.some(f => !!f?.webkitRelativePath), paths = new Set(); let root = null, raw = 0, assets = 0;
43
+ const rows = files.map(file => {
44
+ if (!file || typeof file.name !== "string" || !Number.isSafeInteger(file.size) || file.size < 0 || (raw += file.size) > RAW_MAX || typeof file.arrayBuffer !== "function") throw Error("effect_files");
45
+ let path = file.name;
46
+ if (nested) {
47
+ const relative = file.webkitRelativePath;
48
+ if (typeof relative !== "string" || relative.startsWith("/") || relative.includes("\\")) throw Error("effect_files");
49
+ const at = relative.indexOf("/"); if (at < 1) throw Error("effect_files");
50
+ const prefix = relative.slice(0, at); if (root === null) root = prefix; if (root !== prefix) throw Error("effect_files");
51
+ path = relative.slice(at + 1);
52
+ }
53
+ if (!PATH.test(path) || paths.has(path) || (path.startsWith("assets/") && ++assets > 16)) throw Error("effect_files");
54
+ paths.add(path); return { file, path };
55
+ });
56
+ if (!paths.has("effect.json") || !paths.has("worker.js")) throw Error("effect_files");
57
+ const encoded = [];
58
+ for (const { file, path } of rows) {
59
+ if (signal?.aborted) throw Error("cancelled");
60
+ const buffer = await file.arrayBuffer(); if (signal?.aborted) throw Error("cancelled");
61
+ if (!(buffer instanceof ArrayBuffer) || buffer.byteLength !== file.size) throw Error("effect_files");
62
+ const bytes = new Uint8Array(buffer), chunks = [];
63
+ for (let i = 0; i < bytes.length; i += 8192) chunks.push(String.fromCharCode(...bytes.subarray(i, i + 8192)));
64
+ encoded.push({ path, data_b64: btoa(chunks.join("")) });
65
+ }
66
+ const body = JSON.stringify(encoded); if (new TextEncoder().encode(body).byteLength > WIRE_MAX) throw Error("effect_files");
67
+ return body;
68
+ }
69
+ export function effectCatalog(body) {
70
+ if (!plain(body) || typeof body.canWrite !== "boolean" || !Array.isArray(body.effects)) return null;
71
+ const seen = new Set(), effects = [];
72
+ for (const e of body.effects) {
73
+ if (!plain(e) || typeof e.id !== "string" || !ID.test(e.id) || seen.has(e.id) || !plain(e.manifest) || typeof e.manifest.title !== "string" || e.manifest.title.length < 1 || e.manifest.title.length > 40 || !Number.isInteger(e.bytes) || e.bytes < 1 || e.bytes > 1048576 || typeof e.ownClaim !== "boolean" || typeof e.sharedByYou !== "boolean" || !e.ownClaim && e.sharedByYou) return null;
74
+ if (effectParamOverrides(e.manifest) === null) return null;
75
+ seen.add(e.id); effects.push(e);
76
+ }
77
+ return { canWrite: body.canWrite, effects };
78
+ }
79
+ /** UI draft conversion only; the host independently checks the manifest and every override on apply. */
80
+ export function effectParamOverrides(manifest, draft = {}) {
81
+ if (!plain(manifest) || !plain(draft) || manifest.params !== undefined && !plain(manifest.params)) return null;
82
+ const defs = manifest.params || {}, out = Object.create(null);
83
+ if (Object.keys(defs).length > 16 || Object.keys(draft).some(k => !Object.hasOwn(defs, k))) return null;
84
+ for (const [key, d] of Object.entries(defs)) {
85
+ if (!/^[a-z][a-z0-9_]{0,31}$/.test(key) || !plain(d)) return null;
86
+ const supplied = Object.hasOwn(draft, key); let v = supplied ? draft[key] : d.default;
87
+ if (d.type === "int" || d.type === "number") {
88
+ if (supplied && typeof v === "string" && v.trim() !== "") v = Number(v);
89
+ if (typeof v !== "number" || !Number.isFinite(v) || !Number.isFinite(d.min) || !Number.isFinite(d.max) || v < d.min || v > d.max || d.type === "int" && !Number.isSafeInteger(v)) return null;
90
+ if (d.type === "number" && d.step !== undefined) {
91
+ const n = (v - d.min) / d.step;
92
+ if (!(d.step > 0) || !Number.isFinite(n) || Math.abs(n - Math.round(n)) > 1e-8) return null;
93
+ }
94
+ } else if (d.type === "bool") { if (typeof v !== "boolean") return null; }
95
+ else if (d.type === "enum") { if (!Array.isArray(d.values) || d.values.length > 16 || typeof v !== "string" || !d.values.includes(v)) return null; }
96
+ else if (d.type === "color") { if (typeof v !== "string" || !/^#[0-9a-fA-F]{6}$/.test(v)) return null; v = v.toLowerCase(); }
97
+ else return null;
98
+ if (v !== d.default) out[key] = v;
99
+ }
100
+ return out;
101
+ }
102
+ function Params({ manifest, draft, update, disabled }) {
103
+ const defs = plain(manifest.params) ? Object.entries(manifest.params).slice(0, 16) : [];
104
+ if (!defs.length) return null;
105
+ return html`<fieldset class="s-fx-params" disabled=${disabled}><legend>${tn("params")}</legend>${defs.map(([key, d]) => {
106
+ const value = Object.hasOwn(draft, key) ? draft[key] : d.default;
107
+ if (d.type === "bool") return html`<label key=${key} class="check"><input data-param=${key} type="checkbox" checked=${value} onChange=${e => update(key, e.target.checked)} />${key}</label>`;
108
+ if (d.type === "enum") return html`<label key=${key}>${key}<select data-param=${key} value=${value} onChange=${e => update(key, e.target.value)}>${d.values.map(v => html`<option key=${v} value=${v}>${v}</option>`)}</select></label>`;
109
+ return html`<label key=${key}>${key}<input data-param=${key} type=${d.type === "color" ? "color" : "number"} min=${d.min} max=${d.max} step=${d.type === "int" ? 1 : d.step || "any"} value=${value} onInput=${e => update(key, e.target.value)} /></label>`;
110
+ })}</fieldset>`;
111
+ }
112
+ async function call(path, init, signal) {
113
+ try {
114
+ const res = await fetch(path, { credentials: "same-origin", ...init, signal });
115
+ let body = null; try { body = await res.json(); } catch { /* unknown response refuses */ }
116
+ return { ok: res.ok && !body?.error, body };
117
+ } catch { return { ok: false, body: null }; }
118
+ }
119
+
120
+ function Profile({ value }) {
121
+ if (!value || value.error || !Array.isArray(value.records)) return html`<p class="sub">${tn("unavailable")}</p>`;
122
+ if (value.status === "unmeasured" || !value.records.length) return html`<p class="sub">${tn("unmeasured")}</p>`;
123
+ return html`<div class="s-fx-profile"><p class="sub">${tn("reported")}</p>${value.records.map((p, i) => {
124
+ const valid = p && ["standard", "constrained"].includes(p.device) && [p.fps_p50, p.fps_p10].every(n => typeof n === "number" && Number.isFinite(n) && n >= 0 && n <= 1000);
125
+ if (!valid) return html`<p key=${i} class="sub">${tn("unavailable")}</p>`;
126
+ return html`<div key=${i}><strong>${tn(p.device)}</strong><p class="sub">${tn("fps")}: ${p.fps_p50} / ${p.fps_p10} · ${tn("tasks")}: ${typeof p.long_task_p95 === "number" && Number.isFinite(p.long_task_p95) ? `${p.long_task_p95} ms` : tn("noTasks")}</p></div>`;
127
+ })}</div>`;
128
+ }
129
+
130
+ /** Admin changes remain authoritative on the host; canWrite is a display hint only. */
131
+ export function EffectsCard() {
132
+ const app = useStore(appStore), [view, setView] = useState(null), [error, setError] = useState(null), [busy, setBusy] = useState(false), [message, setMessage] = useState(null);
133
+ const [target, setTarget] = useState(""), [limit, setLimit] = useState(50), [history, setHistory] = useState({}), [drafts, setDrafts] = useState({});
134
+ const uploadInput = useRef(null);
135
+ const life = useRef({ alive: false, seq: 0, controller: null, pending: false });
136
+ const current = (seq) => life.current.alive && seq === life.current.seq;
137
+ const begin = () => { if (!life.current.alive || life.current.pending || publicLink()) return null; life.current.pending = true; life.current.controller?.abort(); const controller = new AbortController(), seq = ++life.current.seq; life.current.controller = controller; return { seq, signal: controller.signal }; };
138
+ const finish = seq => { if (current(seq)) { life.current.pending = false; setBusy(false); } };
139
+ const load = async (op) => {
140
+ const r = await call("/ui/effects", {}, op.signal); if (!current(op.seq)) return false;
141
+ const data = r.ok ? effectCatalog(r.body) : null; setView(data); setHistory({}); setError(data ? null : tn("failed")); return !!data;
142
+ };
143
+ const reload = async () => { const op = begin(); if (!op) return; setBusy(true); setMessage(null); await load(op); finish(op.seq); };
144
+ useEffect(() => {
145
+ life.current.alive = true;
146
+ if (!publicLink()) reload();
147
+ return () => { life.current.alive = false; life.current.seq++; life.current.controller?.abort(); };
148
+ }, []);
149
+ if (publicLink()) return html`<section class="card s-appearance s-effects"><h3>${tn("title")}</h3><p class="sub">${tn("public")}</p></section>`;
150
+ const names = app.ready ? [...new Set((app.instances || []).map(i => i?.name).filter(n => typeof n === "string" && n.length > 0 && n.length <= 128))] : [];
151
+ const selected = names.includes(target) ? target : "";
152
+ const change = async (path, body, method = "POST") => {
153
+ if (busy || !view?.canWrite) return;
154
+ const op = begin(); if (!op) return; setBusy(true); setMessage(null);
155
+ const r = await call(path, { method, ...(body === undefined ? {} : { headers: { "Content-Type": "application/json" }, body: JSON.stringify(body) }) }, op.signal);
156
+ if (!current(op.seq)) return;
157
+ if (!r.ok) setMessage(refusal(r.body, "actionFailed")); else { const refreshed = await load(op); if (current(op.seq)) setMessage(tn(refreshed ? "saved" : "savedRefresh")); }
158
+ finish(op.seq);
159
+ };
160
+ const upload = async (event) => {
161
+ const input = event.currentTarget, files = Array.from(input.files || []); input.value = "";
162
+ if (busy || !view?.canWrite || !files.length) return;
163
+ const op = begin(); if (!op) return; setBusy(true); setMessage(null);
164
+ try {
165
+ const body = await encodeEffectFolder(files, op.signal); if (!current(op.seq)) return;
166
+ const r = await call("/ui/effects/upload", { method: "POST", headers: { "Content-Type": "application/json" }, body }, op.signal);
167
+ if (!current(op.seq)) return;
168
+ if (!r.ok) setMessage(refusal(r.body, "uploadFailed")); else { const refreshed = await load(op); if (current(op.seq)) setMessage(tn(refreshed ? "saved" : "savedRefresh")); }
169
+ } catch { if (current(op.seq)) setMessage(tn("uploadFailed")); }
170
+ finish(op.seq);
171
+ };
172
+ const profile = async (id) => {
173
+ if (busy) return; const op = begin(); if (!op) return; setBusy(true);
174
+ const r = await call(`/ui/effects/profile/${id}`, {}, op.signal); if (!current(op.seq)) return;
175
+ if (r.ok && r.body && Array.isArray(r.body.records) && r.body.records.length <= 40) setHistory(h => ({ ...h, [id]: r.body })); else setMessage(tn("profileFailed"));
176
+ finish(op.seq);
177
+ };
178
+ const askDelete = async id => {
179
+ if (!view?.canWrite) return; const op = begin(); if (!op) return; setBusy(true);
180
+ const yes = await confirmDialog({ message: tn("deleteConfirm"), confirmLabel: tn("delete"), danger: true });
181
+ if (!current(op.seq)) return; finish(op.seq);
182
+ if (yes) await change(`/ui/effects/${id}`, undefined, "DELETE");
183
+ };
184
+ return html`<section class="card s-appearance s-effects"><${CardTitle} title=${tn("title")} topic="effects" /><p class="sub">${tn("lead")}</p>
185
+ <div class="s-fx-actions"><button type="button" class="btn" disabled=${busy} onClick=${reload}>${tn("retry")}</button>
186
+ ${view?.canWrite ? html`<button class="btn s-fx-choose" type="button" disabled=${busy} onClick=${() => uploadInput.current?.click()}>${tn("folder")}</button><input ref=${uploadInput} class="offscreen s-fx-upload" aria-label=${tn("folder")} tabindex="-1" type="file" webkitdirectory=${true} directory=${true} multiple disabled=${busy} onChange=${upload} />` : null}</div>
187
+ ${busy ? html`<p class="sub" role="status">${tn("loading")}</p>` : null}${error ? html`<p class="err" role="status">${error}</p>` : null}${message ? html`<p class="sub" role="status">${message}</p>` : null}
188
+ ${view ? html`<p class="sub">${tn(view.effects.length === 1 ? "one" : "count", view.effects.length)}${!view.canWrite ? ` · ${tn("readOnly")}` : ""}</p>` : null}
189
+ ${view?.canWrite ? html`<p class="sub">${tn("files")}</p><label class="s-fx-chat">${tn("chat")}<select class="s-fx-target" value=${selected} disabled=${busy} onChange=${e => setTarget(e.target.value)}><option value="">${tn("chooseChat")}</option>${names.map(n => html`<option key=${n} value=${n}>${n}</option>`)}</select></label>` : null}
190
+ ${view && !view.effects.length ? html`<p class="sub">${tn("empty")}</p>` : null}
191
+ <${OwnerEffects} catalog=${view} busy=${busy} />
192
+ <div class="s-fx-list">${(view?.effects || []).slice(0, limit).map(e => html`<article class="s-fx-item" key=${e.id} data-effect-id=${e.id}><h4>${e.manifest.title}</h4><p class="sub"><code>${e.id.slice(0, 8)}</code> · ${Math.ceil(e.bytes / 1024)} KiB · ${tn(e.ownClaim ? "yours" : "shared")}</p>
193
+ ${e.manifest.poster === "poster.png" ? html`<img class="s-fx-poster" src=${`/ui/effects/image/${e.id}/poster.png`} alt="" loading="lazy" />` : null}
194
+ <${Profile} value=${history[e.id] || e.profiles} />
195
+ ${view.canWrite ? html`<${Params} manifest=${e.manifest} draft=${drafts[e.id] || {}} disabled=${busy} update=${(key, value) => setDrafts(s => ({ ...s, [e.id]: { ...(s[e.id] || {}), [key]: value } }))} />` : null}<div class="s-fx-actions"><button class="btn" type="button" disabled=${busy} onClick=${() => profile(e.id)}>${tn("history")}</button>
196
+ ${view.canWrite ? html`<button class="btn s-fx-apply" type="button" disabled=${busy || !selected} onClick=${() => { const params = effectParamOverrides(e.manifest, drafts[e.id] || {}); if (params === null) setMessage(tn("badParams")); else change("/ui/effects/apply", { instance: selected, id: e.id, params }); }}>${tn("apply")}</button><button class="btn s-fx-remove" type="button" disabled=${busy || !selected} onClick=${() => change("/ui/effects/remove", { instance: selected, id: e.id })}>${tn("remove")}</button>` : null}
197
+ ${view.canWrite && e.ownClaim ? html`<button class="btn s-fx-delete" type="button" disabled=${busy} onClick=${() => askDelete(e.id)}>${tn("delete")}</button>` : null}</div>
198
+ ${view.canWrite && e.ownClaim ? html`<label class="check"><input class="s-fx-share" type="checkbox" checked=${e.sharedByYou} disabled=${busy} onChange=${event => change("/ui/effects/share", { id: e.id, shared: event.target.checked })} />${tn("share")}</label>` : null}
199
+ </article>`)}</div>
200
+ ${view && limit < view.effects.length ? html`<button class="btn" type="button" onClick=${() => setLimit(n => n + 50)}>${tn("more")}</button>` : null}
201
+ </section>`;
202
+ }
@@ -0,0 +1,117 @@
1
+ // #1920 phase 3 (P3b-2): Settings → Web → Guests — guest sign-in for people who are not admins. The switches (guest
2
+ // sign-in, guests may chat), the agents an open connection's guests may reach, and every guest account: who, on which
3
+ // platform, what it reaches now, its sessions and devices, with Sign out (at once) and Narrow (staged with the rest).
4
+ // Changes go through Review changes → Apply like every Settings edit: one that widens what guests reach waits for a
5
+ // fleet admin's confirmation (the server's settings-change.ts), one that narrows it applies at once.
6
+ import { html, useEffect, useMemo, useState } from "/assets/app-html.js";
7
+ import { t } from "/assets/app-i18n.js";
8
+ import { toast } from "/assets/ui-toast.js";
9
+ import { confirmDialog } from "/assets/ui-confirm.js";
10
+ import { api } from "./settings-confirm.js";
11
+ import "./settings-strings.js";
12
+
13
+ const tn = (k, ...v) => t(`settings.${k}`, ...v);
14
+ const PLATFORMS = { discord: "Discord", telegram: "Telegram" };
15
+ const own = (o, k) => !!o && Object.prototype.hasOwnProperty.call(o, k);
16
+ const copyLists = (m) => { const out = {}; for (const [k, v] of Object.entries(m || {})) if (Array.isArray(v)) out[k] = [...v]; return out; };
17
+ const sameList = (a, b) => (a || []).length === (b || []).length && (a || []).every((n) => (b || []).includes(n));
18
+ const accountKey = (g) => `${g.adapterId}:${g.userId}`;
19
+
20
+ /** What changed against the configuration, as the PUT /api/settings/fleet/web body's `guests` (null: nothing). */
21
+ export function guestsBody(original, f) {
22
+ const out = {};
23
+ if (f.enabled !== original.enabled) out.enabled = f.enabled ? true : null; // off is the default
24
+ if (f.chat !== original.chat) out.chat = f.chat ? null : false; // on is the default
25
+ for (const key of ["open_instances", "narrow"]) {
26
+ const was = original[key], now = f[key], diff = {};
27
+ for (const k of new Set([...Object.keys(was), ...Object.keys(now)])) {
28
+ const a = own(was, k) ? was[k] : undefined, b = own(now, k) ? now[k] : undefined;
29
+ if (a === undefined && b === undefined) continue;
30
+ if (a !== undefined && b !== undefined && sameList(a, b)) continue;
31
+ // open_instances: none named = the entry leaves. narrow: undefined = lifted; [] = narrowed to nothing.
32
+ diff[k] = b === undefined || (key === "open_instances" && !b.length) ? null : [...b];
33
+ }
34
+ if (Object.keys(diff).length) out[key] = diff;
35
+ }
36
+ return Object.keys(out).length ? out : null;
37
+ }
38
+
39
+ /** `away`: narrowed names it no longer reaches — listed (marked) only so they can be unticked; unticked, they leave. */
40
+ function Names({ id, all, value, onChange, away = new Set() }) {
41
+ const chosen = new Set(value || []);
42
+ return html`<div class="guest-names" id=${id} role="group">${all.map((n) => html`<label key=${n} class=${`check guest-name${away.has(n) ? " guest-away" : ""}`}>
43
+ <input type="checkbox" checked=${chosen.has(n)} onChange=${(e) => { const s = new Set(chosen); if (e.target.checked) s.add(n); else s.delete(n); onChange(all.filter((x) => s.has(x))); }} />
44
+ <span>${n}${away.has(n) ? html` <span class="note">${tn("guestNotReachable")}</span>` : null}</span></label>`)}</div>`;
45
+ }
46
+
47
+ export function GuestsCard({ ctx, staged, disabled = false }) {
48
+ const g = (ctx.fleet.web && ctx.fleet.web.guests) || {};
49
+ const original = useMemo(() => ({ enabled: g.enabled === true, chat: g.chat !== false, open_instances: copyLists(g.open_instances), narrow: copyLists(g.narrow) }), []);
50
+ const [f, setF] = useState(() => ({ ...original, open_instances: copyLists(original.open_instances), narrow: copyLists(original.narrow) }));
51
+ const [overview, setOverview] = useState(null), [failed, setFailed] = useState(false), [narrowing, setNarrowing] = useState(null), [msg, setMsg] = useState(null);
52
+ const load = async () => {
53
+ let r = null;
54
+ try { r = await api("/api/settings/guests"); } catch { r = null; }
55
+ if (r && r.ok && r.body) { setOverview(r.body); setFailed(false); } else setFailed(true);
56
+ };
57
+ useEffect(() => { load(); }, []);
58
+ const set = (patch) => setF((x) => ({ ...x, ...patch }));
59
+ const setList = (key, k, list) => setF((x) => { const m = { ...x[key] }; if (list === undefined) delete m[k]; else m[k] = list; return { ...x, [key]: m }; });
60
+ const review = () => {
61
+ const body = guestsBody(original, f);
62
+ if (body) ctx.stage("guests", { label: tn("guestsTitle"), impact: "now", request: { method: "PUT", url: "/api/settings/fleet/web", body: { guests: body } } });
63
+ else if (staged.has("guests")) ctx.unstage("guests");
64
+ setMsg(tn("readyApply"));
65
+ };
66
+ const signOut = async (guest) => {
67
+ const yes = await confirmDialog({ title: tn("guestSignOutTitle"), message: tn("guestSignOutConfirm", guest.name), confirmLabel: tn("guestSignOut"), danger: true });
68
+ if (!yes) return;
69
+ let r = null;
70
+ try { r = await api("/api/settings/guests/revoke", { method: "POST", body: JSON.stringify({ adapterId: guest.adapterId, userId: guest.userId }) }); } catch { r = null; }
71
+ if (r && r.ok) { toast(tn(r.body && r.body.durable === false ? "guestSignedOutNotDurable" : "guestSignedOut", guest.name)); load(); }
72
+ else toast(tn("guestSignOutFailed"), false);
73
+ };
74
+ const instances = (overview && overview.instances) || [];
75
+ const open = ((overview && overview.connections) || []).filter((c) => c.mode === "open");
76
+ const guests = (overview && overview.guests) || [];
77
+ const when = (ms) => (typeof ms === "number" ? tn("guestLastActive", new Date(ms).toLocaleString()) : tn("guestNotSignedIn"));
78
+ return html`<section class="card s-guests"><h3>${tn("guestsTitle")}</h3>
79
+ <p class="note">${tn("guestsHint")}</p>
80
+ <fieldset class="s-guest-fields" disabled=${disabled}>
81
+ <label class="check"><input id="guestsEnabled" type="checkbox" checked=${f.enabled} onChange=${(e) => set({ enabled: e.target.checked })} /><span>${tn("guestsEnabled")}</span></label>
82
+ <p class="note">${tn("guestsEnabledHint")}</p>
83
+ <label class="check"><input id="guestsChat" type="checkbox" checked=${f.chat} onChange=${(e) => set({ chat: e.target.checked })} /><span>${tn("guestsChat")}</span></label>
84
+ <p class="note">${tn("guestsChatHint")}</p>
85
+ ${open.map((c) => html`<div key=${c.id} class="field guest-open"><span class="label">${tn("guestsOpenFor", c.id, PLATFORMS[c.type] || c.type)}</span>
86
+ <p class="note">${tn("guestsOpenHint")}</p>
87
+ <${Names} id=${`guestsOpen-${c.id}`} all=${instances} value=${own(f.open_instances, c.id) ? f.open_instances[c.id] : []} onChange=${(list) => setList("open_instances", c.id, list)} /></div>`)}
88
+ <h4>${tn("guestsList")}</h4>
89
+ ${failed ? html`<p class="note" role="status">${tn("guestsLoadFailed")} <button type="button" class="btn btn-sm" onClick=${load}>${t("app.retry")}</button></p>`
90
+ : !overview ? html`<p class="note">${t("app.loading")}</p>`
91
+ : !guests.length ? html`<p class="note" id="guestsNone">${tn("guestsNone")}</p>`
92
+ : html`<ul class="guest-list">${guests.map((guest) => {
93
+ const k = accountKey(guest), narrowed = own(f.narrow, k) ? f.narrow[k] : undefined;
94
+ return html`<li key=${k} class="guest-row" data-account=${k}>
95
+ <div class="guest-who"><strong>${guest.name}</strong> <span class="note">${PLATFORMS[guest.platform] || guest.platform} · ${guest.userId}</span></div>
96
+ <div class="note guest-facts">${tn("guestFacts", guest.scope.length, guest.sessions, guest.devices)} · ${when(guest.lastSeen)}</div>
97
+ <div class="note guest-narrow">${narrowed === undefined ? tn("guestNotNarrowed") : tn("guestNarrowedTo", narrowed.length ? narrowed.join(", ") : tn("guestNothing"))}</div>
98
+ <div class="guest-actions">
99
+ <button type="button" class="btn btn-sm" aria-expanded=${narrowing === k ? "true" : "false"} onClick=${() => setNarrowing(narrowing === k ? null : k)}>${tn("guestNarrow")}</button>
100
+ ${narrowed !== undefined ? html`<button type="button" class="btn btn-sm" onClick=${() => setList("narrow", k, undefined)}>${tn("guestNarrowLift")}</button>` : null}
101
+ ${guest.sessions || guest.devices ? html`<button type="button" class="btn btn-sm btn-danger" onClick=${() => signOut(guest)}>${tn("guestSignOut")}</button>` : null}
102
+ </div>
103
+ ${narrowing === k ? (() => {
104
+ // #2104 review: only what this account can reach (narrowing never widens); a narrowed name it no longer
105
+ // reaches is listed to be unticked, marked.
106
+ const reach = guest.reach || guest.scope, away = new Set((narrowed || []).filter((n) => !reach.includes(n)));
107
+ return html`<div class="guest-narrow-edit"><p class="note">${tn("guestNarrowHint")}</p>
108
+ ${reach.length || away.size ? html`<${Names} id=${`guestNarrow-${k}`} all=${[...reach, ...away]} away=${away} value=${narrowed === undefined ? guest.scope : narrowed} onChange=${(list) => setList("narrow", k, list)} />`
109
+ : html`<p class="note">${tn("guestReachesNothing")}</p>`}</div>`;
110
+ })() : null}
111
+ </li>`;
112
+ })}</ul>`}
113
+ </fieldset>
114
+ <div class="save-row"><button type="button" class="btn btn-primary" id="guestsReview" disabled=${disabled} onClick=${review}>${tn("guestsReview")}</button>
115
+ ${msg ? html`<span class="note" role="status">${msg}</span>` : null}</div>
116
+ </section>`;
117
+ }
@@ -0,0 +1,25 @@
1
+ // #2056 B: a card title with a small "?" that opens the card's section of the docs, so Settings → Web appearance
2
+ // keeps one short line per control and the long explanations live in docs/web-dashboard(.zh-TW).md.
3
+ import { html } from "/assets/app-html.js";
4
+ import { lang, t } from "/assets/app-i18n.js";
5
+ import "./settings-strings.js";
6
+
7
+ /**
8
+ * #2056 B: where each card's long explanation lives — a heading of docs/web-dashboard(.zh-TW).md on GitHub, so the
9
+ * page keeps one short line per control and a small "?" for the rest.
10
+ */
11
+ const DOCS = { en: "https://github.com/songsid/AgEnD/blob/main/docs/web-dashboard.md", "zh-TW": "https://github.com/songsid/AgEnD/blob/main/docs/web-dashboard.zh-TW.md" };
12
+ export const LEARN_ANCHORS = {
13
+ colors: ["identity-colours", "識別色"], css: ["custom-css-settings--web-appearance", "自訂-css設定--網頁外觀"], themes: ["agents-themes", "agent-的主題"],
14
+ cards: ["identity-cards", "身分卡"], limits: ["card-upload-limits", "身分卡上傳限制"], widgets: ["widgets", "小工具"], mods: ["mods", "mod"],
15
+ effects: ["effects", "效果"], restore: ["restore-defaults", "還原預設值"],
16
+ };
17
+ export function learnHref(topic, language = lang()) {
18
+ const zh = language === "zh-TW";
19
+ return `${DOCS[zh ? "zh-TW" : "en"]}#${encodeURIComponent(LEARN_ANCHORS[topic][zh ? 1 : 0])}`;
20
+ }
21
+ /** A card title with its "?" link to the docs (a new tab; the page itself never fetches anything). */
22
+ export function CardTitle({ title, topic }) {
23
+ return html`<h3 class="s-ap-title"><span>${title}</span>${topic ? html`<a class="s-learn" href=${learnHref(topic)} target="_blank" rel="noopener noreferrer"
24
+ aria-label=${t("settings.learnMore", title)} title=${t("settings.learnMore", title)}>?</a>` : null}</h3>`;
25
+ }
@@ -97,15 +97,20 @@ export function effectiveSummary(live, config, fleetDefaults, classicDefaults) {
97
97
  return { backend, model, effort };
98
98
  }
99
99
 
100
- /** Group by first tag, "Other" last; within a group Running → Paused → Crashed → Stopped, then by name. */
101
- export function groupAgents(instances, live, filter) {
100
+ /**
101
+ * Group by `groupFor(name)` — #2172: the sidebar's rule (instance-nav groupOf: its first team, else its first tag); the
102
+ * first tag when not given — teams first, then tags, "Other" last; within a group Running → Paused → Crashed → Stopped,
103
+ * then by name. `labelOf(group)` sorts by what the heading says.
104
+ */
105
+ export function groupAgents(instances, live, filter, groupFor = (n) => groupOf(instances[n]), labelOf = (g) => g) {
102
106
  const needle = String(filter || "").trim().toLowerCase();
103
107
  let names = Object.keys(instances || {});
104
108
  if (needle) names = names.filter((n) => n.toLowerCase().includes(needle) || shortName(n, instances[n]).toLowerCase().includes(needle));
105
109
  const groups = {};
106
- for (const n of names) (groups[groupOf(instances[n])] ??= []).push(n);
110
+ for (const n of names) (groups[groupFor(n)] ??= []).push(n);
107
111
  const order = (n) => STATUS_ORDER[(live[n] && live[n].status) || "stopped"] ?? 9;
108
- return Object.keys(groups).sort((a, b) => (a === "Other" ? 1 : b === "Other" ? -1 : a.localeCompare(b)))
112
+ const team = (g) => g.charCodeAt(0) < 32; // a team's key (instance-nav TEAM_GROUP)
113
+ return Object.keys(groups).sort((a, b) => (a === "Other" ? 1 : b === "Other" ? -1 : team(a) !== team(b) ? (team(a) ? -1 : 1) : labelOf(a).localeCompare(labelOf(b))))
109
114
  .map((g) => ({ group: g, names: groups[g].sort((a, b) => order(a) - order(b) || a.localeCompare(b)) }));
110
115
  }
111
116
 
@@ -222,8 +227,12 @@ export function accessConfirmations(previousAccess, nextMode, nextUsers) {
222
227
  return out;
223
228
  }
224
229
 
225
- /** #1320: only a short positive numeric id is a Telegram private chat; Discord snowflakes and negative ids are groups. */
226
- export const webEchoIsGroup = (channelIdValue) => !/^\d{1,12}$/.test(String(channelIdValue || ""));
230
+ /**
231
+ * #2054: whether a ClassicBot room copies web chat, as the fleet decides it (ClassicChannelManager's load +
232
+ * classicWebEchoOn): an absent `web_echo` follows the fleet-wide `web.echo_to_channel` (default on); any other value
233
+ * echoes only when it is `true` (a `null`, `0` or string reads as off, as the manager reads it).
234
+ */
235
+ export const classicWebEchoEffective = (room, fleet) => (room?.web_echo === undefined ? fleet?.web?.echo_to_channel !== false : room.web_echo === true);
227
236
 
228
237
  // ── Developer YAML: the config subset's writer and reader, exactly as settings.html had them (round-trip verified) ──
229
238
  export function toYaml(value, indent = 0) {