@songsid/agend 2.2.0 → 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.
- 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 +34 -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-client-retirement.d.ts +5 -0
- package/dist/channel/adapters/discord-client-retirement.js +57 -0
- package/dist/channel/adapters/discord-client-retirement.js.map +1 -0
- package/dist/channel/adapters/discord.d.ts +11 -0
- package/dist/channel/adapters/discord.js +66 -6
- 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 +142 -110
- 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 +10 -0
- package/dist/daemon.js +327 -31
- 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 +514 -12
- package/dist/fleet-manager.js +1885 -163
- package/dist/fleet-manager.js.map +1 -1
- package/dist/fleet-process-handlers.d.ts +18 -0
- package/dist/fleet-process-handlers.js +69 -0
- package/dist/fleet-process-handlers.js.map +1 -0
- 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 +482 -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.d.ts +2 -0
- package/dist/instance-lifecycle.js +20 -10
- 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 +23 -0
- package/dist/settings-api.js +233 -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 +245 -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 +25 -7
- package/dist/ui/chat-store.js +41 -13
- package/dist/ui/chat-strings.js +86 -2
- package/dist/ui/chat-thread.js +204 -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 +252 -58
- package/dist/ui/panel-details.js +20 -6
- package/dist/ui/panel-fleet.js +45 -19
- package/dist/ui/panel-log.js +6 -6
- package/dist/ui/panel-needs.js +3 -3
- package/dist/ui/panel-settings.js +58 -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 +335 -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 +42 -2
- package/dist/ui/shared/app-session.js +3 -1
- package/dist/ui/shared/app-shell.js +119 -41
- 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 +566 -5
- 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 +122 -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 +543 -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 +250 -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 +117 -7
- package/dist/web-api.js +1283 -70
- 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 +63 -0
- package/dist/web-chat-history.js +109 -6
- 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 +38 -0
- package/dist/web-image-meta.js +262 -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 +222 -0
- package/dist/web-owner-css-store.js +440 -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 +273 -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-upload.d.ts +13 -0
- package/dist/web-upload.js +39 -3
- package/dist/web-upload.js.map +1 -1
- 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
|
@@ -9,13 +9,15 @@
|
|
|
9
9
|
// Public (/assets/): an anonymous View reader gets the same list, fed by View's roster read instead of status frames.
|
|
10
10
|
import { html, useLayoutEffect, useRef, useState } from "./app-html.js";
|
|
11
11
|
import { t } from "./app-i18n.js";
|
|
12
|
-
import { appStore, createStore, useStore } from "./app-store.js";
|
|
12
|
+
import { appStore, createStore, guestPage, useStore } from "./app-store.js";
|
|
13
13
|
import { chatPath, detailsPath, logPath, viewPath } from "./app-route.js";
|
|
14
14
|
import { Icon } from "./ui-icons.js";
|
|
15
15
|
import { Skeleton } from "./ui-states.js";
|
|
16
16
|
import { viewStore } from "./view-roster-store.js";
|
|
17
17
|
import { onPrefsChanged, prefInForce, prefsWritable, savePref } from "./app-prefs.js";
|
|
18
18
|
import "./view-strings.js";
|
|
19
|
+
import { themesShown, themesStore } from "./app-themes.js";
|
|
20
|
+
import { identityClasses, identityOf, paintIdentity, useIdentityColors } from "./identity-color.js";
|
|
19
21
|
|
|
20
22
|
/** view.* text with named values. */
|
|
21
23
|
function tn(key, values = {}) {
|
|
@@ -28,12 +30,52 @@ const stored = (k) => { try { return localStorage.getItem(k); } catch { return n
|
|
|
28
30
|
const nameOf = (it) => (it && (it.name != null ? it.name : it.instance_name)) || "";
|
|
29
31
|
|
|
30
32
|
// ── Groups, labels, the filter ──
|
|
33
|
+
/** A team's group key: no tag can be it (a group name is never a control character), so a tag named like a team stays apart. */
|
|
34
|
+
export const TEAM_GROUP = "\u0001team:";
|
|
35
|
+
/**
|
|
36
|
+
* #2172: an agent in a team (fleet.yaml `teams.<name>.members`; `teams` on a status or roster row, in fleet.yaml's
|
|
37
|
+
* order) sits under its first team, whatever its tags say; one in no team under its first tag; a ClassicBot room under
|
|
38
|
+
* "Classic"; the rest under "Other".
|
|
39
|
+
*/
|
|
31
40
|
export function groupOf(it) {
|
|
32
41
|
const tags = Array.isArray(it.tags) ? it.tags : [];
|
|
33
42
|
if (tags.includes("classic")) return "Classic";
|
|
43
|
+
const teams = Array.isArray(it.teams) ? it.teams.filter((x) => typeof x === "string" && x) : [];
|
|
44
|
+
if (teams.length) return TEAM_GROUP + teams[0];
|
|
34
45
|
if (tags.length) return tags[0];
|
|
35
46
|
return "Other";
|
|
36
47
|
}
|
|
48
|
+
/** "team" for a team's group, "tag" for a tag's; null for Classic, Other and Favorites. */
|
|
49
|
+
export function groupKind(g) {
|
|
50
|
+
if (typeof g !== "string") return null;
|
|
51
|
+
if (g.startsWith(TEAM_GROUP)) return "team";
|
|
52
|
+
return g === "Other" || g === "Classic" || g.charCodeAt(0) < 32 ? null : "tag";
|
|
53
|
+
}
|
|
54
|
+
/** A group's heading: a team's name; the fallback group ("Other") in the page's language; a tag as it is. */
|
|
55
|
+
export function groupLabel(g) { return g === "Other" ? tn("groupOther") : groupKind(g) === "team" ? g.slice(TEAM_GROUP.length) : g; }
|
|
56
|
+
/** #2172: what a group is, said on its heading — a team's icon, or "#" before a tag — so the grouping can be read. */
|
|
57
|
+
export function GroupMark({ group }) {
|
|
58
|
+
const kind = groupKind(group);
|
|
59
|
+
if (!kind) return null;
|
|
60
|
+
const what = tn(kind === "team" ? "groupTeam" : "groupTag");
|
|
61
|
+
return kind === "team"
|
|
62
|
+
? html`<span class="v-group-kind team" title=${what}><${Icon} name="team" size=${13} /><span class="sr-only">${what}: </span></span>`
|
|
63
|
+
: html`<span class="v-group-kind tag" title=${what} aria-hidden="true">#</span>`;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* #2050: the model as a person reads it. The server's resolver writes English for a model it has not resolved yet
|
|
67
|
+
* ("default (detected when it starts)") and for a CLI's own default ("x (default)"); those are shown in the page's language.
|
|
68
|
+
*/
|
|
69
|
+
export function modelText(it, raw = it && it.model) {
|
|
70
|
+
if (!raw || typeof raw !== "string") return raw;
|
|
71
|
+
if (it.model_source === "unresolved") {
|
|
72
|
+
const why = /^default \((.*)\)$/.exec(raw)?.[1];
|
|
73
|
+
const reason = why === "detected when it starts" ? tn("modelWhyStart") : why === "this CLI does not report a default" ? tn("modelWhyCli") : why;
|
|
74
|
+
return reason ? tn("modelDefaultWhy", { reason }) : raw === "default" ? tn("modelDefault") : raw;
|
|
75
|
+
}
|
|
76
|
+
if (it.model_source === "cli-default") { const m = /^(.*) \(default\)$/.exec(raw); if (m) return tn("modelCliDefault", { model: m[1] }); }
|
|
77
|
+
return raw;
|
|
78
|
+
}
|
|
37
79
|
const BACKEND_LABELS = { "claude-code": "Claude Code", "kiro-cli": "Kiro CLI", codex: "Codex", grok: "Grok Build", antigravity: "Antigravity", opencode: "OpenCode" };
|
|
38
80
|
export function backendLabel(backend) { return BACKEND_LABELS[backend] || backend || "Unknown"; }
|
|
39
81
|
export const KNOWN_BACKENDS = new Set(Object.keys(BACKEND_LABELS));
|
|
@@ -91,8 +133,8 @@ export function instanceTooltip(it) {
|
|
|
91
133
|
if (it.display_name && it.display_name !== name) lines.push(it.display_name, `(${name})`);
|
|
92
134
|
else lines.push(name);
|
|
93
135
|
lines.push(line("tooltipBackend", backendLabel(it.backend)));
|
|
94
|
-
const src = (s) => (s === "instance" || s === "classic" ? "
|
|
95
|
-
lines.push(line("tooltipModel", (it
|
|
136
|
+
const src = (s) => (s === "instance" || s === "classic" ? tn("srcConfigured") : s === "fleet-default" ? tn("srcFleetDefault") : "");
|
|
137
|
+
lines.push(line("tooltipModel", (modelText(it) || tn("tooltipUnavailable")) + src(it.model_source)));
|
|
96
138
|
lines.push(line("tooltipStatus", status));
|
|
97
139
|
lines.push(line("tooltipContext", context));
|
|
98
140
|
if (it.effort) lines.push(line("tooltipEffort", it.effort + src(it.effort_source)));
|
|
@@ -106,6 +148,7 @@ const SIDEBAR_ORDER_KEY = "agend_view_sidebar_order"; // View's key since #999
|
|
|
106
148
|
const COLLAPSED_KEY = "agend_instance_collapsed";
|
|
107
149
|
const FILTER_KEY = "agend_instance_filter";
|
|
108
150
|
const FAVORITES_KEY = "agend_favorites"; // #1664
|
|
151
|
+
const SORT_KEY = "agend_nav_sort"; // #2179
|
|
109
152
|
/**
|
|
110
153
|
* A synced sidebar preference, as the JSON its reader parses: the value in force in memory (app-prefs.js — this
|
|
111
154
|
* page's own change, else the host's; a full or blocked storage never brings an old one back), else this browser's copy.
|
|
@@ -136,6 +179,13 @@ function readFilter() {
|
|
|
136
179
|
return { q: f && typeof f.q === "string" ? f.q : "", status: strings(f && f.status).filter((s) => STATUS_FACETS.includes(s)), cli: strings(f && f.cli) };
|
|
137
180
|
} catch { return { q: "", status: [], cli: [] }; }
|
|
138
181
|
}
|
|
182
|
+
/** #2179: how the list sorts — { mode: "custom" (the person's order; the default) | "recent", favs: "fixed" | "recent" }. */
|
|
183
|
+
function readSort() {
|
|
184
|
+
try {
|
|
185
|
+
const s = JSON.parse(inForce("nav.sort", SORT_KEY) || "{}");
|
|
186
|
+
return { mode: s && s.mode === "recent" ? "recent" : "custom", favs: s && s.favs === "recent" ? "recent" : "fixed" };
|
|
187
|
+
} catch { return { mode: "custom", favs: "fixed" }; }
|
|
188
|
+
}
|
|
139
189
|
function readFavorites() {
|
|
140
190
|
try { const a = JSON.parse(inForce("nav.favorites", FAVORITES_KEY) || "[]"); return Array.isArray(a) ? [...new Set(a.filter((x) => typeof x === "string"))] : []; } catch { return []; }
|
|
141
191
|
}
|
|
@@ -149,7 +199,9 @@ export function computeOrder(roster, order) {
|
|
|
149
199
|
if (ia !== ib) return ia - ib;
|
|
150
200
|
if (a === "Other") return 1;
|
|
151
201
|
if (b === "Other") return -1;
|
|
152
|
-
|
|
202
|
+
const ta = groupKind(a) === "team", tb = groupKind(b) === "team";
|
|
203
|
+
if (ta !== tb) return ta ? -1 : 1; // #2172: teams first, then tags
|
|
204
|
+
return groupLabel(a).localeCompare(groupLabel(b));
|
|
153
205
|
});
|
|
154
206
|
const instByGroup = new Map();
|
|
155
207
|
for (const [g, items] of groups) {
|
|
@@ -172,12 +224,18 @@ function moveIn(arr, moving, target, before) {
|
|
|
172
224
|
}
|
|
173
225
|
|
|
174
226
|
/** The list's state for the page's life: this browser's order, folded groups and filter (each also kept in storage). */
|
|
175
|
-
export const navStore = createStore({ order: readOrder(), collapsed: readCollapsed(), filter: readFilter(), favorites: readFavorites() });
|
|
227
|
+
export const navStore = createStore({ order: readOrder(), collapsed: readCollapsed(), filter: readFilter(), favorites: readFavorites(), sort: readSort() });
|
|
176
228
|
/** Read them again from this browser's storage — what a page load does (the store above is made that way). */
|
|
177
229
|
export function loadNavPrefs() {
|
|
178
|
-
navStore.set({ order: readOrder(), collapsed: readCollapsed(), filter: readFilter(), favorites: readFavorites() });
|
|
230
|
+
navStore.set({ order: readOrder(), collapsed: readCollapsed(), filter: readFilter(), favorites: readFavorites(), sort: readSort() });
|
|
179
231
|
namesStore.set({ primary: readNamesPrimary() });
|
|
180
232
|
}
|
|
233
|
+
/** #2179: change how the list sorts ({ mode?, favs? }): the same on every page and device. */
|
|
234
|
+
export function setNavSort(patch) {
|
|
235
|
+
const sort = { ...navStore.get().sort, ...patch };
|
|
236
|
+
navStore.set({ sort });
|
|
237
|
+
savePref("nav.sort", { mode: sort.mode, favs: sort.favs });
|
|
238
|
+
}
|
|
181
239
|
/** Change the filter ({ q?, status?, cli? }): the same on every page, and kept by this browser. */
|
|
182
240
|
export function setNavFilter(patch) {
|
|
183
241
|
const filter = { ...navStore.get().filter, ...patch };
|
|
@@ -186,7 +244,7 @@ export function setNavFilter(patch) {
|
|
|
186
244
|
}
|
|
187
245
|
function setCollapsed(collapsed) { navStore.set({ collapsed }); savePref("nav.collapsed", [...collapsed]); }
|
|
188
246
|
// Another device rearranged, folded or filtered: this page's list follows (its cache is already updated).
|
|
189
|
-
onPrefsChanged((keys) => { if (keys.has("nav.order") || keys.has("nav.collapsed") || keys.has("nav.filter") || keys.has("nav.favorites")) loadNavPrefs(); });
|
|
247
|
+
onPrefsChanged((keys) => { if (keys.has("nav.order") || keys.has("nav.collapsed") || keys.has("nav.filter") || keys.has("nav.favorites") || keys.has("nav.sort")) loadNavPrefs(); });
|
|
190
248
|
|
|
191
249
|
// ── #1664: favorites — starred instances, pinned at the top in the order they were starred (or moved) ──
|
|
192
250
|
export const MAX_FAVORITES = 50;
|
|
@@ -222,6 +280,8 @@ function writeFacetsOpen(open) { try { localStorage.setItem(FACETS_KEY, JSON.str
|
|
|
222
280
|
|
|
223
281
|
/** The Favorites group's key among the tag groups: no tag can be it (a group name is never a control character). */
|
|
224
282
|
const FAV_GROUP = "\u0000favorites";
|
|
283
|
+
/** #1936: the open instance, pinned on top while a filter is on. */
|
|
284
|
+
const PINNED_GROUP = "\u0000pinned";
|
|
225
285
|
function persistOrder(groupNames, instByGroup) {
|
|
226
286
|
const rows = [];
|
|
227
287
|
groupNames.forEach((g, i) => rows.push({ item_type: "group", item_name: g, sort_index: i, group_name: null }));
|
|
@@ -280,9 +340,16 @@ const CLI_MONOGRAM = { "claude-code": "C", codex: "X", "kiro-cli": "K", grok: "G
|
|
|
280
340
|
// One path segment under /ui/img/<kind>/ — no further slash or backslash, and not a dot segment ("." / ".." or their
|
|
281
341
|
// %2e spellings, which a browser resolves too): the picture can only ever be one of those routes (#1642 review).
|
|
282
342
|
const sameOrigin = (src) => typeof src === "string"
|
|
283
|
-
&& /^\/ui\/img\/(bot|avatar|emoji)\/(?!(?:\.|%2[eE]){1,2}(?:\?|$))[^/\\?#\s]+(\?v=[0-9A-Za-z%._-]*)?$/.test(src)
|
|
284
|
-
|
|
343
|
+
&& (/^\/ui\/img\/(bot|avatar|emoji)\/(?!(?:\.|%2[eE]){1,2}(?:\?|$))[^/\\?#\s]+(\?v=[0-9A-Za-z%._-]*)?$/.test(src)
|
|
344
|
+
// #1629: a theme's picture — one name segment, then its 32-hex content id.
|
|
345
|
+
|| /^\/ui\/img\/theme\/(?!(?:\.|%2[eE]){1,2}\/)[^/\\?#\s]+\/[0-9a-f]{32}$/.test(src));
|
|
346
|
+
/**
|
|
347
|
+
* The avatar to draw: the server's, if it is one the page may load; else the first character. #1629: the agent's theme
|
|
348
|
+
* picture first, while this viewer shows agents' themes (fx.show, not safe mode).
|
|
349
|
+
*/
|
|
285
350
|
export function avatarOf(it) {
|
|
351
|
+
const th = it && themesShown() ? it.theme_avatar : null;
|
|
352
|
+
if (th && th.kind === "image" && sameOrigin(th.src)) return { kind: "image", src: th.src };
|
|
286
353
|
const a = it && it.avatar;
|
|
287
354
|
if (a && a.kind === "image" && sameOrigin(a.src)) return { kind: "image", src: a.src };
|
|
288
355
|
if (a && a.kind === "emoji" && sameOrigin(a.src)) return { kind: "emoji", src: a.src, alt: typeof a.alt === "string" ? a.alt : "" };
|
|
@@ -290,7 +357,6 @@ export function avatarOf(it) {
|
|
|
290
357
|
// #1722: the initial is the leading name's ("Names"), not the server's (which is always the nickname's).
|
|
291
358
|
return { kind: "initial", text: firstCharacter(namesOf(it).primary) || (a && a.kind === "initial" && typeof a.text === "string" ? a.text : "") };
|
|
292
359
|
}
|
|
293
|
-
const ACCENT = /^#[0-9a-f]{6}$/i;
|
|
294
360
|
/** The status dot on the avatar: needs you, crashed, paused, stopped, looks stuck, working, idle — colour AND a label. */
|
|
295
361
|
export function dotState(it, exec, awaiting) {
|
|
296
362
|
if (awaiting != null) return "needs";
|
|
@@ -306,22 +372,19 @@ const DOT = {
|
|
|
306
372
|
};
|
|
307
373
|
export function dotLabel(state) { return (DOT[state] || DOT.stopped)[1](); }
|
|
308
374
|
|
|
309
|
-
/**
|
|
375
|
+
/**
|
|
376
|
+
* An instance's avatar with its status dot. #1958: its ring is the identity colour, painted by the row it sits in
|
|
377
|
+
* (identity-color.js: `.idc.id-ring`), for every face.
|
|
378
|
+
*/
|
|
310
379
|
export function InstanceAvatar({ it, state, cli = false }) {
|
|
380
|
+
useStore(themesStore); // #1629: redraw when the viewer turns agents' themes on or off
|
|
311
381
|
const a = avatarOf(it);
|
|
312
382
|
const [broken, setBroken] = useState("");
|
|
313
|
-
const el = useRef(null);
|
|
314
|
-
const accent = typeof it.accent === "string" && ACCENT.test(it.accent) && a.kind === "image" ? it.accent : "";
|
|
315
|
-
useLayoutEffect(() => {
|
|
316
|
-
const node = el.current;
|
|
317
|
-
if (!node || !node.style) return;
|
|
318
|
-
if (accent) node.style.setProperty("--ring", accent); else node.style.removeProperty("--ring");
|
|
319
|
-
}, [accent]);
|
|
320
383
|
const src = a.kind !== "initial" && a.src && broken !== a.src ? a.src : "";
|
|
321
384
|
const text = a.kind === "emoji" && a.text ? a.text : a.kind === "initial" ? a.text : firstCharacter(namesOf(it).primary);
|
|
322
385
|
const [cls] = DOT[state] || DOT.stopped;
|
|
323
386
|
const label = dotLabel(state);
|
|
324
|
-
return html`<span
|
|
387
|
+
return html`<span class=${`av${a.kind === "emoji" ? " av-emoji" : ""}`}>
|
|
325
388
|
${src ? html`<img src=${src} alt="" decoding="async" onError=${() => setBroken(src)} />` : html`<span class="av-text" aria-hidden="true">${text}</span>`}
|
|
326
389
|
<span class=${`av-dot dot ${cls}`} role="img" aria-label=${label} title=${label}></span>${cli
|
|
327
390
|
? html`<span class="av-cli" aria-hidden="true" title=${backendLabel(it.backend)}>${CLI_MONOGRAM[it.backend] || "·"}</span>` : null}</span>`;
|
|
@@ -332,6 +395,71 @@ export function InstanceAvatar({ it, state, cli = false }) {
|
|
|
332
395
|
const statusText = (facet) => (facet === "needs" ? t("app.needsYou") : facet === "working" ? t("app.statusWorking") : facet === "idle" ? t("app.statusIdle")
|
|
333
396
|
: facet === "crashed" ? t("app.statusCrashed") : t("app.statusStopped"));
|
|
334
397
|
|
|
398
|
+
// ── #2179: when each instance last replied to a person (the status frame's last_reply) ──
|
|
399
|
+
/** A reply this recent (minutes) is highlighted; the highlight fades to nothing over this time. */
|
|
400
|
+
export const REPLY_FRESH_MIN = 10;
|
|
401
|
+
/** Minutes since the instance last replied to a person, or null (none kept, or no time on this row). */
|
|
402
|
+
export function replyAge(it, now = Date.now()) {
|
|
403
|
+
const at = it && typeof it.last_reply === "string" ? Date.parse(it.last_reply) : NaN;
|
|
404
|
+
return Number.isFinite(at) ? Math.max(0, (now - at) / 60000) : null;
|
|
405
|
+
}
|
|
406
|
+
/** "just now", "2 min ago", "3 h ago", "yesterday", "4 d ago" — in the page's language. */
|
|
407
|
+
export function replyText(mins) {
|
|
408
|
+
if (mins < 1) return tn("replyJustNow");
|
|
409
|
+
if (mins < 60) return tn("replyMinutes", { n: Math.floor(mins) });
|
|
410
|
+
if (mins < 1440) return tn("replyHours", { n: Math.floor(mins / 60) });
|
|
411
|
+
if (mins < 2880) return tn("replyYesterday");
|
|
412
|
+
return tn("replyDays", { n: Math.floor(mins / 1440) });
|
|
413
|
+
}
|
|
414
|
+
/** How strong the highlight is: 1 just after a reply, down to 0 at REPLY_FRESH_MIN minutes. */
|
|
415
|
+
export function replyFade(mins) { return mins == null || mins >= REPLY_FRESH_MIN ? 0 : 1 - mins / REPLY_FRESH_MIN; }
|
|
416
|
+
/** The highlight's strength on the row, through the CSSOM (style-src has no 'unsafe-inline'). */
|
|
417
|
+
function paintReply(el, fade) {
|
|
418
|
+
if (!el) return;
|
|
419
|
+
if (fade > 0) el.style.setProperty("--reply-a", fade.toFixed(3)); else el.style.removeProperty("--reply-a");
|
|
420
|
+
}
|
|
421
|
+
/** Most recent reply first; rows with none keep their order after them (a stable sort). */
|
|
422
|
+
function byRecentReply(age) {
|
|
423
|
+
return (a, b) => {
|
|
424
|
+
const x = age(a), y = age(b);
|
|
425
|
+
if (x == null || y == null) return x == null ? (y == null ? 0 : 1) : -1;
|
|
426
|
+
return x - y;
|
|
427
|
+
};
|
|
428
|
+
}
|
|
429
|
+
/** The group that holds every non-favorite row in "recent" mode: no tag or team can be it, and it is never saved. */
|
|
430
|
+
const RECENT_GROUP = "\u0000recent";
|
|
431
|
+
|
|
432
|
+
/** #2179: the sort button and its menu — how the list sorts, said on the button. */
|
|
433
|
+
function SortMenu({ sort }) {
|
|
434
|
+
const [open, setOpen] = useState(false);
|
|
435
|
+
const box = useRef(null);
|
|
436
|
+
useLayoutEffect(() => {
|
|
437
|
+
if (!open) return undefined;
|
|
438
|
+
const doc = box.current && box.current.ownerDocument;
|
|
439
|
+
if (!doc) return undefined;
|
|
440
|
+
const away = (e) => { if (box.current && !box.current.contains(e.target)) setOpen(false); };
|
|
441
|
+
const esc = (e) => { if (e.key === "Escape") { e.preventDefault(); setOpen(false); } };
|
|
442
|
+
doc.addEventListener("pointerdown", away, true);
|
|
443
|
+
doc.addEventListener("keydown", esc, true);
|
|
444
|
+
return () => { doc.removeEventListener("pointerdown", away, true); doc.removeEventListener("keydown", esc, true); };
|
|
445
|
+
}, [open]);
|
|
446
|
+
const recent = sort.mode === "recent";
|
|
447
|
+
const radio = (group, value, label, hint) => html`<label class="nav-sort-opt"><input type="radio" name=${`nav-sort-${group}`} value=${value}
|
|
448
|
+
checked=${sort[group] === value} onChange=${() => setNavSort({ [group]: value })} /><span>${label}${hint ? html`<span class="nav-sort-hint">${hint}</span>` : null}</span></label>`;
|
|
449
|
+
return html`<div class="nav-sort" ref=${box}>
|
|
450
|
+
<button type="button" class=${`chip-btn nav-sort-btn${recent ? " on" : ""}`} aria-haspopup="dialog" aria-expanded=${open ? "true" : "false"}
|
|
451
|
+
title=${tn("sortTitle")} onClick=${() => setOpen(!open)}><${Icon} name="sort" size=${12} /><span>${tn(recent ? "sortRecent" : "sortCustom")}</span></button>
|
|
452
|
+
${open ? html`<div class="nav-sort-menu" role="dialog" aria-label=${tn("sortTitle")}>
|
|
453
|
+
<fieldset><legend>${tn("sortTitle")}</legend>
|
|
454
|
+
${radio("mode", "custom", tn("sortCustom"), tn("sortCustomHint"))}
|
|
455
|
+
${radio("mode", "recent", tn("sortRecent"), tn("sortRecentHint"))}</fieldset>
|
|
456
|
+
<fieldset><legend>${tn("sortFavs")}</legend>
|
|
457
|
+
${radio("favs", "fixed", tn("sortFavsFixed"), "")}
|
|
458
|
+
${radio("favs", "recent", tn("sortFavsRecent"), "")}</fieldset>
|
|
459
|
+
</div>` : null}
|
|
460
|
+
</div>`;
|
|
461
|
+
}
|
|
462
|
+
|
|
335
463
|
/**
|
|
336
464
|
* The sidebar's instance list. `route`: the page's (which instance is open, and which view a row opens). `items`: the
|
|
337
465
|
* rows (status frames' instances, or View's roster for an anonymous reader); `loaded` false shows a skeleton.
|
|
@@ -341,22 +469,25 @@ const statusText = (facet) => (facet === "needs" ? t("app.needsYou") : facet ===
|
|
|
341
469
|
export function InstanceNav({ route, items, loaded = true, exec = {}, awaiting = {}, viewOnly = false, onPick }) {
|
|
342
470
|
const nav = useStore(navStore);
|
|
343
471
|
const names = useStore(namesStore); // #1722: which name leads
|
|
472
|
+
useIdentityColors(); // #1958: repaint when the host's colour settings or the themes switch change
|
|
344
473
|
const filterRef = useRef(null);
|
|
345
474
|
const list = useRef(null);
|
|
346
475
|
const drag = useRef(null);
|
|
476
|
+
// #2179: the reply times and their highlight move on by themselves — the list is drawn again every 30 s.
|
|
477
|
+
const [, setTick] = useState(0);
|
|
478
|
+
useLayoutEffect(() => { const id = setInterval(() => setTick((n) => n + 1), 30000); return () => clearInterval(id); }, []);
|
|
347
479
|
// #1700: which facet groups are open — this device's (screen layout, not a synced preference), read when the list mounts.
|
|
348
480
|
const [facetOpen, setFacetOpen] = useState(readFacetsOpen);
|
|
349
481
|
const toggleFacet = (key) => setFacetOpen((cur) => { const next = { ...cur, [key]: !cur[key] }; writeFacetsOpen(next); return next; });
|
|
350
482
|
// #1664: "☆ Edit" — the stars on every row (on a phone the only way to them) and ↑ ↓ on the favorites — only on the
|
|
351
483
|
// full list, as drag: in a filtered one the row above may not be the one above in the list (#1677 review).
|
|
352
484
|
const [editing, setEditing] = useState(false);
|
|
353
|
-
// Where a row goes: the view the page is in (#1523 §3.1). View → View; Details,
|
|
354
|
-
//
|
|
485
|
+
// Where a row goes: the view the page is in (#1523 §3.1). View → View; Details → Details; everywhere else, a Fleet tab
|
|
486
|
+
// included (#1917: the user's call, replacing Q4 = A), → Chat. The anonymous reader only ever has View.
|
|
355
487
|
const rowPath = viewOnly || (route && route.panel === "view") ? viewPath
|
|
356
488
|
: route && route.panel === "log" ? logPath // #1218: Log → Log
|
|
357
|
-
: route &&
|
|
489
|
+
: route && route.panel === "details" ? detailsPath : chatPath;
|
|
358
490
|
const active = route && (route.panel === "chat" || route.panel === "view" || route.panel === "log" || route.panel === "details") ? route.instance : null;
|
|
359
|
-
useLayoutEffect(() => { keepActiveInView(list.current); }, [active, items.length, loaded]);
|
|
360
491
|
const own = (map, name) => Object.prototype.hasOwnProperty.call(map, name);
|
|
361
492
|
const byName = new Map(items.map((it) => [nameOf(it), it]));
|
|
362
493
|
const facetOf = (name) => statusFacet(byName.get(name), own(exec, name) ? exec[name] : null, own(awaiting, name) ? awaiting[name] : null);
|
|
@@ -368,18 +499,44 @@ export function InstanceNav({ route, items, loaded = true, exec = {}, awaiting =
|
|
|
368
499
|
const favs = nav.favorites.filter((n) => known.has(n));
|
|
369
500
|
const favSet = new Set(favs);
|
|
370
501
|
const canStar = !viewOnly && prefsWritable();
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
502
|
+
// #1920: a guest's list is to pick from — no reordering and no filters (one this device kept would hide rows with no
|
|
503
|
+
// way to clear it).
|
|
504
|
+
const guest = guestPage();
|
|
505
|
+
// #2179: when each row last replied (a guest's list has no times). "recent" mode (never a guest's): the favorites keep
|
|
506
|
+
// their order or are sorted too, and every other row is one list, the last reply first. "custom" (the default) is the
|
|
507
|
+
// person's own order — a reply never moves a row.
|
|
508
|
+
const now = Date.now();
|
|
509
|
+
// The anonymous View reader (viewOnly) has no reply times (/api/profiles carries none) and no way to change the sort:
|
|
510
|
+
// its list is always Custom, whatever this browser kept from a signed-in page (#2181 review).
|
|
511
|
+
const ageOf = (n) => (guest || viewOnly ? null : replyAge(byName.get(n), now));
|
|
512
|
+
const recent = !guest && !viewOnly && nav.sort.mode === "recent";
|
|
513
|
+
const favsSorted = recent && nav.sort.favs === "recent";
|
|
514
|
+
let shownGroups, shownInsts;
|
|
515
|
+
if (recent) {
|
|
516
|
+
const sortRecent = byRecentReply(ageOf);
|
|
517
|
+
const rest = groupNames.flatMap((g) => instByGroup.get(g) || []).filter((n) => !favSet.has(n)).sort(sortRecent);
|
|
518
|
+
shownGroups = [...(favs.length ? [FAV_GROUP] : []), ...(rest.length ? [RECENT_GROUP] : [])];
|
|
519
|
+
shownInsts = new Map([[FAV_GROUP, favsSorted ? [...favs].sort(sortRecent) : favs], [RECENT_GROUP, rest]]);
|
|
520
|
+
} else {
|
|
521
|
+
shownGroups = favs.length ? [FAV_GROUP, ...groupNames] : groupNames;
|
|
522
|
+
shownInsts = new Map([...instByGroup].map(([g, names]) => [g, names.filter((n) => !favSet.has(n))]));
|
|
523
|
+
if (favs.length) shownInsts.set(FAV_GROUP, favs);
|
|
524
|
+
}
|
|
525
|
+
const f = guest ? { q: "", status: [], cli: [] } : nav.filter;
|
|
375
526
|
const shown = filterSidebar(shownGroups, shownInsts, byName, f.q, backendLabel, { status: f.status, cli: f.cli, statusOf: facetOf });
|
|
527
|
+
// #1936: while a filter is on, the instance you have open stays on top, pinned and marked "Current", whether or not
|
|
528
|
+
// it matches; a match is not repeated below. "N / M" still counts the filter's matches (the pinned row counts only
|
|
529
|
+
// when it is one). Clearing the filter puts it back in its place, and the list scrolls to it again.
|
|
530
|
+
const pinned = shown.active && active && byName.has(active) ? active : null;
|
|
531
|
+
const listed = pinned ? shown.groups.map((x) => ({ ...x, names: x.names.filter((n) => n !== pinned) })).filter((x) => x.names.length) : shown.groups;
|
|
532
|
+
useLayoutEffect(() => { keepActiveInView(list.current); }, [active, items.length, loaded, shown.active]);
|
|
376
533
|
const backends = [...new Set(items.map((it) => it.backend).filter((b) => typeof b === "string" && b))].sort((a, b) => backendLabel(a).localeCompare(backendLabel(b)));
|
|
377
534
|
const toggle = (g) => { const c = new Set(navStore.get().collapsed); if (c.has(g)) c.delete(g); else c.add(g); setCollapsed(c); };
|
|
378
535
|
// From the store, not this render's copy: two clicks before the next render must both count.
|
|
379
536
|
const flip = (key, value) => { const cur = new Set(navStore.get().filter[key]); if (cur.has(value)) cur.delete(value); else cur.add(value); setNavFilter({ [key]: [...cur] }); };
|
|
380
537
|
// Drag to reorder: only the full list (a filtered subset would be ambiguous), groups among groups, instances within
|
|
381
538
|
// their group.
|
|
382
|
-
const dragProps = (item) => shown.active ? {} : {
|
|
539
|
+
const dragProps = (item) => shown.active || guest || recent ? {} : { // #2179: not while sorted by reply
|
|
383
540
|
draggable: true,
|
|
384
541
|
onDragStart: (e) => { drag.current = item; e.currentTarget.classList.add("dragging"); try { e.dataTransfer.effectAllowed = "move"; e.dataTransfer.setData("text/plain", item.name); } catch { /* Firefox needs data */ } },
|
|
385
542
|
onDragEnd: (e) => { e.currentTarget.classList.remove("dragging"); drag.current = null; },
|
|
@@ -431,31 +588,48 @@ export function InstanceNav({ route, items, loaded = true, exec = {}, awaiting =
|
|
|
431
588
|
const waiting = own(awaiting, name) ? awaiting[name] : null;
|
|
432
589
|
const on = name === active;
|
|
433
590
|
const fav = g === FAV_GROUP, starred = favSet.has(name);
|
|
591
|
+
// #1958: its identity colour — the ring, a left bar and/or the name, as resolved; painted through the CSSOM.
|
|
592
|
+
const id = identityOf(it);
|
|
434
593
|
const full = !starred && favs.length >= MAX_FAVORITES;
|
|
435
|
-
|
|
594
|
+
// #2179 (B5, the user's pick): the % (and the ★ beside the row) on the name's line; the time of the last reply at the
|
|
595
|
+
// end of the second line, highlighted for REPLY_FRESH_MIN minutes, fading by itself (a reply under a minute pulses once).
|
|
596
|
+
const mins = ageOf(name), fade = replyFade(mins);
|
|
597
|
+
const two = !!secondary || mins != null;
|
|
598
|
+
return html`<li key=${name} class=${`v-li${two ? " two-line" : ""}`}><a class=${`inst v-inst${on ? " active" : ""}${fade > 0 ? " replied" : ""}${mins != null && mins < 1 ? " just" : ""} ${identityClasses(id)}`}
|
|
599
|
+
ref=${(el) => { paintIdentity(el, id); paintReply(el, fade); }} href=${rowPath(name)} title=${instanceTooltip(it)}
|
|
436
600
|
aria-current=${on ? "page" : undefined} onClick=${onPick} ...${fav ? {} : dragProps({ type: "instance", name, group: g })}>
|
|
437
601
|
<${InstanceAvatar} it=${it} state=${dotState(it, own(exec, name) ? exec[name] : null, waiting)} cli=${true} />
|
|
438
|
-
<span class="inst-text"><span class="inst-name" title=${label}>${label}</span>
|
|
439
|
-
|
|
602
|
+
<span class="inst-text"><span class="inst-line"><span class="inst-name" title=${label}>${label}</span>
|
|
603
|
+
<span class="v-meta">${it.context_pct != null ? `${Math.round(it.context_pct)}%` : ""}</span></span>
|
|
604
|
+
${two ? html`<span class="inst-line inst-line2"><span class="inst-alt" title=${secondary || undefined}>${secondary || ""}</span>
|
|
605
|
+
${mins != null ? html`<span class=${`inst-when${fade > 0 ? " fresh" : ""}`} title=${tn("replyTitle", { time: new Date(Date.parse(it.last_reply)).toLocaleString() })}>${replyText(mins)}</span>` : null}</span>` : null}
|
|
440
606
|
${facet === "needs" ? html`<span class="inst-sub"><span class="badge-await" title=${waiting || t("app.approxNote")}>${t("app.needsYou")}</span></span>` : null}
|
|
441
607
|
<span class="sr-only">${tn("tooltipBackend", { value: backendLabel(it.backend) })}</span></span>
|
|
442
|
-
<span class="v-meta">${it.context_pct != null ? `${Math.round(it.context_pct)}%` : ""}</span>
|
|
443
608
|
</a>${fav && editing && canStar ? html`<button type="button" class="icon-btn inst-move" aria-label=${tn("favUp", { name: label })}
|
|
444
|
-
title=${shown.active ? tn("favFiltered") : tn("favUp", { name: label })} disabled=${shown.active || i === 0} onClick=${() => { if (!shown.active) moveFavorite(name, -1, known); }}><${Icon} name="up" size=${14} /></button><button type="button" class="icon-btn inst-move"
|
|
445
|
-
aria-label=${tn("favDown", { name: label })} title=${shown.active ? tn("favFiltered") : tn("favDown", { name: label })} disabled=${shown.active || i === count - 1}
|
|
446
|
-
onClick=${() => { if (!shown.active) moveFavorite(name, 1, known); }}><${Icon} name="down" size=${14} /></button>` : null}${canStar
|
|
609
|
+
title=${shown.active ? tn("favFiltered") : favsSorted ? tn("favSorted") : tn("favUp", { name: label })} disabled=${shown.active || favsSorted || i === 0} onClick=${() => { if (!shown.active && !favsSorted) moveFavorite(name, -1, known); }}><${Icon} name="up" size=${14} /></button><button type="button" class="icon-btn inst-move"
|
|
610
|
+
aria-label=${tn("favDown", { name: label })} title=${shown.active ? tn("favFiltered") : favsSorted ? tn("favSorted") : tn("favDown", { name: label })} disabled=${shown.active || favsSorted || i === count - 1}
|
|
611
|
+
onClick=${() => { if (!shown.active && !favsSorted) moveFavorite(name, 1, known); }}><${Icon} name="down" size=${14} /></button>` : null}${canStar
|
|
447
612
|
? html`<button type="button" class=${`icon-btn inst-star${starred ? " on" : ""}`} aria-pressed=${starred ? "true" : "false"} disabled=${full}
|
|
448
613
|
aria-label=${tn(starred ? "favRemove" : "favAdd", { name: label })} title=${full ? tn("favFull", { max: MAX_FAVORITES }) : tn(starred ? "favRemove" : "favAdd", { name: label })}
|
|
449
614
|
onClick=${() => toggleFavorite(name, known)}><${Icon} name="star" size=${14} /></button>` : null}</li>`;
|
|
450
615
|
};
|
|
451
616
|
// The list scrolls; the filter under it stays in view (#999), so it sits outside the scrolling section.
|
|
452
617
|
return html`<div class=${`view-roster inst-nav${editing && canStar ? " fav-editing" : ""}`}>
|
|
453
|
-
<div class="side-section" id="instanceList" ref=${list}>
|
|
618
|
+
<div data-trust="navigation" class="side-section" id="instanceList" ref=${list}>
|
|
454
619
|
<div class="inst-head"><h2 class="side-label">${tn("instancesLabel")}</h2>
|
|
620
|
+
${canStar && items.length && !guest ? html`<${SortMenu} sort=${nav.sort} />`
|
|
621
|
+
: recent ? html`<span class="chip-btn nav-sort-btn on nav-sort-static"><${Icon} name="sort" size=${12} /><span>${tn("sortRecent")}</span></span>` : null}
|
|
455
622
|
${canStar && items.length ? html`<button type="button" class="chip-btn fav-edit" aria-pressed=${editing ? "true" : "false"} onClick=${() => setEditing(!editing)}>
|
|
456
623
|
${editing ? tn("favDone") : html`<${Icon} name="star" size=${12} />${tn("favEdit")}`}</button>` : null}</div>
|
|
457
624
|
${!loaded ? html`<${Skeleton} lines=${4} />` : !items.length ? html`<p class="side-empty">${t("app.noInstances")}</p>` : null}
|
|
458
|
-
${
|
|
625
|
+
${pinned ? html`<div key=${PINNED_GROUP} class="v-group v-pinned" role="group" aria-label=${tn("pinnedLabel")}>
|
|
626
|
+
<div class="v-group-head pinned-head"><span class="grow">${tn("pinnedCurrent")}</span></div>
|
|
627
|
+
<ul class="inst-list">${row(pinned, PINNED_GROUP, 0, 1)}</ul></div>` : null}
|
|
628
|
+
${listed.map(({ group: g, names }) => {
|
|
629
|
+
// #2179: in "recent" mode every other row is one list — open, not dragged, not saved as a group.
|
|
630
|
+
if (g === RECENT_GROUP) return html`<div key=${g} class="v-group v-recent" role="group" aria-label=${tn("recentOthers")}>
|
|
631
|
+
<div class="v-group-head recent-head"><${Icon} name="clock" size=${14} /><span class="grow">${tn("recentOthers")}</span><span class="count">${names.length}</span></div>
|
|
632
|
+
<ul class="inst-list">${names.map((name, i) => row(name, g, i, names.length))}</ul></div>`;
|
|
459
633
|
// #1664: Favorites — always open, not dragged (↑ ↓ in Edit), named in the page's language.
|
|
460
634
|
if (g === FAV_GROUP) return html`<div key=${g} class="v-group v-fav" role="group" aria-label=${tn("favorites")}>
|
|
461
635
|
<div class="v-group-head fav-head"><${Icon} name="star" size=${14} cls="fav-mark" /><span class="grow">${tn("favorites")}</span><span class="count">${names.length}</span></div>
|
|
@@ -463,14 +637,14 @@ export function InstanceNav({ route, items, loaded = true, exec = {}, awaiting =
|
|
|
463
637
|
const folded = nav.collapsed.has(g);
|
|
464
638
|
return html`<div key=${g} class="v-group">
|
|
465
639
|
<button type="button" class="v-group-head" aria-expanded=${folded ? "false" : "true"} onClick=${() => toggle(g)}
|
|
466
|
-
title=${tn(folded ? "expandGroup" : "collapseGroup", { group: g })} ...${dragProps({ type: "group", name: g })}>
|
|
467
|
-
<${Icon} name="chevron" size=${14} cls=${folded ? "caret folded" : "caret"} /><span class="grow">${g}</span><span class="count">${names.length}</span></button>
|
|
640
|
+
title=${tn(folded ? "expandGroup" : "collapseGroup", { group: groupLabel(g) })} ...${dragProps({ type: "group", name: g })}>
|
|
641
|
+
<${Icon} name="chevron" size=${14} cls=${folded ? "caret folded" : "caret"} /><${GroupMark} group=${g} /><span class="grow">${groupLabel(g)}</span><span class="count">${names.length}</span></button>
|
|
468
642
|
${folded ? null : html`<ul class="inst-list">${names.map((name, i) => row(name, g, i, names.length))}</ul>`}
|
|
469
643
|
</div>`;
|
|
470
644
|
})}
|
|
471
645
|
${loaded && shown.active && !shown.groups.length ? html`<p class="side-empty">${tn("filterNone")}</p>` : null}
|
|
472
646
|
</div>
|
|
473
|
-
|
|
647
|
+
${guest ? null : html`<div class="v-filter">
|
|
474
648
|
<label class="v-filter-box"><${Icon} name="search" size=${14} /><span class="sr-only">${tn("filterPh")}</span>
|
|
475
649
|
<input ref=${filterRef} id="filterInput" type="text" autocomplete="off" spellcheck=${false} placeholder=${tn("filterPh")} value=${f.q}
|
|
476
650
|
onInput=${(e) => setNavFilter({ q: e.target.value })}
|
|
@@ -486,7 +660,7 @@ export function InstanceNav({ route, items, loaded = true, exec = {}, awaiting =
|
|
|
486
660
|
</div>
|
|
487
661
|
<p class="note nav-shown" aria-live="polite">${shown.shown} / ${shown.total}${shown.active ? ` ${tn("filterShown")}` : ""}</p>
|
|
488
662
|
</div>
|
|
489
|
-
</div
|
|
663
|
+
</div>`}
|
|
490
664
|
</div>`;
|
|
491
665
|
}
|
|
492
666
|
|