@songsid/agend 2.3.0-beta.2 → 2.3.0-beta.21
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/README.md +7 -1
- package/README.zh-TW.md +7 -1
- package/dist/agent-cli-instructions.md +9 -2
- package/dist/agent-cli.js +63 -2
- package/dist/agent-cli.js.map +1 -1
- package/dist/agent-endpoint.js +4 -0
- package/dist/agent-endpoint.js.map +1 -1
- package/dist/agent-file-read.d.ts +4 -1
- package/dist/agent-file-read.js +189 -16
- package/dist/agent-file-read.js.map +1 -1
- package/dist/agent-whats-new.d.ts +11 -0
- package/dist/agent-whats-new.js +29 -5
- package/dist/agent-whats-new.js.map +1 -1
- package/dist/auth-api.d.ts +30 -1
- package/dist/auth-api.js +45 -13
- 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/claude-code.d.ts +2 -2
- package/dist/backend/claude-code.js +18 -8
- package/dist/backend/claude-code.js.map +1 -1
- package/dist/backend/codex-metadata.d.ts +8 -0
- package/dist/backend/codex-metadata.js +74 -17
- package/dist/backend/codex-metadata.js.map +1 -1
- package/dist/backend/codex.js +15 -2
- package/dist/backend/codex.js.map +1 -1
- package/dist/backend/credential-profile.d.ts +2 -0
- package/dist/backend/credential-profile.js +2 -0
- package/dist/backend/credential-profile.js.map +1 -1
- package/dist/backend/types.d.ts +6 -0
- package/dist/backend/types.js +15 -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 +23 -3
- package/dist/backend-command.js +25 -4
- package/dist/backend-command.js.map +1 -1
- package/dist/bug-report-cli.d.ts +30 -0
- package/dist/bug-report-cli.js +138 -0
- package/dist/bug-report-cli.js.map +1 -0
- package/dist/bug-report.d.ts +47 -0
- package/dist/bug-report.js +136 -0
- package/dist/bug-report.js.map +1 -0
- 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-ledger.d.ts +16 -3
- package/dist/cache-ledger.js +33 -13
- package/dist/cache-ledger.js.map +1 -1
- package/dist/cache-service.d.ts +55 -3
- package/dist/cache-service.js +152 -16
- 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 +9 -0
- package/dist/channel/adapters/discord.js +49 -4
- package/dist/channel/adapters/discord.js.map +1 -1
- package/dist/channel/mcp-tools.js +22 -11
- package/dist/channel/mcp-tools.js.map +1 -1
- package/dist/channel/types.d.ts +6 -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 +224 -111
- package/dist/cli.js.map +1 -1
- package/dist/config-validator.d.ts +6 -0
- package/dist/config-validator.js +129 -3
- package/dist/config-validator.js.map +1 -1
- package/dist/config.d.ts +13 -1
- package/dist/config.js +14 -5
- package/dist/config.js.map +1 -1
- package/dist/cross-instance-notice.d.ts +7 -0
- package/dist/cross-instance-notice.js +10 -0
- package/dist/cross-instance-notice.js.map +1 -1
- package/dist/daemon.d.ts +24 -1
- package/dist/daemon.js +151 -19
- 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 +17 -1
- package/dist/delivery-outbox.js.map +1 -1
- package/dist/diagnostic-redaction.d.ts +10 -0
- package/dist/diagnostic-redaction.js +167 -0
- package/dist/diagnostic-redaction.js.map +1 -0
- package/dist/fleet-context.d.ts +10 -0
- package/dist/fleet-manager.d.ts +505 -15
- package/dist/fleet-manager.js +1870 -204
- 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/bug-report/SKILL.md +31 -0
- package/dist/general-knowledge/skills/credential-profiles/SKILL.md +17 -6
- package/dist/general-knowledge/skills/cross-instance-messaging/SKILL.md +6 -1
- package/dist/general-knowledge/skills/fleet-config/SKILL.md +2 -0
- 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/scheduling/SKILL.md +4 -0
- package/dist/general-knowledge/skills/web-appearance/SKILL.md +190 -47
- package/dist/general-knowledge/skills/web-dashboard/SKILL.md +545 -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-config-impact.js +7 -1
- package/dist/instance-config-impact.js.map +1 -1
- package/dist/instance-lifecycle.d.ts +10 -1
- package/dist/instance-lifecycle.js +79 -18
- 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 +173 -12
- package/dist/locale.js.map +1 -1
- package/dist/log-steps.d.ts +1 -0
- package/dist/log-steps.js +15 -0
- package/dist/log-steps.js.map +1 -1
- package/dist/login-controller.d.ts +45 -0
- package/dist/login-controller.js +101 -5
- package/dist/login-controller.js.map +1 -1
- package/dist/ls-attach-resolve.d.ts +96 -0
- package/dist/ls-attach-resolve.js +180 -0
- package/dist/ls-attach-resolve.js.map +1 -0
- package/dist/ls-rows.d.ts +12 -0
- package/dist/ls-rows.js +82 -3
- package/dist/ls-rows.js.map +1 -1
- 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.d.ts +10 -0
- package/dist/needs-you.js +2 -1
- package/dist/needs-you.js.map +1 -1
- package/dist/outbound-handlers.d.ts +11 -0
- package/dist/outbound-handlers.js +139 -11
- package/dist/outbound-handlers.js.map +1 -1
- package/dist/outbound-schemas.d.ts +200 -4
- package/dist/outbound-schemas.js +124 -11
- package/dist/outbound-schemas.js.map +1 -1
- package/dist/public-web-gateway.js +17 -4
- 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 +19 -1
- package/dist/quickstart-api.js +94 -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/restart-progress.d.ts +80 -1
- package/dist/restart-progress.js +306 -21
- 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/db.d.ts +40 -13
- package/dist/scheduler/db.js +123 -89
- package/dist/scheduler/db.js.map +1 -1
- package/dist/scheduler/task-board-text.d.ts +26 -0
- package/dist/scheduler/task-board-text.js +135 -0
- package/dist/scheduler/task-board-text.js.map +1 -0
- package/dist/scheduler/task-board.d.ts +650 -0
- package/dist/scheduler/task-board.js +1938 -0
- package/dist/scheduler/task-board.js.map +1 -0
- package/dist/scheduler/types.d.ts +6 -1
- package/dist/search/search-docs.d.ts +58 -0
- package/dist/search/search-docs.js +63 -0
- package/dist/search/search-docs.js.map +1 -0
- package/dist/search/search-index.d.ts +141 -0
- package/dist/search/search-index.js +440 -0
- package/dist/search/search-index.js.map +1 -0
- package/dist/search/search-store.d.ts +122 -0
- package/dist/search/search-store.js +236 -0
- package/dist/search/search-store.js.map +1 -0
- package/dist/search/search-worker.d.ts +1 -0
- package/dist/search/search-worker.js +163 -0
- package/dist/search/search-worker.js.map +1 -0
- package/dist/service-installer.js +12 -0
- package/dist/service-installer.js.map +1 -1
- package/dist/settings-api.d.ts +23 -0
- package/dist/settings-api.js +164 -3
- package/dist/settings-api.js.map +1 -1
- package/dist/settings-change.js +104 -0
- package/dist/settings-change.js.map +1 -1
- package/dist/settings-confirmation.d.ts +3 -1
- package/dist/settings-confirmation.js +2 -0
- package/dist/settings-confirmation.js.map +1 -1
- package/dist/settings-effect.js +34 -3
- package/dist/settings-effect.js.map +1 -1
- package/dist/settings-http-confirmation.d.ts +11 -0
- package/dist/settings-http-confirmation.js +10 -0
- package/dist/settings-http-confirmation.js.map +1 -1
- package/dist/setup-form.d.ts +1 -1
- package/dist/setup-form.js +23 -7
- package/dist/setup-form.js.map +1 -1
- package/dist/spawn-gate.d.ts +1 -0
- package/dist/spawn-gate.js +13 -3
- package/dist/spawn-gate.js.map +1 -1
- package/dist/task-board-tool.d.ts +25 -0
- package/dist/task-board-tool.js +154 -0
- package/dist/task-board-tool.js.map +1 -0
- package/dist/task-board-waker.d.ts +44 -0
- package/dist/task-board-waker.js +128 -0
- package/dist/task-board-waker.js.map +1 -0
- package/dist/tmux-exec-helper.d.ts +94 -0
- package/dist/tmux-exec-helper.js +407 -0
- package/dist/tmux-exec-helper.js.map +1 -0
- package/dist/tmux-manager.d.ts +5 -0
- package/dist/tmux-manager.js +42 -11
- package/dist/tmux-manager.js.map +1 -1
- package/dist/tool-permissions.d.ts +0 -7
- package/dist/tool-permissions.js +44 -5
- package/dist/tool-permissions.js.map +1 -1
- package/dist/topic-commands.d.ts +17 -1
- package/dist/topic-commands.js +45 -19
- package/dist/topic-commands.js.map +1 -1
- package/dist/tunnel/cloudflared.js +37 -2
- package/dist/tunnel/cloudflared.js.map +1 -1
- package/dist/tunnel/types.d.ts +3 -1
- package/dist/tunnel/types.js.map +1 -1
- package/dist/types.d.ts +63 -1
- package/dist/ui/app-effects.js +90 -32
- package/dist/ui/backend-signin.js +297 -0
- package/dist/ui/chat-ambient.js +307 -0
- package/dist/ui/chat-effect-posters.js +8 -8
- package/dist/ui/chat-effects.js +110 -15
- package/dist/ui/chat-msg-actions.js +176 -0
- package/dist/ui/chat-render.js +21 -3
- package/dist/ui/chat-store.js +64 -12
- package/dist/ui/chat-strings.js +85 -8
- package/dist/ui/chat-thread.js +204 -21
- package/dist/ui/effect-trial-controller.js +13 -8
- package/dist/ui/effect-trial-panel.js +19 -16
- package/dist/ui/effects-waker.js +70 -0
- package/dist/ui/fleet-canvas-layout.js +117 -0
- package/dist/ui/fleet-canvas.js +555 -0
- package/dist/ui/fleet-org.js +27 -12
- package/dist/ui/fleet-route-scene.js +207 -0
- package/dist/ui/fleet-route-worker.js +21 -0
- package/dist/ui/fleet-route.js +88 -0
- package/dist/ui/fleet-task-card.js +436 -0
- package/dist/ui/fleet-task-common.js +448 -0
- package/dist/ui/fleet-tasks.js +535 -0
- package/dist/ui/fleet-traffic.js +359 -0
- package/dist/ui/fleet-usage.js +435 -0
- package/dist/ui/panel-chat.js +264 -66
- package/dist/ui/panel-details.js +2 -1
- package/dist/ui/panel-fleet.js +65 -83
- package/dist/ui/panel-needs.js +4 -2
- package/dist/ui/panel-settings.js +56 -11
- package/dist/ui/preview.js +3 -2
- package/dist/ui/search-dialog.js +349 -0
- package/dist/ui/settings-appearance.js +44 -19
- package/dist/ui/settings-apply.js +3 -3
- package/dist/ui/settings-catalog.js +92 -0
- package/dist/ui/settings-confirm.js +7 -2
- package/dist/ui/settings-dialogs.js +40 -12
- package/dist/ui/settings-effects.js +4 -3
- 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 +23 -9
- package/dist/ui/settings-owner-effects.js +15 -13
- package/dist/ui/settings-strings.js +122 -42
- package/dist/ui/settings-web-authority.js +91 -0
- package/dist/ui/shared/agend-auth.js +1 -1
- package/dist/ui/shared/app-i18n.js +11 -2
- package/dist/ui/shared/app-prefs.js +17 -4
- package/dist/ui/shared/app-route.js +19 -1
- package/dist/ui/shared/app-session.js +2 -0
- package/dist/ui/shared/app-shell.js +174 -30
- package/dist/ui/shared/app-store.js +26 -1
- package/dist/ui/shared/app-stream.js +5 -1
- package/dist/ui/shared/app-themes.js +140 -7
- package/dist/ui/shared/app.css +705 -20
- package/dist/ui/shared/app.js +26 -15
- package/dist/ui/shared/card-strings.js +8 -0
- package/dist/ui/shared/effect-app-runtime.js +68 -19
- package/dist/ui/shared/effect-chat-runtime.js +16 -3
- package/dist/ui/shared/effect-frame.js +26 -7
- package/dist/ui/shared/effect-geometry.js +24 -9
- package/dist/ui/shared/effect-owner-source.js +8 -1
- package/dist/ui/shared/effect-policy.js +21 -7
- package/dist/ui/shared/effect-profiler.js +31 -9
- package/dist/ui/shared/effect-registry.js +97 -50
- package/dist/ui/shared/effect-stock-snapshot.js +9 -0
- package/dist/ui/shared/effect-stock-trust.js +62 -10
- package/dist/ui/shared/effect-trial-shadow.js +62 -0
- package/dist/ui/shared/effect-trial-source.js +1 -1
- package/dist/ui/shared/effect-trial-stage.js +54 -31
- package/dist/ui/shared/effect-trial.css +40 -0
- package/dist/ui/shared/fx-motion.js +67 -0
- package/dist/ui/shared/identity-card.js +100 -11
- package/dist/ui/shared/identity-color.js +14 -10
- package/dist/ui/shared/instance-nav.js +179 -23
- package/dist/ui/shared/owner-css.js +30 -1
- package/dist/ui/shared/panel-view.js +24 -4
- package/dist/ui/shared/tokens.css +9 -3
- package/dist/ui/shared/ui-icons.js +2 -0
- package/dist/ui/shared/ui-menu.js +20 -7
- package/dist/ui/shared/ui-toast.js +16 -3
- package/dist/ui/shared/usage-dialog.js +34 -3
- package/dist/ui/shared/view-strings.js +72 -0
- package/dist/ui/shared/web-authorize.js +198 -0
- package/dist/ui/web-terminal/terminal.css +4 -4
- package/dist/ui/web-terminal/terminal.html +1 -1
- package/dist/update-launch.d.ts +9 -0
- package/dist/update-launch.js +38 -12
- package/dist/update-launch.js.map +1 -1
- package/dist/usage/format-rich.js +6 -3
- package/dist/usage/format-rich.js.map +1 -1
- package/dist/usage/i18n-keys.d.ts +1 -1
- package/dist/usage/i18n-keys.js +2 -1
- package/dist/usage/i18n-keys.js.map +1 -1
- package/dist/usage/i18n.d.ts +13 -1
- package/dist/usage/i18n.js +29 -0
- package/dist/usage/i18n.js.map +1 -1
- package/dist/usage/providers.d.ts +81 -2
- package/dist/usage/providers.js +240 -38
- package/dist/usage/providers.js.map +1 -1
- package/dist/usage/usage-api.d.ts +5 -1
- package/dist/usage/usage-api.js +184 -58
- package/dist/usage/usage-api.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/user-systemd.d.ts +24 -0
- package/dist/user-systemd.js +75 -0
- package/dist/user-systemd.js.map +1 -0
- package/dist/view-api.d.ts +39 -0
- package/dist/view-api.js +69 -6
- 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 +98 -14
- package/dist/web-api.js +1027 -83
- package/dist/web-api.js.map +1 -1
- package/dist/web-appearance-policy.d.ts +4 -6
- package/dist/web-appearance-policy.js +5 -12
- package/dist/web-appearance-policy.js.map +1 -1
- package/dist/web-appearance-tools.d.ts +3 -0
- package/dist/web-appearance-tools.js +22 -3
- package/dist/web-appearance-tools.js.map +1 -1
- package/dist/web-appearance-view.d.ts +27 -2
- package/dist/web-appearance-view.js +129 -14
- package/dist/web-appearance-view.js.map +1 -1
- package/dist/web-appearance.d.ts +17 -3
- package/dist/web-appearance.js +85 -8
- package/dist/web-appearance.js.map +1 -1
- package/dist/web-asset-parts.d.ts +2 -2
- package/dist/web-asset-parts.js +3 -3
- package/dist/web-asset-parts.js.map +1 -1
- package/dist/web-auth.js +3 -1
- package/dist/web-auth.js.map +1 -1
- package/dist/web-backend-login.d.ts +130 -0
- package/dist/web-backend-login.js +225 -0
- package/dist/web-backend-login.js.map +1 -0
- package/dist/web-card.d.ts +14 -0
- package/dist/web-card.js +40 -6
- package/dist/web-card.js.map +1 -1
- 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 +83 -0
- package/dist/web-chat-history.js +137 -5
- package/dist/web-chat-history.js.map +1 -1
- package/dist/web-commands.d.ts +6 -1
- package/dist/web-commands.js +1 -1
- package/dist/web-commands.js.map +1 -1
- 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.js +46 -1
- package/dist/web-effect-bundle.js.map +1 -1
- 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-profiles.d.ts +4 -4
- package/dist/web-effect-service.d.ts +11 -4
- package/dist/web-effect-service.js +25 -4
- package/dist/web-effect-service.js.map +1 -1
- package/dist/web-effect-tools.d.ts +1 -0
- package/dist/web-effect-tools.js +4 -4
- package/dist/web-effect-tools.js.map +1 -1
- package/dist/web-effect-trials.d.ts +2 -2
- package/dist/web-effect-trials.js +3 -3
- package/dist/web-effect-trials.js.map +1 -1
- package/dist/web-effect-upload.js +1 -1
- package/dist/web-effect-upload.js.map +1 -1
- package/dist/web-effect-worker.d.ts +1 -1
- package/dist/web-effect-worker.js +2 -2
- package/dist/web-effect-worker.js.map +1 -1
- package/dist/web-effect.d.ts +1 -1
- package/dist/web-guest-access.d.ts +126 -0
- package/dist/web-guest-access.js +238 -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-host-guard.d.ts +11 -0
- package/dist/web-host-guard.js +11 -0
- package/dist/web-host-guard.js.map +1 -1
- package/dist/web-identity-color.d.ts +1 -1
- package/dist/web-identity-color.js +2 -2
- package/dist/web-identity-color.js.map +1 -1
- package/dist/web-identity-palette.d.ts +1 -1
- package/dist/web-identity-palette.js +2 -2
- package/dist/web-identity-palette.js.map +1 -1
- package/dist/web-image-meta.d.ts +13 -0
- package/dist/web-image-meta.js +62 -0
- package/dist/web-image-meta.js.map +1 -1
- package/dist/web-libavoid.d.ts +14 -0
- package/dist/web-libavoid.js +46 -0
- package/dist/web-libavoid.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 +1 -1
- package/dist/web-mod-agent.js +7 -5
- package/dist/web-mod-agent.js.map +1 -1
- package/dist/web-mod-bundle.d.ts +9 -1
- package/dist/web-mod-bundle.js +62 -3
- package/dist/web-mod-bundle.js.map +1 -1
- package/dist/web-mod-export.d.ts +9 -0
- package/dist/web-mod-export.js +21 -0
- package/dist/web-mod-export.js.map +1 -1
- package/dist/web-mod-store.d.ts +7 -1
- package/dist/web-mod-store.js +19 -6
- package/dist/web-mod-store.js.map +1 -1
- package/dist/web-owner-css-store.d.ts +44 -1
- package/dist/web-owner-css-store.js +70 -9
- package/dist/web-owner-css-store.js.map +1 -1
- package/dist/web-prefs.d.ts +14 -0
- package/dist/web-prefs.js +37 -4
- package/dist/web-prefs.js.map +1 -1
- package/dist/web-preview.d.ts +6 -2
- package/dist/web-preview.js +4 -2
- 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 +49 -2
- package/dist/web-settings-patch.js +145 -1
- package/dist/web-settings-patch.js.map +1 -1
- package/dist/web-settings-tools.d.ts +94 -0
- package/dist/web-settings-tools.js +301 -0
- package/dist/web-settings-tools.js.map +1 -0
- package/dist/web-shell-routes.d.ts +1 -1
- package/dist/web-shell-routes.js +1 -1
- package/dist/web-shell-routes.js.map +1 -1
- package/dist/web-terminal-http.js +15 -2
- package/dist/web-terminal-http.js.map +1 -1
- package/dist/web-theme-store.d.ts +39 -0
- package/dist/web-theme-store.js +122 -11
- package/dist/web-theme-store.js.map +1 -1
- package/dist/web-theme.d.ts +31 -0
- package/dist/web-theme.js +99 -2
- package/dist/web-theme.js.map +1 -1
- 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/windows-shortcuts.d.ts +78 -0
- package/dist/windows-shortcuts.js +203 -0
- package/dist/windows-shortcuts.js.map +1 -0
- package/dist/workflow-templates/default.md +4 -1
- package/dist/yaml-shared-nodes.d.ts +25 -0
- package/dist/yaml-shared-nodes.js +185 -0
- package/dist/yaml-shared-nodes.js.map +1 -0
- package/launcher/agend-install.cjs +553 -0
- package/launcher/install-admission.cjs +4 -39
- package/launcher/postinstall.cjs +14 -12
- package/package.json +4 -2
- package/scripts/preinstall-guard.cjs +2 -5
- package/templates/windows-shortcuts.ps1 +181 -0
- package/launcher/old-updater-note.cjs +0 -48
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
// #2293: global search (Ctrl+K / ⌘K) — chats, messages between agents and Task Board tasks, as the fleet's index has
|
|
2
|
+
// them (GET /ui/search; an admin's only). A Spotlight-style dialog: the query on top, results under it grouped by agent,
|
|
3
|
+
// ↑↓ and Enter, Esc or a click outside to close; filters folded under one toggle. A chat row still in the agent's
|
|
4
|
+
// history opens there, at the message (#m=); anything else — a chat row that has left the history, a message between
|
|
5
|
+
// agents, a task — opens here as the index's copy (GET /ui/search/context). Text only: no innerHTML, no style attribute.
|
|
6
|
+
import { html, useEffect, useLayoutEffect, useMemo, useRef, useState } from "/assets/app-html.js";
|
|
7
|
+
import { t, register } from "/assets/app-i18n.js";
|
|
8
|
+
import { appStore, useStore } from "/assets/app-store.js";
|
|
9
|
+
import { navigate } from "/assets/app-nav.js";
|
|
10
|
+
import { chatPath, fleetPath } from "/assets/app-route.js";
|
|
11
|
+
import { namesOf, namesStore } from "/assets/instance-nav.js";
|
|
12
|
+
import { Icon } from "/assets/ui-icons.js";
|
|
13
|
+
|
|
14
|
+
register("search", {
|
|
15
|
+
en: {
|
|
16
|
+
label: "Search", placeholder: "Search chats, messages and tasks…", close: "Close", filters: "Filters", filtersOn: "Filters ({0})",
|
|
17
|
+
hint: "Find who you asked about something: a word or two from the chat, a message between agents or a task. Short words (1–2 characters) work too.",
|
|
18
|
+
recent: "Recent searches", clearRecent: "Clear",
|
|
19
|
+
agent: "Agent", anyAgent: "All agents", team: "Team", anyTeam: "All teams", time: "Time", anyTime: "Any time",
|
|
20
|
+
day: "24 hours", week: "7 days", month: "30 days", sources: "In", mine: "Only mine",
|
|
21
|
+
src_chat: "Chats", src_xmsg: "Agent messages", src_task: "Tasks",
|
|
22
|
+
badge_chat: "Chat", badge_xmsg: "Message", badge_task: "Task",
|
|
23
|
+
searching: "Searching…", none: "Nothing found", noneFiltered: "Nothing found with these filters",
|
|
24
|
+
building: "Still indexing — some older results may be missing.",
|
|
25
|
+
limited: "Words of 1–2 characters alone search the newest 5,000 entries only — add a longer word to search everything.",
|
|
26
|
+
unavailable: "Search is off or not available (web.search in fleet.yaml).", busy: "Search is busy — try again in a moment.",
|
|
27
|
+
signedOut: "Signed out — sign in again to search.", failed: "Search failed — try again.",
|
|
28
|
+
keys: "↑↓ move · Enter open · Esc close", results: "Results", back: "Results",
|
|
29
|
+
gone: "This message is no longer in the chat's history — this is the copy kept by search.",
|
|
30
|
+
openChat: "Open chat", openChatOf: "Open {0}'s chat", openTasks: "Open the Task Board", loadingCtx: "Loading…",
|
|
31
|
+
notFound: "No longer found — it may have been removed.", from: "{0} → {1}", groupCount: "{0} results",
|
|
32
|
+
},
|
|
33
|
+
"zh-TW": {
|
|
34
|
+
label: "搜尋", placeholder: "搜尋聊天、訊息與任務…", close: "關閉", filters: "篩選", filtersOn: "篩選({0})",
|
|
35
|
+
hint: "找回某件事當初交給誰:輸入聊天、agent 之間訊息或任務裡的一兩個詞。1–2 個字的短詞也可以。",
|
|
36
|
+
recent: "最近的搜尋", clearRecent: "清除",
|
|
37
|
+
agent: "Agent", anyAgent: "所有 agent", team: "團隊", anyTeam: "所有團隊", time: "時間", anyTime: "不限時間",
|
|
38
|
+
day: "24 小時", week: "7 天", month: "30 天", sources: "範圍", mine: "只看我的",
|
|
39
|
+
src_chat: "聊天", src_xmsg: "Agent 訊息", src_task: "任務",
|
|
40
|
+
badge_chat: "聊天", badge_xmsg: "訊息", badge_task: "任務",
|
|
41
|
+
searching: "搜尋中…", none: "找不到結果", noneFiltered: "這些篩選條件下找不到結果",
|
|
42
|
+
building: "索引仍在建立中,較舊的結果可能還沒出現。",
|
|
43
|
+
limited: "只有 1–2 個字的短詞時,只會搜尋最新的 5,000 筆;加上較長的詞即可搜尋全部。",
|
|
44
|
+
unavailable: "搜尋已關閉或無法使用(fleet.yaml 的 web.search)。", busy: "搜尋忙碌中,請稍後再試。",
|
|
45
|
+
signedOut: "已登出,請重新登入後再搜尋。", failed: "搜尋失敗,請再試一次。",
|
|
46
|
+
keys: "↑↓ 移動 · Enter 開啟 · Esc 關閉", results: "結果", back: "結果",
|
|
47
|
+
gone: "這則訊息已不在聊天紀錄中,以下是搜尋保留的副本。",
|
|
48
|
+
openChat: "開啟聊天", openChatOf: "開啟 {0} 的聊天", openTasks: "開啟 Task Board", loadingCtx: "載入中…",
|
|
49
|
+
notFound: "已找不到,可能已被移除。", from: "{0} → {1}", groupCount: "{0} 筆結果",
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
export const DEBOUNCE_MS = 200;
|
|
54
|
+
export const RECENT_KEY = "agend_search_recent";
|
|
55
|
+
export const RECENT_MAX = 8;
|
|
56
|
+
const SOURCES = ["chat", "xmsg", "task"];
|
|
57
|
+
const SINCE = { any: 0, day: 24 * 3600_000, week: 7 * 24 * 3600_000, month: 30 * 24 * 3600_000 };
|
|
58
|
+
|
|
59
|
+
export function readRecent() {
|
|
60
|
+
try { const v = JSON.parse(localStorage.getItem(RECENT_KEY) || "[]"); return Array.isArray(v) ? v.filter(x => typeof x === "string" && x.trim()).slice(0, RECENT_MAX) : []; } catch { return []; }
|
|
61
|
+
}
|
|
62
|
+
export function rememberSearch(q) {
|
|
63
|
+
const s = (q || "").trim();
|
|
64
|
+
if (!s) return;
|
|
65
|
+
try { localStorage.setItem(RECENT_KEY, JSON.stringify([s, ...readRecent().filter(x => x !== s)].slice(0, RECENT_MAX))); } catch { /* this page only */ }
|
|
66
|
+
}
|
|
67
|
+
function forgetRecent() { try { localStorage.removeItem(RECENT_KEY); } catch { /* nothing kept */ } }
|
|
68
|
+
|
|
69
|
+
/** The request's query string for the query and filters (null: nothing to search). */
|
|
70
|
+
export function searchUrl(q, f, now = Date.now()) {
|
|
71
|
+
const query = (q || "").trim();
|
|
72
|
+
if (!query) return null;
|
|
73
|
+
const p = new URLSearchParams({ q: query, limit: "60" });
|
|
74
|
+
if (f.instance) p.append("instance", f.instance);
|
|
75
|
+
if (f.team) p.set("team", f.team);
|
|
76
|
+
if (f.sources && f.sources.length && f.sources.length < SOURCES.length) for (const s of f.sources) p.append("source", s);
|
|
77
|
+
if (SINCE[f.since]) p.set("since", String(now - SINCE[f.since]));
|
|
78
|
+
if (f.mine) p.set("mine", "1");
|
|
79
|
+
return `/ui/search?${p.toString()}`;
|
|
80
|
+
}
|
|
81
|
+
/** Hits grouped by agent, in the order the newest of each appears; `index` is each hit's place in the flat list. */
|
|
82
|
+
export function groupHits(hits) {
|
|
83
|
+
const groups = new Map();
|
|
84
|
+
hits.forEach((hit) => {
|
|
85
|
+
if (!groups.has(hit.instance)) groups.set(hit.instance, []);
|
|
86
|
+
groups.get(hit.instance).push(hit);
|
|
87
|
+
});
|
|
88
|
+
let i = 0;
|
|
89
|
+
return [...groups].map(([instance, list]) => ({ instance, hits: list.map(hit => ({ hit, index: i++ })) }));
|
|
90
|
+
}
|
|
91
|
+
/** The flat order ↑↓ moves through: the groups', top to bottom. */
|
|
92
|
+
export const flatHits = (groups) => groups.flatMap(g => g.hits.map(x => x.hit));
|
|
93
|
+
/** A snippet as text segments, the matched ones marked (ranges from the server, clipped to the text). */
|
|
94
|
+
export function snippetSegments(text, marks) {
|
|
95
|
+
const s = String(text || ""), out = [];
|
|
96
|
+
let at = 0;
|
|
97
|
+
for (const m of Array.isArray(marks) ? marks : []) {
|
|
98
|
+
const a = Math.max(at, Math.min(s.length, Number(m && m[0]) || 0)), b = Math.max(a, Math.min(s.length, Number(m && m[1]) || 0));
|
|
99
|
+
if (a > at) out.push({ text: s.slice(at, a) });
|
|
100
|
+
if (b > a) out.push({ text: s.slice(a, b), mark: true });
|
|
101
|
+
at = b;
|
|
102
|
+
}
|
|
103
|
+
if (at < s.length) out.push({ text: s.slice(at) });
|
|
104
|
+
return out;
|
|
105
|
+
}
|
|
106
|
+
/** The query's words marked in `text` (case-insensitive, every occurrence; overlapping ones merged) — for the copy view. */
|
|
107
|
+
export function markTerms(text, q) {
|
|
108
|
+
const s = String(text || ""), lower = s.toLowerCase();
|
|
109
|
+
const terms = [...new Set(String(q || "").toLowerCase().split(/\s+/).filter(Boolean))].slice(0, 8);
|
|
110
|
+
const ranges = [];
|
|
111
|
+
for (const term of terms) for (let at = lower.indexOf(term); at !== -1 && ranges.length < 500; at = lower.indexOf(term, at + term.length)) ranges.push([at, at + term.length]);
|
|
112
|
+
ranges.sort((a, b) => a[0] - b[0]);
|
|
113
|
+
const merged = [];
|
|
114
|
+
for (const r of ranges) { const last = merged[merged.length - 1]; if (last && r[0] <= last[1]) last[1] = Math.max(last[1], r[1]); else merged.push([...r]); }
|
|
115
|
+
return snippetSegments(s, merged);
|
|
116
|
+
}
|
|
117
|
+
/** "14:05" today, "10/19 14:05" this year, "2025/10/19" before. */
|
|
118
|
+
export function when(ms, now = Date.now()) {
|
|
119
|
+
const d = new Date(ms), n = new Date(now);
|
|
120
|
+
if (Number.isNaN(d.getTime())) return "";
|
|
121
|
+
const hm = `${String(d.getHours()).padStart(2, "0")}:${String(d.getMinutes()).padStart(2, "0")}`;
|
|
122
|
+
if (d.toDateString() === n.toDateString()) return hm;
|
|
123
|
+
return d.getFullYear() === n.getFullYear() ? `${d.getMonth() + 1}/${d.getDate()} ${hm}` : `${d.getFullYear()}/${d.getMonth() + 1}/${d.getDate()}`;
|
|
124
|
+
}
|
|
125
|
+
/** A chat hit's message key in its agent's chat (#m=<boot>-<id>), or null. */
|
|
126
|
+
export function messageKeyOf(hit) {
|
|
127
|
+
const boot = hit && hit.ref && hit.ref.boot, id = hit && hit.ref && hit.ref.id;
|
|
128
|
+
return typeof boot === "string" && /^[0-9a-f]{1,32}$/.test(boot) && Number.isSafeInteger(id) && id >= 0 ? `${boot}-${id}` : null;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Open an agent's chat at a message: the route, then the address's #m= — the chat reads it as it mounts, and (already
|
|
132
|
+
* open) on the hashchange this sends.
|
|
133
|
+
*/
|
|
134
|
+
export function jumpToMessage(instance, key, w = window) {
|
|
135
|
+
const path = chatPath(instance);
|
|
136
|
+
navigate(path);
|
|
137
|
+
if (!key) return;
|
|
138
|
+
w.history.replaceState(null, "", `${path}#m=${key}`);
|
|
139
|
+
w.dispatchEvent(new Event("hashchange"));
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const errorText = (status) => (status === 404 ? t("search.unavailable") : status === 503 ? t("search.busy") : status === 401 ? t("search.signedOut") : t("search.failed"));
|
|
143
|
+
|
|
144
|
+
export function SearchDialog({ preset = {}, onClose }) {
|
|
145
|
+
const ref = useRef(null), input = useRef(null), list = useRef(null);
|
|
146
|
+
const app = useStore(appStore);
|
|
147
|
+
const names = useStore(namesStore);
|
|
148
|
+
const [q, setQ] = useState(preset.q || "");
|
|
149
|
+
const [f, setF] = useState({ instance: preset.instance || "", team: "", since: "any", sources: [...SOURCES], mine: false });
|
|
150
|
+
const [showFilters, setShowFilters] = useState(!!preset.instance);
|
|
151
|
+
const [teams, setTeams] = useState(null);
|
|
152
|
+
const [res, setRes] = useState({ status: "idle" });
|
|
153
|
+
const [active, setActive] = useState(0);
|
|
154
|
+
const [detail, setDetail] = useState(null);
|
|
155
|
+
const [recent, setRecent] = useState(readRecent);
|
|
156
|
+
const seq = useRef(0);
|
|
157
|
+
|
|
158
|
+
// The native modal: focus trapped, the page inert, Esc → cancel (closed through the parent), a click on the backdrop.
|
|
159
|
+
useLayoutEffect(() => {
|
|
160
|
+
const d = ref.current;
|
|
161
|
+
const opener = typeof document !== "undefined" ? document.activeElement : null;
|
|
162
|
+
if (d && typeof d.showModal === "function" && !d.open) d.showModal();
|
|
163
|
+
input.current && input.current.focus();
|
|
164
|
+
return () => {
|
|
165
|
+
if (d && d.open && typeof d.close === "function") d.close();
|
|
166
|
+
if (opener && typeof opener.focus === "function" && document.contains?.(opener)) opener.focus();
|
|
167
|
+
};
|
|
168
|
+
}, []);
|
|
169
|
+
useEffect(() => {
|
|
170
|
+
const d = ref.current;
|
|
171
|
+
if (!d) return undefined;
|
|
172
|
+
const onCancel = (e) => { e.preventDefault(); onClose(); };
|
|
173
|
+
const onClick = (e) => { if (e.target === d) onClose(); };
|
|
174
|
+
d.addEventListener("cancel", onCancel);
|
|
175
|
+
d.addEventListener("click", onClick);
|
|
176
|
+
return () => { d.removeEventListener("cancel", onCancel); d.removeEventListener("click", onClick); };
|
|
177
|
+
}, [onClose]);
|
|
178
|
+
|
|
179
|
+
// The query, DEBOUNCE_MS after the last change; an answer to an older one is dropped.
|
|
180
|
+
useEffect(() => {
|
|
181
|
+
const url = searchUrl(q, f);
|
|
182
|
+
const mine = ++seq.current;
|
|
183
|
+
if (!url) { setRes({ status: "idle" }); return undefined; }
|
|
184
|
+
setRes(r => ({ ...r, status: "loading" }));
|
|
185
|
+
const ac = typeof AbortController === "function" ? new AbortController() : null;
|
|
186
|
+
const timer = setTimeout(async () => {
|
|
187
|
+
try {
|
|
188
|
+
const r = await fetch(url, { headers: { Accept: "application/json" }, ...(ac ? { signal: ac.signal } : {}) });
|
|
189
|
+
if (mine !== seq.current) return;
|
|
190
|
+
if (!r.ok) { setRes({ status: "error", error: errorText(r.status) }); return; }
|
|
191
|
+
const body = await r.json();
|
|
192
|
+
if (mine !== seq.current) return;
|
|
193
|
+
setRes({ status: "ok", hits: Array.isArray(body.hits) ? body.hits : [], limited: !!body.limited, building: !!body.building });
|
|
194
|
+
setActive(0);
|
|
195
|
+
} catch (err) {
|
|
196
|
+
if (mine === seq.current && !(err && err.name === "AbortError")) setRes({ status: "error", error: t("search.failed") });
|
|
197
|
+
}
|
|
198
|
+
}, DEBOUNCE_MS);
|
|
199
|
+
return () => { clearTimeout(timer); if (ac) ac.abort(); };
|
|
200
|
+
}, [q, f]);
|
|
201
|
+
|
|
202
|
+
// Teams, once the filters are first shown.
|
|
203
|
+
useEffect(() => {
|
|
204
|
+
if (!showFilters || teams) return;
|
|
205
|
+
fetch("/ui/teams", { headers: { Accept: "application/json" } }).then(r => (r.ok ? r.json() : { teams: {} })).then(
|
|
206
|
+
(b) => setTeams(Object.keys((b && b.teams) || {}).sort()), () => setTeams([]));
|
|
207
|
+
}, [showFilters, teams]);
|
|
208
|
+
|
|
209
|
+
const byName = useMemo(() => new Map((app.instances || []).map(it => [it.name ?? it.instance_name, it])), [app.instances]);
|
|
210
|
+
const label = (name) => (byName.has(name) ? namesOf(byName.get(name), names.primary).primary : name);
|
|
211
|
+
// While a new query runs, the last answer stays (dimmed) rather than flashing empty.
|
|
212
|
+
const groups = useMemo(() => groupHits(res.hits || []), [res]);
|
|
213
|
+
const flat = useMemo(() => flatHits(groups), [groups]);
|
|
214
|
+
const activeFilters = (f.instance ? 1 : 0) + (f.team ? 1 : 0) + (f.since !== "any" ? 1 : 0) + (f.sources.length < SOURCES.length ? 1 : 0) + (f.mine ? 1 : 0);
|
|
215
|
+
|
|
216
|
+
// The active row stays in view.
|
|
217
|
+
useEffect(() => {
|
|
218
|
+
const el = list.current && list.current.querySelector(`#search-opt-${active}`);
|
|
219
|
+
if (el && typeof el.scrollIntoView === "function") el.scrollIntoView({ block: "nearest" });
|
|
220
|
+
}, [active, res]);
|
|
221
|
+
|
|
222
|
+
const open = (hit) => {
|
|
223
|
+
if (!hit) return;
|
|
224
|
+
rememberSearch(q); setRecent(readRecent());
|
|
225
|
+
const key = hit.source === "chat" ? messageKeyOf(hit) : null;
|
|
226
|
+
if (key && hit.live) { onClose(); jumpToMessage(hit.instance, key); return; }
|
|
227
|
+
setDetail({ hit, status: "loading" });
|
|
228
|
+
// Any failure — the request, or a body that is not JSON — ends as an error, never "Loading…" for good.
|
|
229
|
+
fetch(`/ui/search/context?key=${encodeURIComponent(hit.key)}`, { headers: { Accept: "application/json" } })
|
|
230
|
+
.then(async (r) => {
|
|
231
|
+
if (!r.ok) { setDetail(d => (d && d.hit === hit ? { hit, status: "error", error: r.status === 404 ? t("search.notFound") : errorText(r.status) } : d)); return; }
|
|
232
|
+
const body = await r.json();
|
|
233
|
+
setDetail(d => (d && d.hit === hit ? { hit, status: "ok", ctx: body } : d));
|
|
234
|
+
})
|
|
235
|
+
.catch(() => setDetail(d => (d && d.hit === hit ? { hit, status: "error", error: t("search.failed") } : d)));
|
|
236
|
+
};
|
|
237
|
+
const onKey = (e) => {
|
|
238
|
+
if (e.isComposing) return;
|
|
239
|
+
if ((e.ctrlKey || e.metaKey) && (e.key === "k" || e.key === "K")) { e.preventDefault(); input.current && input.current.select(); return; }
|
|
240
|
+
// ↑↓ and Enter are the query box's: in the filters a select or a chip keeps its own keys.
|
|
241
|
+
if (detail || e.target !== input.current) return;
|
|
242
|
+
if (e.key === "ArrowDown") { e.preventDefault(); setActive(i => (flat.length ? (i + 1) % flat.length : 0)); }
|
|
243
|
+
else if (e.key === "ArrowUp") { e.preventDefault(); setActive(i => (flat.length ? (i - 1 + flat.length) % flat.length : 0)); }
|
|
244
|
+
else if (e.key === "Enter") { e.preventDefault(); if (flat.length) open(flat[Math.min(active, flat.length - 1)]); else rememberSearch(q); }
|
|
245
|
+
};
|
|
246
|
+
const setFilter = (patch) => setF(x => ({ ...x, ...patch }));
|
|
247
|
+
const toggleSource = (s) => setF(x => {
|
|
248
|
+
const has = x.sources.includes(s);
|
|
249
|
+
const next = has ? x.sources.filter(y => y !== s) : [...x.sources, s];
|
|
250
|
+
return { ...x, sources: next.length ? SOURCES.filter(y => next.includes(y)) : x.sources }; // never none
|
|
251
|
+
});
|
|
252
|
+
const activeId = flat.length && !detail ? `search-opt-${Math.min(active, flat.length - 1)}` : undefined;
|
|
253
|
+
|
|
254
|
+
return html`<dialog data-trust="dialog" ref=${ref} class="dlg search-dlg" aria-label=${t("search.label")} onKeyDown=${onKey}>
|
|
255
|
+
<div class="search-box">
|
|
256
|
+
<div class="search-head">
|
|
257
|
+
<${Icon} name="search" />
|
|
258
|
+
<input ref=${input} class="search-input" type="search" value=${q} placeholder=${t("search.placeholder")} aria-label=${t("search.label")}
|
|
259
|
+
role="combobox" aria-expanded=${flat.length && !detail ? "true" : "false"} aria-controls="search-results" aria-autocomplete="list"
|
|
260
|
+
aria-activedescendant=${activeId} autocomplete="off" spellcheck=${false} enterkeyhint="search"
|
|
261
|
+
onInput=${(e) => { setQ(e.target.value); setDetail(null); }} />
|
|
262
|
+
<button type="button" class=${`chip-btn search-filters-toggle${showFilters ? " on" : ""}`} aria-expanded=${showFilters ? "true" : "false"} aria-controls="search-filters"
|
|
263
|
+
onClick=${() => setShowFilters(v => !v)}><${Icon} name="sliders" size=${14} />${activeFilters ? t("search.filtersOn", activeFilters) : t("search.filters")}</button>
|
|
264
|
+
<button type="button" class="icon-btn" aria-label=${t("search.close")} title=${t("search.close")} onClick=${onClose}><${Icon} name="close" /></button>
|
|
265
|
+
</div>
|
|
266
|
+
${showFilters ? html`<div id="search-filters" class="search-filters" role="group" aria-label=${t("search.filters")}>
|
|
267
|
+
<label class="search-field"><span>${t("search.agent")}</span>
|
|
268
|
+
<select value=${f.instance} onChange=${(e) => setFilter({ instance: e.target.value })}>
|
|
269
|
+
<option value="">${t("search.anyAgent")}</option>
|
|
270
|
+
${[...byName.keys()].sort().map(n => html`<option value=${n}>${label(n)}</option>`)}
|
|
271
|
+
${f.instance && !byName.has(f.instance) ? html`<option value=${f.instance}>${f.instance}</option>` : null}
|
|
272
|
+
</select></label>
|
|
273
|
+
${teams && teams.length ? html`<label class="search-field"><span>${t("search.team")}</span>
|
|
274
|
+
<select value=${f.team} onChange=${(e) => setFilter({ team: e.target.value })}>
|
|
275
|
+
<option value="">${t("search.anyTeam")}</option>${teams.map(n => html`<option value=${n}>${n}</option>`)}</select></label>` : null}
|
|
276
|
+
<div class="search-chips" role="group" aria-label=${t("search.time")}>
|
|
277
|
+
${["any", "day", "week", "month"].map(k => html`<button type="button" class=${`chip-btn${f.since === k ? " on" : ""}`} aria-pressed=${f.since === k ? "true" : "false"}
|
|
278
|
+
onClick=${() => setFilter({ since: k })}>${k === "any" ? t("search.anyTime") : t(`search.${k}`)}</button>`)}
|
|
279
|
+
</div>
|
|
280
|
+
<div class="search-chips" role="group" aria-label=${t("search.sources")}>
|
|
281
|
+
${SOURCES.map(s => html`<button type="button" class=${`chip-btn${f.sources.includes(s) ? " on" : ""}`} aria-pressed=${f.sources.includes(s) ? "true" : "false"}
|
|
282
|
+
onClick=${() => toggleSource(s)}>${t(`search.src_${s}`)}</button>`)}
|
|
283
|
+
<label class="search-mine"><input type="checkbox" checked=${f.mine} onChange=${(e) => setFilter({ mine: e.target.checked })} />${t("search.mine")}</label>
|
|
284
|
+
</div>
|
|
285
|
+
</div>` : null}
|
|
286
|
+
<div class="search-body" ref=${list}>
|
|
287
|
+
${detail ? html`<${Detail} detail=${detail} q=${q} label=${label} onBack=${() => { setDetail(null); input.current && input.current.focus(); }} onClose=${onClose} />`
|
|
288
|
+
: html`<${Results} q=${q} res=${res} groups=${groups} active=${active} setActive=${setActive} open=${open} label=${label} filtered=${activeFilters > 0}
|
|
289
|
+
recent=${recent} pick=${(s) => { setQ(s); input.current && input.current.focus(); }} clearRecent=${() => { forgetRecent(); setRecent([]); }} />`}
|
|
290
|
+
</div>
|
|
291
|
+
<div class="search-foot" aria-hidden="true">${t("search.keys")}</div>
|
|
292
|
+
</div></dialog>`;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function Results({ q, res, groups, active, setActive, open, label, filtered, recent, pick, clearRecent }) {
|
|
296
|
+
if (!q.trim()) {
|
|
297
|
+
return html`<div class="search-empty"><p class="search-hint">${t("search.hint")}</p>
|
|
298
|
+
${recent.length ? html`<div class="search-recent"><div class="search-recent-head"><span>${t("search.recent")}</span>
|
|
299
|
+
<button type="button" class="chip-btn" onClick=${clearRecent}>${t("search.clearRecent")}</button></div>
|
|
300
|
+
<ul>${recent.map(s => html`<li><button type="button" class="search-recent-item" onClick=${() => pick(s)}><${Icon} name="clock" size=${14} /><span>${s}</span></button></li>`)}</ul></div>` : null}</div>`;
|
|
301
|
+
}
|
|
302
|
+
if (res.status === "error") return html`<p class="search-status bad" role="alert">${res.error}</p>`;
|
|
303
|
+
const hits = res.hits || [];
|
|
304
|
+
return html`
|
|
305
|
+
${res.building ? html`<p class="search-note" role="status">${t("search.building")}</p>` : null}
|
|
306
|
+
${res.limited ? html`<p class="search-note" role="status">${t("search.limited")}</p>` : null}
|
|
307
|
+
${res.status === "loading" && !hits.length ? html`<p class="search-status" role="status">${t("search.searching")}</p>` : null}
|
|
308
|
+
${res.status === "ok" && !hits.length ? html`<p class="search-status" role="status">${filtered ? t("search.noneFiltered") : t("search.none")}</p>` : null}
|
|
309
|
+
<div id="search-results" role="listbox" aria-label=${t("search.results")} class=${`search-results${res.status === "loading" ? " stale" : ""}`}>
|
|
310
|
+
${groups.map(g => html`<div role="group" class="search-group" aria-label=${label(g.instance)}>
|
|
311
|
+
<div class="search-group-head" aria-hidden="true"><span>${label(g.instance)}</span><span class="search-count">${t("search.groupCount", g.hits.length)}</span></div>
|
|
312
|
+
${g.hits.map(({ hit, index }) => html`<${Row} hit=${hit} index=${index} on=${index === active} label=${label} onHover=${() => setActive(index)} onOpen=${() => open(hit)} />`)}
|
|
313
|
+
</div>`)}
|
|
314
|
+
</div>`;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
function Row({ hit, index, on, label, onHover, onOpen }) {
|
|
318
|
+
const who = hit.source === "xmsg" ? t("search.from", label(hit.instance), label(hit.peer || "")) : hit.source === "task" ? (hit.role || "") : hit.sender || "";
|
|
319
|
+
return html`<div id=${`search-opt-${index}`} role="option" aria-selected=${on ? "true" : "false"} class=${`search-row${on ? " on" : ""}`}
|
|
320
|
+
onMouseMove=${on ? undefined : onHover} onClick=${onOpen}>
|
|
321
|
+
<div class="search-row-top"><span class=${`search-badge ${hit.source}`}>${t(`search.badge_${hit.source}`)}</span>
|
|
322
|
+
<span class="search-who">${who}${hit.source === "xmsg" && hit.role && hit.role !== "message" ? html` <span class="search-kind">${hit.role}</span>` : null}</span>
|
|
323
|
+
<time class="search-time" datetime=${Number.isFinite(hit.ts) ? new Date(hit.ts).toISOString() : undefined}>${when(hit.ts)}</time></div>
|
|
324
|
+
<div class="search-snippet">${snippetSegments(hit.snippet, hit.marks).map(s => (s.mark ? html`<mark>${s.text}</mark>` : s.text))}</div>
|
|
325
|
+
</div>`;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
function Detail({ detail, q, label, onBack, onClose }) {
|
|
329
|
+
const { hit } = detail;
|
|
330
|
+
const go = (path, key) => { onClose(); if (key !== undefined) jumpToMessage(hit.instance, key); else navigate(path); };
|
|
331
|
+
const head = html`<div class="search-detail-head"><button type="button" class="chip-btn" onClick=${onBack}><${Icon} name="back" size=${14} />${t("search.back")}</button>
|
|
332
|
+
<span class=${`search-badge ${hit.source}`}>${t(`search.badge_${hit.source}`)}</span><span class="search-who">${hit.source === "xmsg" ? t("search.from", label(hit.instance), label(hit.peer || "")) : label(hit.instance)}</span>
|
|
333
|
+
<time class="search-time">${when(hit.ts)}</time></div>`;
|
|
334
|
+
if (detail.status === "loading") return html`<div class="search-detail">${head}<p class="search-status" role="status">${t("search.loadingCtx")}</p></div>`;
|
|
335
|
+
if (detail.status === "error") return html`<div class="search-detail">${head}<p class="search-status bad" role="alert">${detail.error}</p></div>`;
|
|
336
|
+
const ctx = detail.ctx || {};
|
|
337
|
+
const doc = ctx.doc || {};
|
|
338
|
+
const line = (d, here) => html`<li class=${`search-ctx-line${here ? " here" : ""}`}><div class="search-row-top"><span class="search-who">${d.sender || ""}</span><time class="search-time">${when(d.ts)}</time></div>
|
|
339
|
+
<div class="search-ctx-text">${markTerms(d.text, q).map(x => (x.mark ? html`<mark>${x.text}</mark>` : x.text))}</div></li>`;
|
|
340
|
+
let actions;
|
|
341
|
+
if (hit.source === "chat") actions = html`<button type="button" class="btn btn-primary" onClick=${() => go(null, messageKeyOf(hit))}>${t("search.openChat")}</button>`;
|
|
342
|
+
else if (hit.source === "xmsg") actions = html`<button type="button" class="btn" onClick=${() => go(chatPath(hit.instance))}>${t("search.openChatOf", label(hit.instance))}</button>
|
|
343
|
+
${hit.peer ? html`<button type="button" class="btn" onClick=${() => go(chatPath(hit.peer))}>${t("search.openChatOf", label(hit.peer))}</button>` : null}`;
|
|
344
|
+
else actions = html`<button type="button" class="btn btn-primary" onClick=${() => go(fleetPath("tasks"))}>${t("search.openTasks")}</button>`;
|
|
345
|
+
return html`<div class="search-detail">${head}
|
|
346
|
+
${hit.source === "chat" ? html`<p class="search-note">${t("search.gone")}</p>` : null}
|
|
347
|
+
<ol class="search-ctx">${(ctx.before || []).map(d => line(d, false))}${line(doc, true)}${(ctx.after || []).map(d => line(d, false))}</ol>
|
|
348
|
+
<div class="search-detail-actions">${actions}</div></div>`;
|
|
349
|
+
}
|
|
@@ -10,12 +10,14 @@ import { Icon } from "/assets/ui-icons.js";
|
|
|
10
10
|
import { toast } from "/assets/ui-toast.js";
|
|
11
11
|
import { ownerCssOffHere, ownerCssTrial, safeMode, setOwnerCssActive, setOwnerCssOffHere, startOwnerCssTrial } from "/assets/owner-css.js";
|
|
12
12
|
import { setThemesShown, themesStore } from "/assets/app-themes.js";
|
|
13
|
+
import { followsReducedMotion, onMotionChange, setFollowsReducedMotion } from "/assets/fx-motion.js";
|
|
13
14
|
import { CARD_SIZES, cardSizeDefault, cardSizeStore, setCardSizeDefault } from "/assets/identity-card.js";
|
|
14
15
|
import { AgentWidget, checkWidget, fleetBinds, setSidebarWidget, sidebarWidgetRaw, sidebarWidgetStore, widgetProblem } from "/assets/agent-widget.js";
|
|
15
16
|
import { confirmedWrite } from "./settings-confirm.js";
|
|
16
17
|
import { savePref } from "/assets/app-prefs.js";
|
|
17
18
|
import { confirmDialog } from "/assets/ui-confirm.js";
|
|
18
|
-
import { colorsOff, defaultPlaces, PLACEMENTS, setColorsOff, setDefaultPlaces, useIdentityColors } from "/assets/identity-color.js";
|
|
19
|
+
import { colorsOff, defaultPlaces, identityClasses, identityOf, PLACEMENTS, paintIdentity, setColorsOff, setDefaultPlaces, useIdentityColors } from "/assets/identity-color.js";
|
|
20
|
+
import { CardTitle } from "./settings-learn.js";
|
|
19
21
|
import "./settings-strings.js";
|
|
20
22
|
|
|
21
23
|
const tn = (k, ...v) => t(`settings.${k}`, ...v);
|
|
@@ -42,22 +44,38 @@ function refusal(body) {
|
|
|
42
44
|
export function AppearanceSection() {
|
|
43
45
|
const publicLink = typeof document !== "undefined" && document.body && document.body.dataset.publicLink === "1";
|
|
44
46
|
if (publicLink) return html`<section class="card s-appearance"><h3>${tn("cssTitle")}</h3><p class="sub">${tn("cssPublicLink")}</p></section>`;
|
|
45
|
-
|
|
47
|
+
const group = (key) => html`<h2 class="s-ap-group">${tn(key)}</h2>`;
|
|
48
|
+
return html`${group("groupLook")}<${ThemesCard} /><${IdentityColorCard} /><${CardSizeCard} />
|
|
49
|
+
${group("groupLayout")}<${SidebarWidgetCard} />
|
|
50
|
+
${group("groupCss")}<${OwnerCssCard} /><${DeviceCard} />
|
|
51
|
+
${group("groupMods")}<${ModsCard} /><${EffectsCard} />
|
|
52
|
+
${group("groupAdvanced")}<${CardLimitsCard} /><${RestoreDefaultsCard} />`;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** #2056 B: a sample sidebar row in the host's default colour places, so the choice is seen rather than described. */
|
|
56
|
+
const SAMPLE = { color: { person: "preset:teal" } };
|
|
57
|
+
function ColorSample({ places, off }) {
|
|
58
|
+
const id = identityOf(SAMPLE, { hostDefault: places, off });
|
|
59
|
+
return html`<div class="idc-preview s-colors-sample" aria-hidden="true"><span class=${`inst v-inst ${identityClasses(id)}`} ref=${(el) => paintIdentity(el, id)}>
|
|
60
|
+
<span class="av"><span class="av-text">A</span></span><span class="inst-text"><span class="inst-name">${tn("colorSample")}</span></span></span></div>`;
|
|
46
61
|
}
|
|
47
62
|
|
|
48
63
|
/**
|
|
49
|
-
* #1958: identity colours on this host — where they show for an instance nobody chose for (
|
|
50
|
-
* changed), and a switch that turns them off everywhere while every pick is kept.
|
|
64
|
+
* #1958: identity colours on this host — where they show for an instance nobody chose for (#2056: name, bar and
|
|
65
|
+
* ring unless changed), and a switch that turns them off everywhere while every pick is kept.
|
|
51
66
|
*/
|
|
52
67
|
function IdentityColorCard() {
|
|
53
68
|
useIdentityColors();
|
|
54
69
|
const places = defaultPlaces(), off = colorsOff();
|
|
55
70
|
const toggle = (p) => setDefaultPlaces(places.includes(p) ? places.filter((x) => x !== p) : [...places, p]);
|
|
56
|
-
return html`<section class="card s-appearance s-synced s-colors"
|
|
71
|
+
return html`<section class="card s-appearance s-synced s-colors"><${CardTitle} title=${tn("colorTitle")} topic="colors" />
|
|
57
72
|
<p class="sub">${tn("colorLead")}</p>
|
|
58
|
-
<
|
|
59
|
-
|
|
60
|
-
|
|
73
|
+
<div class="s-colors-row">
|
|
74
|
+
<fieldset class="idc-places" disabled=${off}><legend>${tn("colorPlaces")}</legend>
|
|
75
|
+
${PLACEMENTS.map((p) => html`<label key=${p} class="check"><input type="checkbox" checked=${places.includes(p)} onChange=${() => toggle(p)} /> ${t(`color.place_${p}`)}</label>`)}
|
|
76
|
+
</fieldset>
|
|
77
|
+
<${ColorSample} places=${places} off=${off} />
|
|
78
|
+
</div>
|
|
61
79
|
<label class="check"><input type="checkbox" class="s-colors-off" checked=${off} onChange=${(e) => setColorsOff(e.target.checked)} /> <span>${tn("colorOff")}</span></label>
|
|
62
80
|
<p class="sub">${tn("colorOffLead")}</p>
|
|
63
81
|
</section>`;
|
|
@@ -66,10 +84,12 @@ function IdentityColorCard() {
|
|
|
66
84
|
/**
|
|
67
85
|
* #1958 decision 5: Restore defaults — the host's appearance settings back to their defaults after a confirm that says
|
|
68
86
|
* what it resets; this person's custom CSS turned off (its versions stay). Not settings, so never touched: the colours
|
|
69
|
-
* people picked per instance, installed mods, uploaded effects and the
|
|
87
|
+
* people picked per instance (and where each agent's colours go, #2196), installed mods, uploaded effects and the
|
|
88
|
+
* fleet's card limits.
|
|
70
89
|
*/
|
|
71
90
|
export async function restoreAppearanceDefaults() {
|
|
72
91
|
setThemesShown(true);
|
|
92
|
+
setFollowsReducedMotion(false);
|
|
73
93
|
setCardSizeDefault("m");
|
|
74
94
|
savePref("card.sizeByInstance", null);
|
|
75
95
|
setSidebarWidget(null);
|
|
@@ -88,7 +108,7 @@ function RestoreDefaultsCard() {
|
|
|
88
108
|
setBusy(false);
|
|
89
109
|
if (r.ok) toast(tn("restoreDone")); else toast(tn("restoreCssFailed", r.error), false);
|
|
90
110
|
}
|
|
91
|
-
return html`<section class="card s-appearance s-restore"
|
|
111
|
+
return html`<section class="card s-appearance s-restore"><${CardTitle} title=${tn("restoreTitle")} topic="restore" />
|
|
92
112
|
<p class="sub">${tn("restoreLead")}</p>
|
|
93
113
|
<button type="button" class="btn" disabled=${busy} onClick=${go}>${tn("restoreTitle")}</button>
|
|
94
114
|
</section>`;
|
|
@@ -98,7 +118,7 @@ function RestoreDefaultsCard() {
|
|
|
98
118
|
function CardSizeCard() {
|
|
99
119
|
useStore(cardSizeStore);
|
|
100
120
|
const size = cardSizeDefault();
|
|
101
|
-
return html`<section class="card s-appearance s-synced"
|
|
121
|
+
return html`<section class="card s-appearance s-synced"><${CardTitle} title=${tn("cardSizeTitle")} topic="cards" />
|
|
102
122
|
<div class="card-size" role="group" aria-label=${tn("cardSizeTitle")}>
|
|
103
123
|
${CARD_SIZES.map((s) => html`<button key=${s} type="button" class=${`seg-item${s === size ? " active" : ""}`} aria-pressed=${s === size ? "true" : "false"}
|
|
104
124
|
onClick=${() => setCardSizeDefault(s)}>${t(`card.size${s.toUpperCase()}Name`)}</button>`)}
|
|
@@ -131,7 +151,7 @@ function CardLimitsCard() {
|
|
|
131
151
|
};
|
|
132
152
|
useEffect(() => { load(); }, []);
|
|
133
153
|
if (!view) return null;
|
|
134
|
-
if (view.error) return html`<section class="card s-appearance"
|
|
154
|
+
if (view.error) return html`<section class="card s-appearance"><${CardTitle} title=${tn("cardLimitsTitle")} topic="limits" /><p class="sub">${tn("cardLimitsUnavailable")}</p></section>`;
|
|
135
155
|
const configured = (g, k) => (view.configured && view.configured[g] && typeof view.configured[g][k] === "number" ? view.configured[g][k] : null);
|
|
136
156
|
const save = async () => {
|
|
137
157
|
const body = {};
|
|
@@ -148,7 +168,7 @@ function CardLimitsCard() {
|
|
|
148
168
|
if (r.ok) { setMsg(tn("cardLimitsSaved")); load(); } else setMsg(tn("cardLimitsFailed", (r.body && r.body.error) || r.status));
|
|
149
169
|
};
|
|
150
170
|
const warnings = Array.isArray(view.warnings) ? view.warnings : [];
|
|
151
|
-
return html`<section class="card s-appearance"
|
|
171
|
+
return html`<section class="card s-appearance"><${CardTitle} title=${tn("cardLimitsTitle")} topic="limits" />
|
|
152
172
|
<p class="sub">${tn("cardLimitsLead")}</p>
|
|
153
173
|
<fieldset class="form" disabled=${busy}>
|
|
154
174
|
${["portrait", "banner", "instance_quota"].map((g) => html`<div key=${g} class="grid3">
|
|
@@ -170,10 +190,16 @@ function CardLimitsCard() {
|
|
|
170
190
|
/** #1629 r2 (d): whether this person's screens show agents' themes (and, as they come, effects) — synced. */
|
|
171
191
|
function ThemesCard() {
|
|
172
192
|
const s = useStore(themesStore);
|
|
173
|
-
|
|
193
|
+
// #2117: this device's own switch (not synced): effects play unless it follows the system's reduced-motion setting.
|
|
194
|
+
const [follow, setFollow] = useState(followsReducedMotion);
|
|
195
|
+
useEffect(() => onMotionChange(() => setFollow(followsReducedMotion())), []);
|
|
196
|
+
return html`<section class="card s-appearance s-synced"><${CardTitle} title=${tn("themesTitle")} topic="themes" />
|
|
174
197
|
<label class="check"><input type="checkbox" checked=${s.shown} disabled=${safeMode()} onChange=${(e) => setThemesShown(e.target.checked)} />
|
|
175
198
|
<span>${tn("themesShow")}</span></label>
|
|
176
199
|
<p class="sub">${safeMode() ? tn("themesSafeMode") : tn("themesShowLead")}</p>
|
|
200
|
+
<label class="check"><input type="checkbox" id="fx-follow-reduced-motion" checked=${follow} onChange=${(e) => setFollowsReducedMotion(e.target.checked)} />
|
|
201
|
+
<span>${tn("themesFollowMotion")}</span></label>
|
|
202
|
+
<p class="sub">${tn("themesFollowMotionLead")}</p>
|
|
177
203
|
</section>`;
|
|
178
204
|
}
|
|
179
205
|
|
|
@@ -209,7 +235,7 @@ function SidebarWidgetCard() {
|
|
|
209
235
|
toast(tn("widgetSaved"));
|
|
210
236
|
};
|
|
211
237
|
const remove = () => { setSidebarWidget(null); setText(""); setError(""); toast(tn("widgetRemoved")); };
|
|
212
|
-
return html`<section class="card s-appearance s-synced s-widget"
|
|
238
|
+
return html`<section class="card s-appearance s-synced s-widget"><${CardTitle} title=${tn("widgetTitle")} topic="widgets" />
|
|
213
239
|
<p class="sub">${tn("widgetLead")}</p>
|
|
214
240
|
<label class="field"><span class="sr-only">${tn("widgetTitle")}</span>
|
|
215
241
|
<textarea class="s-widget-text" rows="10" spellcheck=${false} autocapitalize="off" autocomplete="off" value=${text}
|
|
@@ -240,8 +266,8 @@ function OwnerCssCard() {
|
|
|
240
266
|
useEffect(() => { load(); }, []);
|
|
241
267
|
// A trial answered (kept or reverted) changes what is in force: read it again, keeping the editor's text.
|
|
242
268
|
useEffect(() => { if (state) load({ text: false }); }, [trial && trial.sha]);
|
|
243
|
-
if (!state) return html`<section class="card s-appearance"
|
|
244
|
-
if (state.failed) return html`<section class="card s-appearance"
|
|
269
|
+
if (!state) return html`<section class="card s-appearance"><${CardTitle} title=${tn("cssTitle")} topic="css" /><p class="sub">…</p></section>`;
|
|
270
|
+
if (state.failed) return html`<section class="card s-appearance"><${CardTitle} title=${tn("cssTitle")} topic="css" /><p class="err">${tn("cssLoadFailed")}</p></section>`;
|
|
245
271
|
|
|
246
272
|
const save = async () => {
|
|
247
273
|
setBusy(true); setError("");
|
|
@@ -285,7 +311,7 @@ function OwnerCssCard() {
|
|
|
285
311
|
? (state.pending ? tn("cssOnTrial", short(state.active)) : tn("cssInForce", short(state.active)))
|
|
286
312
|
: tn("cssNone");
|
|
287
313
|
const assetBytes = state.assets.reduce((n, a) => n + a.bytes, 0);
|
|
288
|
-
return html`<section class="card s-appearance s-owner-css"
|
|
314
|
+
return html`<section class="card s-appearance s-owner-css"><${CardTitle} title=${tn("cssTitle")} topic="css" />
|
|
289
315
|
<p class="sub">${tn("cssLead")}</p>
|
|
290
316
|
<p class="s-css-status" role="status">${inForce}</p>
|
|
291
317
|
<label class="field"><span class="sr-only">${tn("cssTitle")}</span>
|
|
@@ -319,7 +345,6 @@ function DeviceCard() {
|
|
|
319
345
|
return html`<section class="card s-appearance s-local"><h3>${tn("cssDeviceTitle")}</h3>
|
|
320
346
|
<label class="check"><input type="checkbox" checked=${off} onChange=${(e) => { setOwnerCssOffHere(e.target.checked); setOff(ownerCssOffHere()); }} />
|
|
321
347
|
<span>${tn("cssDeviceOff")}</span></label>
|
|
322
|
-
<p class="sub">${tn("cssDeviceOffLead")}</p>
|
|
323
348
|
<p class="sub">${tn("cssSafe")}</p>
|
|
324
349
|
</section>`;
|
|
325
350
|
}
|
|
@@ -11,7 +11,7 @@ import { appStore } from "/assets/app-store.js";
|
|
|
11
11
|
import { t } from "/assets/app-i18n.js";
|
|
12
12
|
import { toast } from "/assets/ui-toast.js";
|
|
13
13
|
import "./settings-strings.js";
|
|
14
|
-
import { api, confirmedWrite, newKey } from "./settings-confirm.js";
|
|
14
|
+
import { api, confirmedWrite, newKey, selfConfirmed } from "./settings-confirm.js";
|
|
15
15
|
|
|
16
16
|
const WATCH_LIMIT_MS = 10 * 60_000;
|
|
17
17
|
const POST_RETRIES = [1000, 2000, 4000];
|
|
@@ -114,7 +114,7 @@ async function perform(step, o) {
|
|
|
114
114
|
if (step.request) {
|
|
115
115
|
const { method, url, body } = step.request;
|
|
116
116
|
return confirmedWrite(url, { method, body, key: step.key, label: step.label,
|
|
117
|
-
onPending: (id) => { if (mine(o)) { step.pendingId = id; step.status = "waiting"; publish(); } } });
|
|
117
|
+
onPending: (id, view) => { if (mine(o)) { step.pendingId = id; step.status = selfConfirmed(view) ? "running" : "waiting"; publish(); } } });
|
|
118
118
|
}
|
|
119
119
|
if (step.connectionSecret) return rotateConnectionSecret(step, o);
|
|
120
120
|
return { ok: false, body: { error: "unknown change" } };
|
|
@@ -129,7 +129,7 @@ async function rotateConnectionSecret(step, o) {
|
|
|
129
129
|
if (!verified.ok) return verified;
|
|
130
130
|
const applied = await confirmedWrite(`${base}/apply`, { method: "POST", key: step.key, label: step.label,
|
|
131
131
|
body: { verification_id: verified.body && verified.body.verification_id, idempotency_key: step.key },
|
|
132
|
-
onPending: (pid) => { if (mine(o)) { step.pendingId = pid; step.status = "waiting"; publish(); } } });
|
|
132
|
+
onPending: (pid, view) => { if (mine(o)) { step.pendingId = pid; step.status = selfConfirmed(view) ? "running" : "waiting"; publish(); } } });
|
|
133
133
|
if (!applied.ok && applied.status !== 409) return applied;
|
|
134
134
|
let result = applied.body || {};
|
|
135
135
|
for (let n = 0; result.job_id && result.result === "applying" && n < 60; n++) {
|