@songsid/agend 2.2.1 → 2.3.0-beta.10
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.
- package/dist/agent-cli-instructions.md +5 -2
- package/dist/agent-cli.js +284 -2
- package/dist/agent-cli.js.map +1 -1
- package/dist/agent-endpoint.d.ts +2 -0
- package/dist/agent-endpoint.js +29 -0
- package/dist/agent-endpoint.js.map +1 -1
- package/dist/agent-file-read.d.ts +38 -0
- package/dist/agent-file-read.js +317 -0
- package/dist/agent-file-read.js.map +1 -0
- package/dist/agent-whats-new.d.ts +11 -0
- package/dist/agent-whats-new.js +29 -2
- package/dist/agent-whats-new.js.map +1 -1
- package/dist/atomic-write.js +9 -1
- package/dist/atomic-write.js.map +1 -1
- package/dist/auth-api.d.ts +30 -1
- package/dist/auth-api.js +61 -12
- package/dist/auth-api.js.map +1 -1
- package/dist/authority/authority-state.d.ts +38 -0
- package/dist/authority/authority-state.js +114 -0
- package/dist/authority/authority-state.js.map +1 -0
- package/dist/authority/session-authorizations.d.ts +43 -0
- package/dist/authority/session-authorizations.js +102 -0
- package/dist/authority/session-authorizations.js.map +1 -0
- package/dist/authority/web-authority.d.ts +120 -0
- package/dist/authority/web-authority.js +157 -0
- package/dist/authority/web-authority.js.map +1 -0
- package/dist/backend/antigravity.js +3 -3
- package/dist/backend/antigravity.js.map +1 -1
- package/dist/backend/claude-code.d.ts +2 -2
- package/dist/backend/claude-code.js +10 -5
- package/dist/backend/claude-code.js.map +1 -1
- package/dist/backend/launch-effort.js +1 -1
- package/dist/backend/launch-effort.js.map +1 -1
- package/dist/backend/muse.js +10 -2
- package/dist/backend/muse.js.map +1 -1
- package/dist/backend/types.d.ts +14 -0
- package/dist/backend/types.js +25 -0
- package/dist/backend/types.js.map +1 -1
- package/dist/backend-availability.d.ts +120 -0
- package/dist/backend-availability.js +396 -0
- package/dist/backend-availability.js.map +1 -0
- package/dist/backend-command.d.ts +21 -3
- package/dist/backend-command.js +23 -4
- package/dist/backend-command.js.map +1 -1
- package/dist/bundled-skills.d.ts +14 -0
- package/dist/bundled-skills.js +29 -0
- package/dist/bundled-skills.js.map +1 -0
- package/dist/cache-analysis.d.ts +47 -1
- package/dist/cache-analysis.js +68 -8
- package/dist/cache-analysis.js.map +1 -1
- package/dist/cache-ledger.d.ts +16 -3
- package/dist/cache-ledger.js +33 -13
- package/dist/cache-ledger.js.map +1 -1
- package/dist/cache-report-tool.d.ts +17 -0
- package/dist/cache-report-tool.js +40 -0
- package/dist/cache-report-tool.js.map +1 -0
- package/dist/cache-service.d.ts +70 -3
- package/dist/cache-service.js +178 -17
- package/dist/cache-service.js.map +1 -1
- package/dist/channel/adapters/discord.d.ts +9 -0
- package/dist/channel/adapters/discord.js +52 -2
- package/dist/channel/adapters/discord.js.map +1 -1
- package/dist/channel/adapters/telegram.d.ts +8 -1
- package/dist/channel/adapters/telegram.js +74 -4
- package/dist/channel/adapters/telegram.js.map +1 -1
- package/dist/channel/mcp-tools.js +48 -2
- package/dist/channel/mcp-tools.js.map +1 -1
- package/dist/channel/types.d.ts +21 -0
- package/dist/classic-channel-manager.d.ts +10 -5
- package/dist/classic-channel-manager.js +12 -9
- package/dist/classic-channel-manager.js.map +1 -1
- package/dist/cli.js +130 -31
- package/dist/cli.js.map +1 -1
- package/dist/config-validator.js +119 -4
- package/dist/config-validator.js.map +1 -1
- package/dist/daemon.d.ts +5 -0
- package/dist/daemon.js +273 -22
- package/dist/daemon.js.map +1 -1
- package/dist/decision-list.d.ts +25 -0
- package/dist/decision-list.js +30 -0
- package/dist/decision-list.js.map +1 -0
- package/dist/delivery-outbox.d.ts +10 -0
- package/dist/delivery-outbox.js +12 -1
- package/dist/delivery-outbox.js.map +1 -1
- package/dist/fleet-context.d.ts +15 -0
- package/dist/fleet-manager.d.ts +506 -11
- package/dist/fleet-manager.js +1784 -130
- package/dist/fleet-manager.js.map +1 -1
- package/dist/fleet-traffic.d.ts +74 -0
- package/dist/fleet-traffic.js +196 -0
- package/dist/fleet-traffic.js.map +1 -0
- package/dist/general-knowledge/skills/credential-profiles/SKILL.md +20 -5
- package/dist/general-knowledge/skills/cross-instance-messaging/SKILL.md +6 -1
- package/dist/general-knowledge/skills/multi-channel/SKILL.md +3 -2
- package/dist/general-knowledge/skills/persona-emoji/SKILL.md +1 -1
- package/dist/general-knowledge/skills/web-appearance/SKILL.md +492 -0
- package/dist/general-knowledge/skills/web-dashboard/SKILL.md +476 -0
- package/dist/general-knowledge/skills/worker-collaboration/SKILL.md +1 -1
- package/dist/human-delivery-receipts.d.ts +24 -1
- package/dist/human-delivery-receipts.js +64 -11
- package/dist/human-delivery-receipts.js.map +1 -1
- package/dist/instance-avatar.js +3 -0
- package/dist/instance-avatar.js.map +1 -1
- package/dist/instance-config-impact.js +6 -1
- package/dist/instance-config-impact.js.map +1 -1
- package/dist/instance-lifecycle.js +9 -4
- package/dist/instance-lifecycle.js.map +1 -1
- package/dist/instance-names.d.ts +4 -0
- package/dist/instance-names.js +12 -0
- package/dist/instance-names.js.map +1 -0
- package/dist/instructions.d.ts +12 -1
- package/dist/instructions.js +13 -2
- package/dist/instructions.js.map +1 -1
- package/dist/locale.js +100 -8
- package/dist/locale.js.map +1 -1
- package/dist/ls-attach-resolve.d.ts +95 -0
- package/dist/ls-attach-resolve.js +174 -0
- package/dist/ls-attach-resolve.js.map +1 -0
- package/dist/memory-hogs.d.ts +27 -0
- package/dist/memory-hogs.js +124 -0
- package/dist/memory-hogs.js.map +1 -0
- package/dist/needs-you-hub.d.ts +4 -0
- package/dist/needs-you-hub.js +31 -6
- package/dist/needs-you-hub.js.map +1 -1
- package/dist/needs-you.d.ts +1 -13
- package/dist/needs-you.js +3 -37
- package/dist/needs-you.js.map +1 -1
- package/dist/outbound-handlers.d.ts +15 -0
- package/dist/outbound-handlers.js +152 -11
- package/dist/outbound-handlers.js.map +1 -1
- package/dist/outbound-schemas.d.ts +228 -4
- package/dist/outbound-schemas.js +147 -1
- package/dist/outbound-schemas.js.map +1 -1
- package/dist/public-web-gateway.js +21 -1
- package/dist/public-web-gateway.js.map +1 -1
- package/dist/public-web-link.d.ts +9 -0
- package/dist/public-web-link.js +8 -0
- package/dist/public-web-link.js.map +1 -1
- package/dist/quickstart-api.d.ts +10 -1
- package/dist/quickstart-api.js +12 -4
- package/dist/quickstart-api.js.map +1 -1
- package/dist/reply-buttons-controller.d.ts +4 -0
- package/dist/reply-buttons-controller.js +2 -2
- package/dist/reply-buttons-controller.js.map +1 -1
- package/dist/repo-worktree-guard.d.ts +36 -0
- package/dist/repo-worktree-guard.js +143 -0
- package/dist/repo-worktree-guard.js.map +1 -0
- package/dist/restart-progress.d.ts +65 -1
- package/dist/restart-progress.js +236 -10
- package/dist/restart-progress.js.map +1 -1
- package/dist/rollout-index.d.ts +5 -0
- package/dist/rollout-index.js +26 -5
- package/dist/rollout-index.js.map +1 -1
- package/dist/scheduler/scheduler.d.ts +70 -1
- package/dist/scheduler/scheduler.js +256 -17
- package/dist/scheduler/scheduler.js.map +1 -1
- package/dist/service-installer.d.ts +2 -1
- package/dist/service-installer.js +18 -3
- package/dist/service-installer.js.map +1 -1
- package/dist/settings-api.d.ts +19 -0
- package/dist/settings-api.js +210 -2
- package/dist/settings-api.js.map +1 -1
- package/dist/settings-change.js +112 -0
- package/dist/settings-change.js.map +1 -1
- package/dist/settings-effect.js +32 -2
- package/dist/settings-effect.js.map +1 -1
- package/dist/settings-http-confirmation.js +5 -0
- package/dist/settings-http-confirmation.js.map +1 -1
- package/dist/tmux-manager.d.ts +5 -0
- package/dist/tmux-manager.js +16 -0
- package/dist/tmux-manager.js.map +1 -1
- package/dist/tool-permissions.d.ts +4 -6
- package/dist/tool-permissions.js +100 -2
- package/dist/tool-permissions.js.map +1 -1
- package/dist/topic-commands.d.ts +17 -7
- package/dist/topic-commands.js +39 -21
- package/dist/topic-commands.js.map +1 -1
- package/dist/topic-link.d.ts +128 -0
- package/dist/topic-link.js +269 -0
- package/dist/topic-link.js.map +1 -0
- package/dist/types.d.ts +61 -1
- package/dist/ui/app-effects.js +233 -0
- package/dist/ui/chat-ambient.js +307 -0
- package/dist/ui/chat-effect-posters.js +42 -0
- package/dist/ui/chat-effects.js +160 -0
- package/dist/ui/chat-live-replies.js +58 -0
- package/dist/ui/chat-render.js +9 -6
- package/dist/ui/chat-store.js +9 -3
- package/dist/ui/chat-strings.js +70 -2
- package/dist/ui/chat-thread.js +191 -37
- package/dist/ui/chat-tour.js +1 -1
- package/dist/ui/effect-trial-controller.js +124 -0
- package/dist/ui/effect-trial-offer.js +31 -0
- package/dist/ui/effect-trial-panel.js +40 -0
- package/dist/ui/fleet-cache.js +23 -1
- package/dist/ui/fleet-canvas-layout.js +111 -0
- package/dist/ui/fleet-canvas.js +525 -0
- package/dist/ui/fleet-org.js +27 -12
- package/dist/ui/fleet-traffic.js +343 -0
- package/dist/ui/fleet-usage.js +401 -0
- package/dist/ui/image-lightbox.js +1 -1
- package/dist/ui/panel-chat.js +203 -57
- package/dist/ui/panel-details.js +20 -6
- package/dist/ui/panel-fleet.js +43 -18
- package/dist/ui/panel-log.js +6 -6
- package/dist/ui/panel-needs.js +3 -3
- package/dist/ui/panel-settings.js +34 -13
- package/dist/ui/preview-panel.js +25 -9
- package/dist/ui/preview.js +3 -2
- package/dist/ui/settings-appearance.js +349 -0
- package/dist/ui/settings-catalog.js +92 -0
- package/dist/ui/settings-dialogs.js +40 -12
- package/dist/ui/settings-effects.js +202 -0
- package/dist/ui/settings-guests.js +117 -0
- package/dist/ui/settings-learn.js +25 -0
- package/dist/ui/settings-model.js +15 -6
- package/dist/ui/settings-mods.js +271 -0
- package/dist/ui/settings-owner-effects.js +80 -0
- package/dist/ui/settings-strings.js +333 -10
- package/dist/ui/settings-web-authority.js +91 -0
- package/dist/ui/shared/agend-auth.js +28 -4
- package/dist/ui/shared/agent-widget.js +248 -0
- package/dist/ui/shared/app-i18n.js +6 -2
- package/dist/ui/shared/app-prefs.js +34 -13
- package/dist/ui/shared/app-route.js +24 -2
- package/dist/ui/shared/app-session.js +3 -1
- package/dist/ui/shared/app-shell.js +102 -23
- package/dist/ui/shared/app-store.js +28 -1
- package/dist/ui/shared/app-stream.js +14 -7
- package/dist/ui/shared/app-themes.js +206 -0
- package/dist/ui/shared/app.css +565 -4
- package/dist/ui/shared/app.js +23 -10
- package/dist/ui/shared/card-strings.js +32 -0
- package/dist/ui/shared/effect-app-runtime.js +297 -0
- package/dist/ui/shared/effect-baseline.js +64 -0
- package/dist/ui/shared/effect-chat-runtime.js +167 -0
- package/dist/ui/shared/effect-cursor.js +125 -0
- package/dist/ui/shared/effect-frame.js +146 -0
- package/dist/ui/shared/effect-geometry.js +137 -0
- package/dist/ui/shared/effect-host-style.js +10 -0
- package/dist/ui/shared/effect-owner-regions.js +74 -0
- package/dist/ui/shared/effect-owner-source.js +115 -0
- package/dist/ui/shared/effect-policy.js +151 -0
- package/dist/ui/shared/effect-poster-reader.js +98 -0
- package/dist/ui/shared/effect-posters.js +117 -0
- package/dist/ui/shared/effect-profiler.js +184 -0
- package/dist/ui/shared/effect-profiles.js +65 -0
- package/dist/ui/shared/effect-registry.js +535 -0
- package/dist/ui/shared/effect-runtime-loader.js +234 -0
- package/dist/ui/shared/effect-safety.js +153 -0
- package/dist/ui/shared/effect-stock-snapshot.js +43 -0
- package/dist/ui/shared/effect-stock-trust.js +217 -0
- package/dist/ui/shared/effect-trial-shadow.js +62 -0
- package/dist/ui/shared/effect-trial-source.js +103 -0
- package/dist/ui/shared/effect-trial-stage.js +170 -0
- package/dist/ui/shared/effect-trial.css +40 -0
- package/dist/ui/shared/fx-motion.js +67 -0
- package/dist/ui/shared/header-tools.js +1 -1
- package/dist/ui/shared/identity-card.js +273 -0
- package/dist/ui/shared/identity-color.js +261 -0
- package/dist/ui/shared/instance-nav.js +215 -41
- package/dist/ui/shared/owner-css.js +221 -0
- package/dist/ui/shared/owner-effect-prefs.js +117 -0
- package/dist/ui/shared/panel-view.js +103 -10
- package/dist/ui/shared/signin.js +2 -0
- package/dist/ui/shared/tokens.css +9 -3
- package/dist/ui/shared/ui-dialog.js +1 -1
- package/dist/ui/shared/ui-icons.js +3 -0
- package/dist/ui/shared/ui-menu.js +1 -1
- package/dist/ui/shared/ui-toast.js +1 -1
- package/dist/ui/shared/usage-dialog.js +22 -2
- package/dist/ui/shared/view-strings.js +98 -2
- package/dist/ui/shared/web-authorize.js +151 -0
- package/dist/ui/signin.html +1 -0
- package/dist/ui/web-terminal/terminal.css +4 -4
- package/dist/usage/providers.d.ts +28 -0
- package/dist/usage/providers.js +126 -36
- package/dist/usage/providers.js.map +1 -1
- package/dist/usage-report.d.ts +158 -0
- package/dist/usage-report.js +226 -0
- package/dist/usage-report.js.map +1 -0
- package/dist/usage-rollup.d.ts +51 -0
- package/dist/usage-rollup.js +168 -0
- package/dist/usage-rollup.js.map +1 -0
- package/dist/view-api.d.ts +68 -0
- package/dist/view-api.js +249 -8
- package/dist/view-api.js.map +1 -1
- package/dist/web-ambient.d.ts +72 -0
- package/dist/web-ambient.js +161 -0
- package/dist/web-ambient.js.map +1 -0
- package/dist/web-api.d.ts +111 -5
- package/dist/web-api.js +1235 -58
- package/dist/web-api.js.map +1 -1
- package/dist/web-appearance-limits.d.ts +82 -0
- package/dist/web-appearance-limits.js +83 -0
- package/dist/web-appearance-limits.js.map +1 -0
- package/dist/web-appearance-policy.d.ts +64 -0
- package/dist/web-appearance-policy.js +87 -0
- package/dist/web-appearance-policy.js.map +1 -0
- package/dist/web-appearance-tools.d.ts +53 -0
- package/dist/web-appearance-tools.js +124 -0
- package/dist/web-appearance-tools.js.map +1 -0
- package/dist/web-appearance-view.d.ts +124 -0
- package/dist/web-appearance-view.js +278 -0
- package/dist/web-appearance-view.js.map +1 -0
- package/dist/web-appearance.d.ts +93 -0
- package/dist/web-appearance.js +373 -0
- package/dist/web-appearance.js.map +1 -0
- package/dist/web-asset-parts.d.ts +93 -0
- package/dist/web-asset-parts.js +133 -0
- package/dist/web-asset-parts.js.map +1 -0
- package/dist/web-auth.js +20 -2
- package/dist/web-auth.js.map +1 -1
- package/dist/web-card.d.ts +110 -0
- package/dist/web-card.js +266 -0
- package/dist/web-card.js.map +1 -0
- package/dist/web-channel-echo.d.ts +7 -1
- package/dist/web-channel-echo.js +6 -2
- package/dist/web-channel-echo.js.map +1 -1
- package/dist/web-chat-history.d.ts +59 -0
- package/dist/web-chat-history.js +102 -4
- package/dist/web-chat-history.js.map +1 -1
- package/dist/web-commands.d.ts +1 -0
- package/dist/web-effect-bootstrap.d.ts +12 -0
- package/dist/web-effect-bootstrap.js +49 -0
- package/dist/web-effect-bootstrap.js.map +1 -0
- package/dist/web-effect-bundle.d.ts +28 -0
- package/dist/web-effect-bundle.js +171 -0
- package/dist/web-effect-bundle.js.map +1 -0
- package/dist/web-effect-canonical.d.ts +8 -0
- package/dist/web-effect-canonical.js +27 -0
- package/dist/web-effect-canonical.js.map +1 -0
- package/dist/web-effect-library.d.ts +114 -0
- package/dist/web-effect-library.js +530 -0
- package/dist/web-effect-library.js.map +1 -0
- package/dist/web-effect-playback.d.ts +30 -0
- package/dist/web-effect-playback.js +71 -0
- package/dist/web-effect-playback.js.map +1 -0
- package/dist/web-effect-poster.d.ts +8 -0
- package/dist/web-effect-poster.js +52 -0
- package/dist/web-effect-poster.js.map +1 -0
- package/dist/web-effect-profiles.d.ts +118 -0
- package/dist/web-effect-profiles.js +180 -0
- package/dist/web-effect-profiles.js.map +1 -0
- package/dist/web-effect-service.d.ts +93 -0
- package/dist/web-effect-service.js +335 -0
- package/dist/web-effect-service.js.map +1 -0
- package/dist/web-effect-tools.d.ts +48 -0
- package/dist/web-effect-tools.js +129 -0
- package/dist/web-effect-tools.js.map +1 -0
- package/dist/web-effect-trials.d.ts +86 -0
- package/dist/web-effect-trials.js +204 -0
- package/dist/web-effect-trials.js.map +1 -0
- package/dist/web-effect-upload.d.ts +27 -0
- package/dist/web-effect-upload.js +185 -0
- package/dist/web-effect-upload.js.map +1 -0
- package/dist/web-effect-worker.d.ts +8 -0
- package/dist/web-effect-worker.js +170 -0
- package/dist/web-effect-worker.js.map +1 -0
- package/dist/web-effect.d.ts +103 -0
- package/dist/web-effect.js +153 -0
- package/dist/web-effect.js.map +1 -0
- package/dist/web-guest-access.d.ts +125 -0
- package/dist/web-guest-access.js +234 -0
- package/dist/web-guest-access.js.map +1 -0
- package/dist/web-guest-rooms.d.ts +82 -0
- package/dist/web-guest-rooms.js +135 -0
- package/dist/web-guest-rooms.js.map +1 -0
- package/dist/web-identity-color.d.ts +87 -0
- package/dist/web-identity-color.js +209 -0
- package/dist/web-identity-color.js.map +1 -0
- package/dist/web-identity-palette.d.ts +113 -0
- package/dist/web-identity-palette.js +84 -0
- package/dist/web-identity-palette.js.map +1 -0
- package/dist/web-image-meta.d.ts +25 -0
- package/dist/web-image-meta.js +200 -0
- package/dist/web-image-meta.js.map +1 -0
- package/dist/web-login.d.ts +47 -0
- package/dist/web-login.js +115 -0
- package/dist/web-login.js.map +1 -1
- package/dist/web-mod-agent.d.ts +50 -0
- package/dist/web-mod-agent.js +207 -0
- package/dist/web-mod-agent.js.map +1 -0
- package/dist/web-mod-bundle.d.ts +157 -0
- package/dist/web-mod-bundle.js +529 -0
- package/dist/web-mod-bundle.js.map +1 -0
- package/dist/web-mod-export.d.ts +68 -0
- package/dist/web-mod-export.js +100 -0
- package/dist/web-mod-export.js.map +1 -0
- package/dist/web-mod-store.d.ts +289 -0
- package/dist/web-mod-store.js +638 -0
- package/dist/web-mod-store.js.map +1 -0
- package/dist/web-org.d.ts +3 -3
- package/dist/web-org.js +11 -17
- package/dist/web-org.js.map +1 -1
- package/dist/web-owner-css-store.d.ts +180 -0
- package/dist/web-owner-css-store.js +384 -0
- package/dist/web-owner-css-store.js.map +1 -0
- package/dist/web-owner-css.d.ts +58 -0
- package/dist/web-owner-css.js +552 -0
- package/dist/web-owner-css.js.map +1 -0
- package/dist/web-prefs.d.ts +62 -0
- package/dist/web-prefs.js +232 -36
- package/dist/web-prefs.js.map +1 -1
- package/dist/web-preview.d.ts +15 -3
- package/dist/web-preview.js +28 -9
- package/dist/web-preview.js.map +1 -1
- package/dist/web-returning-device.d.ts +41 -0
- package/dist/web-returning-device.js +57 -5
- package/dist/web-returning-device.js.map +1 -1
- package/dist/web-session-authorization.d.ts +132 -0
- package/dist/web-session-authorization.js +193 -0
- package/dist/web-session-authorization.js.map +1 -0
- package/dist/web-session.d.ts +70 -10
- package/dist/web-session.js +137 -22
- package/dist/web-session.js.map +1 -1
- package/dist/web-settings-patch.d.ts +67 -2
- package/dist/web-settings-patch.js +196 -1
- package/dist/web-settings-patch.js.map +1 -1
- package/dist/web-settings-tools.d.ts +89 -0
- package/dist/web-settings-tools.js +270 -0
- package/dist/web-settings-tools.js.map +1 -0
- package/dist/web-shell-routes.d.ts +2 -2
- package/dist/web-shell-routes.js +2 -2
- package/dist/web-shell-routes.js.map +1 -1
- package/dist/web-theme-store.d.ts +257 -0
- package/dist/web-theme-store.js +627 -0
- package/dist/web-theme-store.js.map +1 -0
- package/dist/web-theme.d.ts +123 -0
- package/dist/web-theme.js +416 -0
- package/dist/web-theme.js.map +1 -0
- package/dist/web-widget.d.ts +72 -0
- package/dist/web-widget.js +245 -0
- package/dist/web-widget.js.map +1 -0
- package/dist/workflow-templates/default.md +1 -1
- package/package.json +1 -1
- package/templates/systemd.service.ejs +8 -0
|
@@ -5,8 +5,9 @@ import { html, useEffect, useLayoutEffect, useRef, useState } from "./app-html.j
|
|
|
5
5
|
import { Icon } from "./ui-icons.js";
|
|
6
6
|
import { Toasts } from "./ui-toast.js";
|
|
7
7
|
import { ConfirmHost } from "./ui-confirm.js";
|
|
8
|
+
import { ColorDialogHost } from "./identity-color.js";
|
|
8
9
|
import { t, lang, setLang, onLang } from "./app-i18n.js";
|
|
9
|
-
import { appStore, createStore, useStore } from "./app-store.js";
|
|
10
|
+
import { appStore, createStore, guestBadge, guestPage, useStore } from "./app-store.js";
|
|
10
11
|
import { navStore } from "./app-nav.js";
|
|
11
12
|
import { chatPath, viewPath, settingsPath, routeKey, NEEDS_PATH, detailsPath, fleetPath } from "./app-route.js";
|
|
12
13
|
import { SessionMenu } from "./app-session.js";
|
|
@@ -14,6 +15,8 @@ import { savePref } from "./app-prefs.js";
|
|
|
14
15
|
import { ErrorState, Skeleton } from "./ui-states.js";
|
|
15
16
|
import { InstanceNav, RosterNav, namesStore, setNamesPrimary } from "./instance-nav.js";
|
|
16
17
|
import { UsageRail, usageRailStore } from "./header-tools.js";
|
|
18
|
+
import { OwnerCssKeepBar } from "./owner-css.js";
|
|
19
|
+
import { AgentWidget, fleetBinds, sidebarWidget, sidebarWidgetStore } from "./agent-widget.js";
|
|
17
20
|
|
|
18
21
|
/** Shell state shared with panels: the phone drawer, the collapsed sidebar, extensions a panel module adds. */
|
|
19
22
|
export const shellStore = createStore({ drawer: false, collapsed: readCollapsed(), keyboard: false, dialog: null, side: null });
|
|
@@ -36,6 +39,25 @@ let newInstanceOpener = null;
|
|
|
36
39
|
export function setNewInstanceOpener(fn) { newInstanceOpener = fn; }
|
|
37
40
|
export function requestNewInstance() { if (newInstanceOpener) newInstanceOpener(); }
|
|
38
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
|
+
|
|
39
61
|
/** An app-level dialog (New instance): rendered by the shell until closed, whatever panel is showing. */
|
|
40
62
|
export function showDialog(Component, props = {}) { shellStore.set({ dialog: { Component, props } }); }
|
|
41
63
|
export function closeDialog() { shellStore.set({ dialog: null }); }
|
|
@@ -175,7 +197,7 @@ function SidebarResize() {
|
|
|
175
197
|
handle.addEventListener("pointercancel", cancel);
|
|
176
198
|
handle.addEventListener("lostpointercapture", cancel);
|
|
177
199
|
};
|
|
178
|
-
return html`<div class="side-resize" role="separator" aria-orientation="vertical" tabindex="0" aria-controls="sidebar"
|
|
200
|
+
return html`<div data-trust="navigation" class="side-resize" role="separator" aria-orientation="vertical" tabindex="0" aria-controls="sidebar"
|
|
179
201
|
aria-label=${t("app.resizeSidebar")} title=${t("app.resizeSidebarHint")} aria-valuemin=${String(SIDEBAR_W.min)}
|
|
180
202
|
aria-valuemax=${String(clampSidebarWidth(Infinity, mainBudget()))} aria-valuenow=${String(now)}
|
|
181
203
|
onKeyDown=${onKey} onPointerDown=${onDown} ondblclick=${() => setSidebarWidth(null)}></div>`;
|
|
@@ -244,6 +266,7 @@ export function signInHref() {
|
|
|
244
266
|
function Sidebar({ route, onNewInstance, viewOnly }) {
|
|
245
267
|
const app = useStore(appStore);
|
|
246
268
|
const shell = useStore(shellStore);
|
|
269
|
+
const canCreate = useCanCreate();
|
|
247
270
|
// #1675: the Fleet panel is "Fleet overview"'s (one current row); the "Fleet" row is current on an instance's Details.
|
|
248
271
|
const on = (panel) => !!route && (panel === "fleet" ? route.panel === "details" : route.panel === panel);
|
|
249
272
|
const openInstance = openInstanceOf(route);
|
|
@@ -255,39 +278,56 @@ function Sidebar({ route, onNewInstance, viewOnly }) {
|
|
|
255
278
|
// View-only (an anonymous reader, #1408 §3): View and a way to sign in — nothing that needs a session is rendered.
|
|
256
279
|
if (viewOnly) {
|
|
257
280
|
return html`<aside id="sidebar" class="sidebar" aria-label=${t("app.menu")}>
|
|
258
|
-
<div class="side-head">
|
|
281
|
+
<div class="side-head" data-trust="navigation">
|
|
259
282
|
<a class="brand" href="/view" onClick=${closeDrawer}>${t("app.brand")}</a>
|
|
260
283
|
<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>
|
|
261
284
|
</div>
|
|
262
|
-
<nav class="side-nav" aria-label=${t("app.menu")}>${navLink("view", "/view", "view", t("app.view"))}</nav>
|
|
285
|
+
<nav data-trust="navigation" class="side-nav" aria-label=${t("app.menu")}>${navLink("view", "/view", "view", t("app.view"))}</nav>
|
|
263
286
|
${shell.side ? html`<${shell.side.Component} />` : html`<${ViewOnlyNav} route=${route} />`}
|
|
264
|
-
<div class="side-foot">
|
|
287
|
+
<div class="side-foot" data-trust="navigation">
|
|
265
288
|
<a class="side-row" href=${signInHref()}><${Icon} name="user" /><span>${t("app.signIn")}</span></a>
|
|
266
289
|
<${PrefsPopover} />
|
|
267
290
|
</div>
|
|
268
291
|
<${SidebarResize} />
|
|
269
292
|
</aside>`;
|
|
270
293
|
}
|
|
294
|
+
// #1920 phase 3: a guest's sidebar — its own agents to pick from, who it is signed in as, and its session; no fleet,
|
|
295
|
+
// Needs you, View, Settings, new agent or widget (each is an admin's, and the server refuses a guest them anyway).
|
|
296
|
+
if (guestPage()) {
|
|
297
|
+
return html`<aside id="sidebar" class="sidebar" aria-label=${t("app.menu")}>
|
|
298
|
+
<div class="side-head" data-trust="navigation">
|
|
299
|
+
<a class="brand" href="/ui" onClick=${closeDrawer}>${t("app.brand")}</a>
|
|
300
|
+
<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>
|
|
301
|
+
</div>
|
|
302
|
+
<${InstanceNav} route=${route} items=${app.instances} loaded=${app.ready !== false} exec=${app.exec} awaiting=${app.awaiting} onPick=${closeDrawer} />
|
|
303
|
+
<div class="side-foot" data-trust="navigation">
|
|
304
|
+
<${GuestChip} />
|
|
305
|
+
<${SessionMenu} />
|
|
306
|
+
</div>
|
|
307
|
+
<${SidebarResize} />
|
|
308
|
+
</aside>`;
|
|
309
|
+
}
|
|
271
310
|
// #1675: the whole fleet, on its Org chart (#1389), from anywhere — top left, so at the top of a phone's drawer too. The
|
|
272
311
|
// "Fleet" row under it goes to the open instance's Details (#1523 N2); this one is the Fleet panel, any of its tabs.
|
|
273
312
|
const onFleet = !!route && route.panel === "fleet";
|
|
274
313
|
const overview = html`<a class=${`side-row side-overview${onFleet ? " active" : ""}`} href=${fleetPath("org")}
|
|
275
314
|
aria-current=${onFleet ? "page" : undefined} onClick=${closeDrawer}><${Icon} name="org" /><span class="grow">${t("app.fleetOverview")}</span></a>`;
|
|
276
315
|
return html`<aside id="sidebar" class="sidebar" aria-label=${t("app.menu")}>
|
|
277
|
-
<div class="side-head">
|
|
316
|
+
<div class="side-head" data-trust="navigation">
|
|
278
317
|
<a class="brand" href="/ui" onClick=${closeDrawer}>${t("app.brand")}</a>
|
|
279
318
|
<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>
|
|
280
|
-
<button type="button" class="icon-btn" onClick=${onNewInstance} aria-label=${t("app.newInstance")} title=${t("app.newInstance")}><${Icon} name="plus" /></button>
|
|
281
319
|
</div>
|
|
282
|
-
<nav class="side-nav" aria-label=${t("app.menu")}>
|
|
320
|
+
<nav data-trust="navigation" class="side-nav" aria-label=${t("app.menu")}>
|
|
283
321
|
${overview}
|
|
284
322
|
${navLink("needs", NEEDS_PATH, "inbox", t("app.needsNav"), needsCount)}
|
|
285
323
|
${navLink("fleet", openInstance ? detailsPath(openInstance) : "/ui/fleet", "fleet", t("app.fleet"))}
|
|
286
324
|
${navLink("view", "/view", "view", t("app.view"))}
|
|
325
|
+
${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}
|
|
287
326
|
</nav>
|
|
288
327
|
${shell.side ? html`<${shell.side.Component} />` : html`<${InstanceNav} route=${route} items=${app.instances} loaded=${app.ready !== false}
|
|
289
328
|
exec=${app.exec} awaiting=${app.awaiting} onPick=${closeDrawer} />`}
|
|
290
|
-
|
|
329
|
+
<${SidebarWidget} app=${app} />
|
|
330
|
+
<div class="side-foot" data-trust="navigation">
|
|
291
331
|
${navLink("settings", settingsPath(), "settings", t("app.settings"))}
|
|
292
332
|
<${SessionMenu} />
|
|
293
333
|
</div>
|
|
@@ -295,6 +335,24 @@ function Sidebar({ route, onNewInstance, viewOnly }) {
|
|
|
295
335
|
</aside>`;
|
|
296
336
|
}
|
|
297
337
|
|
|
338
|
+
/** #1920 phase 3: "Guest · <name> (<platform>)" — who this guest's page is signed in as; what a guest sees, in its title. */
|
|
339
|
+
const PLATFORM_NAMES = { discord: "Discord", telegram: "Telegram" };
|
|
340
|
+
function GuestChip() {
|
|
341
|
+
const { name, platform } = guestBadge();
|
|
342
|
+
const where = PLATFORM_NAMES[platform] || platform;
|
|
343
|
+
const label = name ? (where ? t("app.guestAsOn", name, where) : t("app.guestAs", name)) : t("app.guestChip");
|
|
344
|
+
// Inside .side-foot (a marked, stock-trusted element): no trust marker of its own — effect-stock-trust's closed inventory
|
|
345
|
+
// refuses any element it does not know, which would hide a guest's static effect posters (#2101 review).
|
|
346
|
+
return html`<p class="guest-chip" title=${t("app.guestHint")}><${Icon} name="user" size=${14} /><span class="grow">${label}</span></p>`;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/** #1629 r4-4: the person's own sidebar widget (their synced preference), with the fleet's counts. */
|
|
350
|
+
function SidebarWidget({ app }) {
|
|
351
|
+
useStore(sidebarWidgetStore);
|
|
352
|
+
const widget = sidebarWidget();
|
|
353
|
+
return widget ? html`<div class="side-widget" data-trust="widget"><${AgentWidget} slot="sidebar" widget=${widget} binds=${fleetBinds(app.instances, app.exec, app.awaiting)} /></div>` : null;
|
|
354
|
+
}
|
|
355
|
+
|
|
298
356
|
/** The anonymous View reader's list: View's roster read (no status frames without a session), the same component. */
|
|
299
357
|
function ViewOnlyNav({ route }) {
|
|
300
358
|
return html`<${RosterNav} route=${route} viewOnly=${true} onPick=${closeDrawer} />`;
|
|
@@ -328,7 +386,7 @@ export function DevicePrefs({ labels = false } = {}) {
|
|
|
328
386
|
function PrefsPopover() {
|
|
329
387
|
return html`<details class="prefs-pop">
|
|
330
388
|
<summary class="icon-btn" aria-label=${t("app.devicePrefs")} title=${t("app.devicePrefs")}><${Icon} name="sun" /></summary>
|
|
331
|
-
<div class="pop" role="group" aria-label=${t("app.devicePrefs")}><${DevicePrefs} labels=${true} /></div>
|
|
389
|
+
<div data-trust="popover" class="pop" role="group" aria-label=${t("app.devicePrefs")}><${DevicePrefs} labels=${true} /></div>
|
|
332
390
|
</details>`;
|
|
333
391
|
}
|
|
334
392
|
|
|
@@ -336,14 +394,17 @@ function PrefsPopover() {
|
|
|
336
394
|
* The header every panel shows: ☰ (phone, or a collapsed sidebar), its title and status, then its actions. `nav`
|
|
337
395
|
* (#1523 N2): an instance page's view switch — beside the title on a wide screen, a row of tabs under it on a phone.
|
|
338
396
|
*/
|
|
339
|
-
export function PanelHeader({ title, sub, children, headingRef, nav }) {
|
|
397
|
+
export function PanelHeader({ title, sub, children, headingRef, nav, notice }) {
|
|
340
398
|
const shell = useStore(shellStore);
|
|
341
|
-
|
|
399
|
+
const canCreate = useCanCreate() && !!newInstanceOpener;
|
|
400
|
+
return html`<header data-trust="header" class=${`panel-head${shell.collapsed ? " collapsed" : ""}`}>
|
|
342
401
|
<button type="button" id="sbOpen" class="icon-btn sb-open" onClick=${() => (narrow() ? openDrawer() : toggleSidebar())}
|
|
343
402
|
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>
|
|
403
|
+
${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}
|
|
344
404
|
<div class="panel-title"><h1 ref=${headingRef} tabindex="-1">${title}</h1>${sub ? html`<div class="panel-sub">${sub}</div>` : null}</div>
|
|
345
405
|
${nav || null}
|
|
346
406
|
<div class="panel-actions">${children}</div>
|
|
407
|
+
${notice || null}
|
|
347
408
|
</header>`;
|
|
348
409
|
}
|
|
349
410
|
|
|
@@ -368,12 +429,14 @@ function BottomTabs({ route, viewOnly }) {
|
|
|
368
429
|
try { lastView = localStorage.getItem("agend_last_view"); } catch { /* none */ }
|
|
369
430
|
const viewHref = route && (route.panel === "view" || route.panel === "log" || route.panel === "details") && route.instance ? viewPath(route.instance) : viewPath(lastView);
|
|
370
431
|
if (viewOnly) {
|
|
371
|
-
return html`<nav class="tabs" aria-label=${t("app.menu")}>
|
|
432
|
+
return html`<nav data-trust="navigation" class="tabs" aria-label=${t("app.menu")}>
|
|
372
433
|
${tab(viewHref, "view", t("app.view"), route && route.panel === "view")}
|
|
373
434
|
${tab(signInHref(), "user", t("app.signIn"), false)}
|
|
374
435
|
</nav>`;
|
|
375
436
|
}
|
|
376
|
-
|
|
437
|
+
// #1920: a guest has only its chats (the drawer lists them): no tabs to switch between.
|
|
438
|
+
if (guestPage()) return null;
|
|
439
|
+
return html`<nav data-trust="navigation" class="tabs" aria-label=${t("app.menu")}>
|
|
377
440
|
${tab(chatHref, "chat", t("app.chat"), route && route.panel === "chat", anyAwaiting)}
|
|
378
441
|
${tab(NEEDS_PATH, "inbox", t("app.needsTab"), route && route.panel === "needs", (app.needs || []).length, t("app.needsCount", (app.needs || []).length))}
|
|
379
442
|
${tab(open ? detailsPath(open) : "/ui/fleet", "fleet", t("app.fleet"), route && (route.panel === "fleet" || route.panel === "details"))}
|
|
@@ -386,7 +449,7 @@ function ConnectionLine() {
|
|
|
386
449
|
const { connection, hydration, retryHydration } = useStore(appStore);
|
|
387
450
|
// A late catch-up that is not done: the chat may be missing what was open before it loaded (#1425 review).
|
|
388
451
|
if (hydration === "retrying" || hydration === "failed") {
|
|
389
|
-
return html`<div class="conn" role="status">${hydration === "failed" ? t("app.hydrateFailed") : t("app.hydrating")}
|
|
452
|
+
return html`<div data-trust="status" class="conn" role="status">${hydration === "failed" ? t("app.hydrateFailed") : t("app.hydrating")}
|
|
390
453
|
${hydration === "failed" && retryHydration ? html` <button type="button" class="btn btn-sm" onClick=${() => retryHydration()}>${t("app.retry")}</button>` : null}</div>`;
|
|
391
454
|
}
|
|
392
455
|
if (connection === "reconnecting") return html`<${Reconnecting} />`;
|
|
@@ -400,7 +463,7 @@ function Reconnecting() {
|
|
|
400
463
|
const { retryAt } = useStore(appStore);
|
|
401
464
|
useTick(true);
|
|
402
465
|
const s = typeof retryAt === "number" ? Math.max(0, Math.ceil((retryAt - NOW()) / 1000)) : 0;
|
|
403
|
-
return html`<div class="conn reconnecting" role="status">${s > 0 ? t("app.connReconnectingIn", s) : t("app.connReconnecting")}</div>`;
|
|
466
|
+
return html`<div data-trust="status" class="conn reconnecting" role="status">${s > 0 ? t("app.connReconnectingIn", s) : t("app.connReconnecting")}</div>`;
|
|
404
467
|
}
|
|
405
468
|
|
|
406
469
|
const NOW = () => (typeof performance !== "undefined" ? performance.now() : Date.now());
|
|
@@ -424,8 +487,21 @@ export function PendingChanges({ inline = false } = {}) {
|
|
|
424
487
|
const { pendingChanges } = useStore(appStore);
|
|
425
488
|
const list = pendingChanges || [];
|
|
426
489
|
useTick(list.some(p => p.state === "pending"));
|
|
490
|
+
// #2168: inline (Settings' own column), a request that has just started waiting is brought into view — on a phone the
|
|
491
|
+
// person applied far down the list, and the card that says where to confirm it sat above the screen. Once per request.
|
|
492
|
+
const box = useRef(null), shown = useRef(new Set());
|
|
493
|
+
useEffect(() => {
|
|
494
|
+
if (!inline || !box.current) return;
|
|
495
|
+
for (const p of list) {
|
|
496
|
+
if (p.state !== "pending" || shown.current.has(p.id)) continue;
|
|
497
|
+
shown.current.add(p.id);
|
|
498
|
+
const card = box.current.querySelector(`[data-pending="${p.id}"]`);
|
|
499
|
+
const still = typeof matchMedia === "function" && matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
500
|
+
if (card && typeof card.scrollIntoView === "function") card.scrollIntoView({ block: "nearest", behavior: still ? "auto" : "smooth" });
|
|
501
|
+
}
|
|
502
|
+
});
|
|
427
503
|
if (!list.length) return null;
|
|
428
|
-
return html`<div 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">
|
|
504
|
+
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">
|
|
429
505
|
<div class="pending-head"><${Icon} name=${p.state === "applied" ? "check" : p.state === "pending" || p.state === "applying" ? "clock" : "alert"} size=${16} />
|
|
430
506
|
<strong>${t(`app.pending_${p.state}`)}</strong>
|
|
431
507
|
${p.state === "pending" ? html`<span class="pending-left">${t("app.pendingLeft", mmss(p.deadline - NOW()))}</span>` : null}
|
|
@@ -450,7 +526,7 @@ function OperationLine({ route }) {
|
|
|
450
526
|
: op.phase === "posting" || op.phase === "watching" ? t("app.opApplying")
|
|
451
527
|
: op.phase === "failed" ? t("app.opFailed") : restart ? t("app.opRestart") : failedRows ? t("app.opPartial") : t("app.opDone");
|
|
452
528
|
const busy = op.phase === "writing" || op.phase === "posting" || op.phase === "watching";
|
|
453
|
-
return html`<div class=${`conn op-line${busy ? "" : op.phase === "failed" || restart || failedRows ? " bad" : " ok"}`} role="status">
|
|
529
|
+
return html`<div data-trust="status" class=${`conn op-line${busy ? "" : op.phase === "failed" || restart || failedRows ? " bad" : " ok"}`} role="status">
|
|
454
530
|
<span>${text}</span> <a class="btn btn-sm" href=${settingsPath()}>${t("app.openSettings")}</a></div>`;
|
|
455
531
|
}
|
|
456
532
|
|
|
@@ -497,21 +573,24 @@ export function Shell({ panels, onNewInstance, viewOnly = false }) {
|
|
|
497
573
|
vv.addEventListener("resize", onResize);
|
|
498
574
|
return () => vv.removeEventListener("resize", onResize);
|
|
499
575
|
}, []);
|
|
500
|
-
const
|
|
576
|
+
const guest = guestPage(); // #1920: a guest's page — no operations, usage, pending changes or owner CSS
|
|
577
|
+
const cls = ["shell", shell.collapsed ? "sb-collapsed" : "", shell.drawer ? "sb-open" : "", shell.keyboard ? "kb-open" : "", guest ? "guest" : ""].filter(Boolean).join(" ");
|
|
501
578
|
return html`<div class=${cls}>
|
|
502
|
-
<a class="skip" href="#main">${t("app.skip")}</a>
|
|
579
|
+
<a data-trust="navigation" class="skip" href="#main">${t("app.skip")}</a>
|
|
503
580
|
<${Sidebar} route=${nav.route} onNewInstance=${onNewInstance} viewOnly=${viewOnly} />
|
|
504
581
|
<div class="scrim" onClick=${closeDrawer} aria-hidden="true"></div>
|
|
505
582
|
<main id="main" class="main" tabindex="-1" inert=${shell.drawer && narrow() ? true : undefined}>
|
|
506
583
|
<${ConnectionLine} />
|
|
507
|
-
${viewOnly ? null : html`<${OperationLine} route=${nav.route} />`}
|
|
584
|
+
${viewOnly || guest ? null : html`<${OperationLine} route=${nav.route} />`}
|
|
508
585
|
<${Outlet} route=${nav.route} seq=${nav.seq} panels=${panels} />
|
|
509
586
|
</main>
|
|
510
|
-
|
|
587
|
+
${guest ? null : html`<${UsageRail} route=${nav.route} />`}
|
|
511
588
|
<${BottomTabs} route=${nav.route} viewOnly=${viewOnly} />
|
|
512
589
|
${shell.dialog ? html`<${shell.dialog.Component} ...${shell.dialog.props} onClose=${closeDialog} />` : null}
|
|
513
|
-
${viewOnly || (nav.route && nav.route.panel === "settings") ? null : html`<${PendingChanges} />`}
|
|
590
|
+
${viewOnly || guest || (nav.route && nav.route.panel === "settings") ? null : html`<${PendingChanges} />`}
|
|
514
591
|
<${ConfirmHost} />
|
|
592
|
+
<${ColorDialogHost} />
|
|
593
|
+
${viewOnly || guest ? null : html`<${OwnerCssKeepBar} />`}
|
|
515
594
|
<${Toasts} />
|
|
516
595
|
</div>`;
|
|
517
596
|
}
|
|
@@ -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;
|
|
@@ -39,8 +53,21 @@ export function applyStatus(d) {
|
|
|
39
53
|
connections: typeof d.connections === "number" ? d.connections : null,
|
|
40
54
|
// #1665: the host preferences' revision (app-prefs.js re-reads them when it moves).
|
|
41
55
|
prefsRev: typeof d.prefs_rev === "number" ? d.prefs_rev : null,
|
|
56
|
+
// #1629: the agents' themes' revision (app-themes.js re-reads them when it moves).
|
|
57
|
+
themesRev: typeof d.themes_rev === "number" ? d.themes_rev : null,
|
|
42
58
|
// #1685: host memory pressure slowing or holding new agents ("elevated" | "critical"), else null.
|
|
43
|
-
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
|
+
: []);
|
|
44
71
|
}
|
|
45
72
|
|
|
46
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
|
-
|
|
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
|
|
@@ -68,17 +70,20 @@ export function createStream(opts) {
|
|
|
68
70
|
const set = listeners.get(name);
|
|
69
71
|
if (set) for (const fn of [...set]) { try { fn(data, extra); } catch (e) { if (env.console) env.console.error(e); } }
|
|
70
72
|
}
|
|
73
|
+
function sseIngress() {
|
|
74
|
+
try { return Object.freeze({ source: "sse", receivedAt: now() }); } catch { return undefined; }
|
|
75
|
+
}
|
|
71
76
|
function setConnection(c) { if (state.connection !== c) { state.connection = c; emit("connection", c); } }
|
|
72
77
|
function setHydration(h) { if (state.hydration !== h) { state.hydration = h; emit("hydration", h); } }
|
|
73
78
|
|
|
74
79
|
/** A live event: delivered now, or held (in order) while a catch-up snapshot is on its way. */
|
|
75
|
-
function live(name, data, cursor) {
|
|
80
|
+
function live(name, data, cursor, extra) {
|
|
76
81
|
if (held) {
|
|
77
|
-
if (held.length < MAX_HELD) held.push([name, data, cursor]); else held.overflow = true;
|
|
82
|
+
if (held.length < MAX_HELD) held.push([name, data, cursor, extra]); else held.overflow = true;
|
|
78
83
|
return;
|
|
79
84
|
}
|
|
80
85
|
if (cursor) state.cursor = cursor;
|
|
81
|
-
emit(name, data);
|
|
86
|
+
emit(name, data, extra);
|
|
82
87
|
// A full set of open prompts arrives on every (re)connect: whatever a failed catch-up missed is current again.
|
|
83
88
|
if (name === "prompts" && (state.hydration === "failed" || state.hydration === "retrying") && !catching) setHydration("ok");
|
|
84
89
|
}
|
|
@@ -86,7 +91,7 @@ export function createStream(opts) {
|
|
|
86
91
|
const list = held;
|
|
87
92
|
held = null;
|
|
88
93
|
if (!list) return false;
|
|
89
|
-
for (const [name, data, cursor] of list) { if (cursor) state.cursor = cursor; emit(name, data); }
|
|
94
|
+
for (const [name, data, cursor, extra] of list) { if (cursor) state.cursor = cursor; emit(name, data, extra); }
|
|
90
95
|
return !!list.overflow;
|
|
91
96
|
}
|
|
92
97
|
|
|
@@ -116,7 +121,7 @@ export function createStream(opts) {
|
|
|
116
121
|
if (closed || mine !== pollSeq) return false;
|
|
117
122
|
if (typeof d.now === "number") emit("clock", { now: d.now }); // #1823: read just now, handed over at once
|
|
118
123
|
if (d.status) emit("status", d.status);
|
|
119
|
-
for (const m of d.messages || []) emit("message", m);
|
|
124
|
+
for (const m of d.messages || []) emit("message", m, { source: "poll" });
|
|
120
125
|
if (typeof d.cursor === "string") state.cursor = d.cursor;
|
|
121
126
|
if (Array.isArray(d.deliveries)) emit("deliveries", d.deliveries);
|
|
122
127
|
if (Array.isArray(d.prompts)) emit("prompts", d.prompts);
|
|
@@ -206,13 +211,15 @@ export function createStream(opts) {
|
|
|
206
211
|
for (const name of EVENTS) {
|
|
207
212
|
es.addEventListener(name, (e) => {
|
|
208
213
|
if (es !== sse) return; // a stream this page already let go of
|
|
214
|
+
// Snapshot before parsing or any synchronous connection/hydration observer can consume time.
|
|
215
|
+
const ingress = name === "message" || name === "agent_reply_live" ? sseIngress() : undefined;
|
|
209
216
|
let data;
|
|
210
217
|
try { data = JSON.parse(e.data); } catch { return; }
|
|
211
218
|
if (!spoke) { spoke = true; alive(); if (again) catchUp(); }
|
|
212
219
|
else alive();
|
|
213
220
|
// #1823: a clock reading is about now, not about order: it is never held behind a catch-up (it would be stale).
|
|
214
221
|
if (name === "clock") { emit(name, data); return; }
|
|
215
|
-
live(name, data, name === "message" ? e.lastEventId : null);
|
|
222
|
+
live(name, data, name === "message" ? e.lastEventId : null, ingress);
|
|
216
223
|
});
|
|
217
224
|
}
|
|
218
225
|
es.onerror = () => {
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
// #1629 (first design §5, r2 (b)(d)): agents' web themes on the page. Each applies to THAT agent's own chat surface
|
|
2
|
+
// (and its avatar ring) only — never the shell, the sidebar chrome or another instance. What the host sends is
|
|
3
|
+
// canonical already; this checks it again (a value that fails is dropped, never passed on) and applies it through the
|
|
4
|
+
// CSSOM (custom properties) and fixed class tokens. No style attribute, no CSS text, no url() built in CSS.
|
|
5
|
+
// Not shown: with "Show agents' themes, effects and widgets" off (fx.show, synced), and in safe mode (?safe=1).
|
|
6
|
+
import { appStore, createStore } from "./app-store.js";
|
|
7
|
+
import { onPrefsChanged, prefInForce, savePref } from "./app-prefs.js";
|
|
8
|
+
import { safeMode } from "./owner-css.js";
|
|
9
|
+
import { t } from "./app-i18n.js";
|
|
10
|
+
import { toast } from "./ui-toast.js";
|
|
11
|
+
import { confirmDialog } from "./ui-confirm.js";
|
|
12
|
+
|
|
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 });
|
|
18
|
+
const own = (o, k) => !!o && Object.prototype.hasOwnProperty.call(o, k);
|
|
19
|
+
|
|
20
|
+
const COLOR = /^#[0-9a-f]{6}$/;
|
|
21
|
+
const ASSET = /^[0-9a-f]{32}$/;
|
|
22
|
+
const PALETTE = { bg: "--bg", surface: "--surface", text: "--text", text2: "--text-2", accent: "--accent", userBubble: "--user-bubble", agentBubble: "--agent-bubble" };
|
|
23
|
+
const FONTS = ["inter", "system", "mono"], BORDERS = ["none", "thin", "accent"], FITS = ["cover", "contain"];
|
|
24
|
+
// The label's rule, as the host's (web-theme.ts canonicalText): 1..24 graphemes on one line, no control, format or
|
|
25
|
+
// separator character but the zero-width joiner, and at least one character that draws something (#1888 review).
|
|
26
|
+
const FORBIDDEN = /(?!\u200d)[\p{Cc}\p{Cf}\p{Zl}\p{Zp}]/u;
|
|
27
|
+
const VISIBLE = /[^\p{M}\p{C}\p{Z}\u115f\u1160\u3164\uffa0\u2800]/u;
|
|
28
|
+
let graphemes = null;
|
|
29
|
+
function labelOk(s) {
|
|
30
|
+
if (typeof s !== "string" || FORBIDDEN.test(s) || s.trim() !== s || !VISIBLE.test(s)) return false;
|
|
31
|
+
try { graphemes ??= new Intl.Segmenter(undefined, { granularity: "grapheme" }); } catch { return false; }
|
|
32
|
+
const n = [...graphemes.segment(s)].length;
|
|
33
|
+
return n >= 1 && n <= 24;
|
|
34
|
+
}
|
|
35
|
+
|
|
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.
|
|
38
|
+
const PUBLIC_FX_SHOW = "agend_public_fx_show";
|
|
39
|
+
let publicFxChoice = (() => { try { const v = localStorage.getItem(PUBLIC_FX_SHOW); return v === "on" ? true : v === "off" ? false : null; } catch { return null; } })();
|
|
40
|
+
|
|
41
|
+
/** Whether this page shows agents' themes now: the viewer's switch (default on), and never in safe mode. */
|
|
42
|
+
export function themesShown() {
|
|
43
|
+
if (safeMode()) return false;
|
|
44
|
+
if (localAppearanceViewer() && publicFxChoice !== null) return publicFxChoice;
|
|
45
|
+
try {
|
|
46
|
+
const f = prefInForce("fx.show"); // this page's choice, else the host's, else the default
|
|
47
|
+
if (f) return f.value !== false;
|
|
48
|
+
return localStorage.getItem("agend_fx_show") !== "off"; // not known here (the public link): this browser's copy
|
|
49
|
+
} catch { return true; }
|
|
50
|
+
}
|
|
51
|
+
/** The viewer's switch (Settings → Appearance): kept with the person's preferences, and in force on this page at once. */
|
|
52
|
+
export function setThemesShown(on) {
|
|
53
|
+
if (localAppearanceViewer()) {
|
|
54
|
+
publicFxChoice = !!on;
|
|
55
|
+
try { localStorage.setItem(PUBLIC_FX_SHOW, on ? "on" : "off"); } catch { /* the in-memory choice still applies */ }
|
|
56
|
+
} else savePref("fx.show", on ? null : false);
|
|
57
|
+
themesStore.set({ shown: themesShown() });
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** The mode the page is drawn in: the chosen one, or the system's for "system". */
|
|
61
|
+
export function resolvedMode() {
|
|
62
|
+
const T = globalThis.AgendTheme;
|
|
63
|
+
const chosen = T && typeof T.get === "function" ? T.get() : "system";
|
|
64
|
+
if (chosen === "light" || chosen === "dark") return chosen;
|
|
65
|
+
try { return globalThis.matchMedia && globalThis.matchMedia("(prefers-color-scheme: light)").matches ? "light" : "dark"; } catch { return "dark"; }
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** The theme to draw for `name`, re-checked; null when none (or not shown). */
|
|
69
|
+
export function themeFor(name) {
|
|
70
|
+
const s = themesStore.get();
|
|
71
|
+
if (!s.shown || !themesShown() || !own(s.themes, name)) return null;
|
|
72
|
+
const t = s.themes[name];
|
|
73
|
+
if (!t || typeof t !== "object") return null;
|
|
74
|
+
const out = {};
|
|
75
|
+
const palette = (p) => { const o = {}; if (p && typeof p === "object") for (const k of Object.keys(PALETTE)) if (typeof p[k] === "string" && COLOR.test(p[k])) o[k] = p[k]; return o; };
|
|
76
|
+
if (t.colors && typeof t.colors === "object") out.colors = { light: palette(t.colors.light), dark: palette(t.colors.dark) };
|
|
77
|
+
if (typeof t.accent === "string" && COLOR.test(t.accent)) out.accent = t.accent;
|
|
78
|
+
if (t.bubble && typeof t.bubble === "object") {
|
|
79
|
+
out.bubble = {};
|
|
80
|
+
if (Number.isInteger(t.bubble.radius) && t.bubble.radius >= 0 && t.bubble.radius <= 24) out.bubble.radius = t.bubble.radius;
|
|
81
|
+
if (BORDERS.includes(t.bubble.border)) out.bubble.border = t.bubble.border;
|
|
82
|
+
}
|
|
83
|
+
if (FONTS.includes(t.font)) out.font = t.font;
|
|
84
|
+
const bg = t.background;
|
|
85
|
+
if (bg && typeof bg.image === "string" && ASSET.test(bg.image) && FITS.includes(bg.fit) && typeof bg.opacity === "number" && bg.opacity >= 0.05 && bg.opacity <= 1) {
|
|
86
|
+
out.background = { image: bg.image, fit: bg.fit, opacity: bg.opacity };
|
|
87
|
+
}
|
|
88
|
+
if (labelOk(t.workingLabel)) out.workingLabel = t.workingLabel;
|
|
89
|
+
return out;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function isThemeLocked(name) { return (themesStore.get().locked || []).includes(name); }
|
|
93
|
+
|
|
94
|
+
const TOKENS = ["th", "th-font-inter", "th-font-system", "th-font-mono", "th-bubble-none", "th-bubble-thin", "th-bubble-accent", "th-agent-bubble"];
|
|
95
|
+
/**
|
|
96
|
+
* Draw `theme` (from themeFor) on one agent's chat root `el` for `mode`; null clears it. Colours are custom properties
|
|
97
|
+
* set through the CSSOM; choices are class tokens from the fixed list above.
|
|
98
|
+
*/
|
|
99
|
+
export function applyTheme(el, theme, mode) {
|
|
100
|
+
if (!el || !el.style) return;
|
|
101
|
+
for (const prop of [...Object.values(PALETTE), "--bubble-radius"]) el.style.removeProperty(prop);
|
|
102
|
+
for (const c of TOKENS) el.classList.remove(c);
|
|
103
|
+
if (!theme) return;
|
|
104
|
+
const pal = { ...(theme.accent ? { accent: theme.accent } : {}), ...((theme.colors && theme.colors[mode]) || {}) };
|
|
105
|
+
for (const [k, prop] of Object.entries(PALETTE)) if (typeof pal[k] === "string" && COLOR.test(pal[k])) el.style.setProperty(prop, pal[k]);
|
|
106
|
+
el.classList.add("th");
|
|
107
|
+
if (theme.font) el.classList.add(`th-font-${theme.font}`);
|
|
108
|
+
if (theme.bubble && theme.bubble.border) el.classList.add(`th-bubble-${theme.bubble.border}`);
|
|
109
|
+
if (theme.bubble && Number.isInteger(theme.bubble.radius)) el.style.setProperty("--bubble-radius", `${theme.bubble.radius}px`);
|
|
110
|
+
if (typeof pal.agentBubble === "string") el.classList.add("th-agent-bubble");
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** The theme picture's same-origin URL (the id is its content hash). */
|
|
114
|
+
export function themeImageUrl(name, id) { return `/ui/img/theme/${encodeURIComponent(name)}/${id}`; }
|
|
115
|
+
|
|
116
|
+
let reading = false, wanted = -1, started = false;
|
|
117
|
+
async function read(rev) {
|
|
118
|
+
wanted = rev;
|
|
119
|
+
if (reading) return;
|
|
120
|
+
reading = true;
|
|
121
|
+
try {
|
|
122
|
+
while (themesStore.get().rev !== wanted) {
|
|
123
|
+
const want = wanted;
|
|
124
|
+
let body = null;
|
|
125
|
+
try { const r = await fetch("/ui/themes", { credentials: "same-origin" }); body = r.ok ? await r.json() : null; } catch { body = null; }
|
|
126
|
+
if (!body || typeof body !== "object") break;
|
|
127
|
+
themesStore.set({ rev: typeof body.rev === "number" ? body.rev : want, themes: body.themes && typeof body.themes === "object" ? body.themes : {},
|
|
128
|
+
locked: Array.isArray(body.locked) ? body.locked.filter((n) => typeof n === "string") : [],
|
|
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 : {} });
|
|
131
|
+
if (want === wanted) break;
|
|
132
|
+
}
|
|
133
|
+
} finally { reading = false; }
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/** Once per page load (the full app): read the themes when the status frame's themes_rev moves; follow fx.show. */
|
|
137
|
+
export function startThemes() {
|
|
138
|
+
if (started) return;
|
|
139
|
+
started = true;
|
|
140
|
+
themesStore.set({ shown: themesShown() });
|
|
141
|
+
let last = null;
|
|
142
|
+
appStore.subscribe((s) => {
|
|
143
|
+
const rev = typeof s.themesRev === "number" ? s.themesRev : null;
|
|
144
|
+
if (rev === null || rev === last) return;
|
|
145
|
+
last = rev;
|
|
146
|
+
read(rev);
|
|
147
|
+
});
|
|
148
|
+
onPrefsChanged((changed) => { if (changed.has("fx.show")) themesStore.set({ shown: themesShown() }); });
|
|
149
|
+
}
|
|
150
|
+
|
|
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";
|
|
153
|
+
|
|
154
|
+
/** A person's Reset or Lock of an agent's theme (an admin session; never over the public link). */
|
|
155
|
+
async function themeAction(name, what, body) {
|
|
156
|
+
let r;
|
|
157
|
+
try {
|
|
158
|
+
const res = await fetch(`/ui/themes/${encodeURIComponent(name)}/${what}`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body || {}) });
|
|
159
|
+
r = await res.json().catch(() => ({}));
|
|
160
|
+
if (!res.ok) r = { error: r && r.error ? r.error : `HTTP ${res.status}` };
|
|
161
|
+
} catch (err) { r = { error: err && err.message ? err.message : "network" }; }
|
|
162
|
+
if (r && r.error) toast(t("chat.themeFailed", r.error), false);
|
|
163
|
+
else toast(t(what === "reset" ? "chat.themeResetDone" : body && body.locked ? "chat.themeLocked" : "chat.themeUnlocked", name));
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/** A person's removal of an agent's widget from one slot (r4-4). */
|
|
167
|
+
async function removeWidget(name, slot) {
|
|
168
|
+
let r;
|
|
169
|
+
try {
|
|
170
|
+
const res = await fetch(`/ui/themes/${encodeURIComponent(name)}/widget`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ slot }) });
|
|
171
|
+
r = await res.json().catch(() => ({}));
|
|
172
|
+
if (!res.ok) r = { error: r && r.error ? r.error : `HTTP ${res.status}` };
|
|
173
|
+
} catch (err) { r = { error: err && err.message ? err.message : "network" }; }
|
|
174
|
+
if (r && r.error) toast(t("chat.themeFailed", r.error), false);
|
|
175
|
+
else toast(t("chat.widgetRemoved", name));
|
|
176
|
+
}
|
|
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
|
+
|
|
190
|
+
/** The chat's and Details' ⋯ entries for an agent's theme: Lock/Unlock, Reset… and its widgets' Remove — none over the public link. */
|
|
191
|
+
export function themeMenuItems(name) {
|
|
192
|
+
if (publicLink()) return [];
|
|
193
|
+
const locked = isThemeLocked(name);
|
|
194
|
+
const w = themesStore.get().widgets;
|
|
195
|
+
const placed = w && own(w, name) && w[name] && typeof w[name] === "object" ? w[name] : {};
|
|
196
|
+
return [
|
|
197
|
+
...["chat_header", "details"].filter((slot) => own(placed, slot)).map((slot) => ({
|
|
198
|
+
key: `widget-remove-${slot}`, label: t(slot === "chat_header" ? "chat.widgetRemoveHeader" : "chat.widgetRemoveDetails"), icon: "trash",
|
|
199
|
+
onSelect: () => removeWidget(name, slot),
|
|
200
|
+
})),
|
|
201
|
+
{ key: "theme-lock", label: t(locked ? "chat.themeUnlock" : "chat.themeLock"), icon: "key", onSelect: () => themeAction(name, "lock", { locked: !locked }) },
|
|
202
|
+
{ key: "theme-reset", label: t("chat.themeReset"), icon: "restart", onSelect: async () => {
|
|
203
|
+
if (await confirmDialog({ message: t("chat.themeResetConfirm", name), confirmLabel: t("chat.themeResetGo"), danger: true })) themeAction(name, "reset");
|
|
204
|
+
} },
|
|
205
|
+
];
|
|
206
|
+
}
|