@songsid/agend 2.3.0-beta.9 → 2.3.0
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 +16 -8
- package/README.zh-TW.md +16 -8
- package/dist/adapter-world.js.map +1 -1
- package/dist/agend-home.d.ts +5 -0
- package/dist/agend-home.js +7 -0
- package/dist/agend-home.js.map +1 -0
- package/dist/agent-cli-instructions.md +7 -1
- package/dist/agent-cli.js +41 -0
- package/dist/agent-cli.js.map +1 -1
- package/dist/agent-endpoint.d.ts +9 -0
- package/dist/agent-endpoint.js +23 -0
- package/dist/agent-endpoint.js.map +1 -1
- package/dist/agent-file-read.js +1 -1
- package/dist/agent-file-read.js.map +1 -1
- package/dist/agent-whats-new.d.ts +16 -0
- package/dist/agent-whats-new.js +53 -12
- package/dist/agent-whats-new.js.map +1 -1
- package/dist/apply-job.js.map +1 -1
- package/dist/atomic-write.js.map +1 -1
- package/dist/auth-api.d.ts +12 -0
- package/dist/auth-api.js +77 -22
- package/dist/auth-api.js.map +1 -1
- package/dist/authority/authority-state.d.ts +4 -0
- package/dist/authority/authority-state.js +17 -8
- package/dist/authority/authority-state.js.map +1 -1
- package/dist/authority/session-authorizations.js.map +1 -1
- package/dist/authority/web-authority.d.ts +16 -8
- package/dist/authority/web-authority.js +19 -5
- package/dist/authority/web-authority.js.map +1 -1
- package/dist/backend/antigravity.d.ts +2 -2
- package/dist/backend/antigravity.js +2 -1
- package/dist/backend/antigravity.js.map +1 -1
- package/dist/backend/claude-code.js +13 -4
- 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-package.d.ts +30 -0
- package/dist/backend/codex-package.js +133 -0
- package/dist/backend/codex-package.js.map +1 -0
- package/dist/backend/codex-session-lookup.js.map +1 -1
- package/dist/backend/codex.d.ts +2 -0
- package/dist/backend/codex.js +25 -5
- 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/effort-metadata.js.map +1 -1
- package/dist/backend/grok.js +58 -3
- package/dist/backend/grok.js.map +1 -1
- package/dist/backend/kiro-engine-ledger.js.map +1 -1
- package/dist/backend/kiro-kas-store.js.map +1 -1
- package/dist/backend/kiro-v2-store.js.map +1 -1
- package/dist/backend/kiro-v3-identity.js.map +1 -1
- package/dist/backend/kiro.d.ts +9 -1
- package/dist/backend/kiro.js +102 -1
- package/dist/backend/kiro.js.map +1 -1
- package/dist/backend/launch-effort.js.map +1 -1
- package/dist/backend/mock.js.map +1 -1
- package/dist/backend/muse.js.map +1 -1
- package/dist/backend/opencode-permission.js +1 -1
- package/dist/backend/opencode-permission.js.map +1 -1
- package/dist/backend/opencode.js +2 -1
- package/dist/backend/opencode.js.map +1 -1
- package/dist/backend/session-signals.js.map +1 -1
- package/dist/backend-availability.js.map +1 -1
- package/dist/backend-command.d.ts +2 -0
- package/dist/backend-command.js +3 -1
- package/dist/backend-command.js.map +1 -1
- package/dist/backend-outage.js.map +1 -1
- package/dist/binary-probe.js +1 -1
- package/dist/binary-probe.js.map +1 -1
- package/dist/bug-report-cli.d.ts +30 -0
- package/dist/bug-report-cli.js +139 -0
- package/dist/bug-report-cli.js.map +1 -0
- package/dist/bug-report.d.ts +47 -0
- package/dist/bug-report.js +137 -0
- package/dist/bug-report.js.map +1 -0
- package/dist/bundled-skills.d.ts +18 -0
- package/dist/bundled-skills.js +38 -0
- package/dist/bundled-skills.js.map +1 -0
- package/dist/cache-service.js +1 -1
- 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 +4 -0
- package/dist/channel/adapters/discord.js +57 -12
- package/dist/channel/adapters/discord.js.map +1 -1
- package/dist/channel/adapters/telegram.d.ts +6 -0
- package/dist/channel/adapters/telegram.js +54 -14
- package/dist/channel/adapters/telegram.js.map +1 -1
- package/dist/channel/mcp-server.js +21 -4
- package/dist/channel/mcp-server.js.map +1 -1
- package/dist/channel/mcp-tools.d.ts +3 -1
- package/dist/channel/mcp-tools.js +41 -4
- package/dist/channel/mcp-tools.js.map +1 -1
- package/dist/channel/message-queue.js.map +1 -1
- package/dist/channel/sticker-download.js.map +1 -1
- package/dist/channel/tool-router.js.map +1 -1
- package/dist/child-process.d.ts +21 -0
- package/dist/child-process.js +54 -0
- package/dist/child-process.js.map +1 -0
- package/dist/classic-channel-manager.js +3 -2
- package/dist/classic-channel-manager.js.map +1 -1
- package/dist/cli-entry.js.map +1 -1
- package/dist/cli-flags.js.map +1 -1
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +443 -311
- package/dist/cli.js.map +1 -1
- package/dist/command-table.d.ts +1 -1
- package/dist/command-table.js +1 -1
- package/dist/command-table.js.map +1 -1
- package/dist/compact-watch.js.map +1 -1
- package/dist/completion-install.js.map +1 -1
- package/dist/completion.js.map +1 -1
- package/dist/config-validator.d.ts +6 -0
- package/dist/config-validator.js +81 -8
- package/dist/config-validator.js.map +1 -1
- package/dist/config.d.ts +15 -3
- package/dist/config.js +41 -10
- package/dist/config.js.map +1 -1
- package/dist/connection-secrets.js.map +1 -1
- package/dist/context-guardian.js.map +1 -1
- package/dist/coredump-filter.js.map +1 -1
- package/dist/cpu-profile.js.map +1 -1
- package/dist/cross-instance-envelope.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 +39 -1
- package/dist/daemon.js +234 -15
- package/dist/daemon.js.map +1 -1
- package/dist/daily-summary.js.map +1 -1
- package/dist/darwin-memory.js +1 -1
- package/dist/darwin-memory.js.map +1 -1
- package/dist/dashboard-live.d.ts +8 -0
- package/dist/dashboard-live.js +35 -0
- package/dist/dashboard-live.js.map +1 -0
- package/dist/dashboard-permissions.d.ts +69 -0
- package/dist/dashboard-permissions.js +138 -0
- package/dist/dashboard-permissions.js.map +1 -0
- package/dist/decision-list.js.map +1 -1
- package/dist/delivery-consumed-watch.js.map +1 -1
- package/dist/delivery-outbox.js +5 -0
- package/dist/delivery-outbox.js.map +1 -1
- package/dist/delivery-outbox.test.js.map +1 -1
- package/dist/delivery-reconciliation.test.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/doctor.d.ts +8 -0
- package/dist/doctor.js +52 -0
- package/dist/doctor.js.map +1 -1
- package/dist/e2e-isolation.js.map +1 -1
- package/dist/event-log.js.map +1 -1
- package/dist/export-import.js +7 -5
- package/dist/export-import.js.map +1 -1
- package/dist/fleet-control-audit.js.map +1 -1
- package/dist/fleet-label.js.map +1 -1
- package/dist/fleet-level-config.d.ts +1 -1
- package/dist/fleet-level-config.js +2 -0
- package/dist/fleet-level-config.js.map +1 -1
- package/dist/fleet-lock.js +3 -2
- package/dist/fleet-lock.js.map +1 -1
- package/dist/fleet-manager.d.ts +249 -13
- package/dist/fleet-manager.js +1561 -292
- package/dist/fleet-manager.js.map +1 -1
- package/dist/fleet-process-handlers.d.ts +18 -0
- package/dist/fleet-process-handlers.js +70 -0
- package/dist/fleet-process-handlers.js.map +1 -0
- package/dist/fleet-startup.d.ts +14 -0
- package/dist/fleet-startup.js +30 -0
- package/dist/fleet-startup.js.map +1 -0
- package/dist/fleet-traffic.js.map +1 -1
- package/dist/forged-envelope-scan.d.ts +0 -20
- package/dist/forged-envelope-scan.js +3 -2
- package/dist/forged-envelope-scan.js.map +1 -1
- package/dist/full-restart.d.ts +5 -5
- package/dist/full-restart.js +5 -5
- package/dist/full-restart.js.map +1 -1
- package/dist/general-knowledge/skills/bug-report/SKILL.md +31 -0
- package/dist/general-knowledge/skills/credential-profiles/SKILL.md +52 -11
- package/dist/general-knowledge/skills/cross-instance-messaging/SKILL.md +5 -1
- package/dist/general-knowledge/skills/fleet-config/SKILL.md +3 -1
- package/dist/general-knowledge/skills/fleet-restart/SKILL.md +8 -9
- package/dist/general-knowledge/skills/multi-channel/SKILL.md +1 -1
- 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.canvas-worker-off.md +321 -0
- package/dist/general-knowledge/skills/web-appearance/SKILL.md +24 -4
- package/dist/general-knowledge/skills/web-dashboard/SKILL.canvas-worker-off.md +553 -0
- package/dist/general-knowledge/skills/web-dashboard/SKILL.md +154 -31
- package/dist/general-knowledge/skills/worker-collaboration/SKILL.md +1 -1
- package/dist/general-knowledge/steering/core-rules.md +1 -1
- package/dist/host-memory.js.map +1 -1
- package/dist/idle-gate-log.d.ts +19 -0
- package/dist/idle-gate-log.js +15 -0
- package/dist/idle-gate-log.js.map +1 -0
- package/dist/instance-config-impact.js +25 -4
- package/dist/instance-config-impact.js.map +1 -1
- package/dist/instance-lifecycle.d.ts +10 -1
- package/dist/instance-lifecycle.js +86 -25
- package/dist/instance-lifecycle.js.map +1 -1
- package/dist/instructions.d.ts +12 -6
- package/dist/instructions.js +19 -10
- package/dist/instructions.js.map +1 -1
- package/dist/kiro-compact-pane.js.map +1 -1
- package/dist/kiro-db-reader.js.map +1 -1
- package/dist/kiro-engine-status.js.map +1 -1
- package/dist/kiro-log-gate.js.map +1 -1
- package/dist/kiro-transcript-lane.js.map +1 -1
- package/dist/locale.js +428 -20
- package/dist/locale.js.map +1 -1
- package/dist/log-service.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/logger.js +3 -2
- package/dist/logger.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/login-flows.js +1 -1
- package/dist/login-manager.js.map +1 -1
- package/dist/login-shell.js +3 -1
- package/dist/login-shell.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 +83 -4
- package/dist/ls-rows.js.map +1 -1
- package/dist/memory-pressure.js.map +1 -1
- package/dist/muse-usage-relay.js.map +1 -1
- package/dist/needs-you-hub.d.ts +98 -1
- package/dist/needs-you-hub.js +313 -44
- package/dist/needs-you-hub.js.map +1 -1
- package/dist/needs-you-live.d.ts +5 -2
- package/dist/needs-you-live.js +6 -3
- package/dist/needs-you-live.js.map +1 -1
- package/dist/needs-you.d.ts +68 -3
- package/dist/needs-you.js +38 -4
- package/dist/needs-you.js.map +1 -1
- package/dist/network-family.js.map +1 -1
- package/dist/operator-control-paths.js +3 -4
- package/dist/operator-control-paths.js.map +1 -1
- package/dist/outbound-handlers.d.ts +3 -0
- package/dist/outbound-handlers.js +76 -19
- package/dist/outbound-handlers.js.map +1 -1
- package/dist/outbound-schemas.d.ts +106 -60
- package/dist/outbound-schemas.js +33 -10
- package/dist/outbound-schemas.js.map +1 -1
- package/dist/outbox-open-error.js.map +1 -1
- package/dist/pane-context-cache.js.map +1 -1
- package/dist/paths.js.map +1 -1
- package/dist/private-dir.js.map +1 -1
- package/dist/probe-worker-pool.js.map +1 -1
- package/dist/profile-control.js.map +1 -1
- package/dist/prompt-file-ref.js.map +1 -1
- package/dist/provider-probe.js.map +1 -1
- package/dist/provider-secret-registry.d.ts +5 -3
- package/dist/provider-secret-registry.js +8 -2
- package/dist/provider-secret-registry.js.map +1 -1
- package/dist/public-link-auto-open.d.ts +32 -0
- package/dist/public-link-auto-open.js +86 -0
- package/dist/public-link-auto-open.js.map +1 -0
- package/dist/public-link-owner.d.ts +10 -0
- package/dist/public-link-owner.js +36 -0
- package/dist/public-link-owner.js.map +1 -0
- package/dist/public-link-progress.d.ts +12 -1
- package/dist/public-link-progress.js +5 -10
- package/dist/public-link-progress.js.map +1 -1
- package/dist/public-preview.js.map +1 -1
- package/dist/public-tunnel-config.d.ts +11 -0
- package/dist/public-tunnel-config.js +68 -0
- package/dist/public-tunnel-config.js.map +1 -0
- package/dist/public-web-gateway.d.ts +2 -0
- package/dist/public-web-gateway.js +19 -6
- package/dist/public-web-gateway.js.map +1 -1
- package/dist/public-web-link.d.ts +48 -3
- package/dist/public-web-link.js +177 -51
- package/dist/public-web-link.js.map +1 -1
- package/dist/quickstart-api.d.ts +9 -0
- package/dist/quickstart-api.js +82 -0
- package/dist/quickstart-api.js.map +1 -1
- package/dist/quickstart.d.ts +5 -0
- package/dist/quickstart.js +40 -25
- package/dist/quickstart.js.map +1 -1
- package/dist/reply-buttons-controller.js.map +1 -1
- package/dist/reply-dedup.js.map +1 -1
- package/dist/resource-report.js +1 -1
- package/dist/resource-report.js.map +1 -1
- package/dist/restart-guard.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.js.map +1 -1
- package/dist/runtime-cpu-profile.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/scheduler.js.map +1 -1
- package/dist/scheduler/task-board-text.d.ts +26 -0
- package/dist/scheduler/task-board-text.js +140 -0
- package/dist/scheduler/task-board-text.js.map +1 -0
- package/dist/scheduler/task-board.d.ts +730 -0
- package/dist/scheduler/task-board.js +2179 -0
- package/dist/scheduler/task-board.js.map +1 -0
- package/dist/scheduler/types.d.ts +6 -1
- package/dist/sd-notify.js +2 -1
- package/dist/sd-notify.js.map +1 -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/secret-file.js.map +1 -1
- package/dist/secret-store.d.ts +2 -0
- package/dist/secret-store.js +21 -0
- package/dist/secret-store.js.map +1 -1
- package/dist/service-installer.d.ts +75 -0
- package/dist/service-installer.js +206 -0
- package/dist/service-installer.js.map +1 -1
- package/dist/service-owner.d.ts +44 -0
- package/dist/service-owner.js +48 -0
- package/dist/service-owner.js.map +1 -0
- package/dist/service-plan.d.ts +39 -2
- package/dist/service-plan.js +188 -6
- package/dist/service-plan.js.map +1 -1
- package/dist/session-oom.d.ts +21 -0
- package/dist/session-oom.js +139 -0
- package/dist/session-oom.js.map +1 -0
- package/dist/settings-api.d.ts +13 -0
- package/dist/settings-api.js +73 -12
- package/dist/settings-api.js.map +1 -1
- package/dist/settings-baseline.js.map +1 -1
- package/dist/settings-change.d.ts +5 -0
- package/dist/settings-change.js +167 -6
- package/dist/settings-change.js.map +1 -1
- package/dist/settings-confirmation.d.ts +9 -1
- package/dist/settings-confirmation.js +18 -8
- package/dist/settings-confirmation.js.map +1 -1
- package/dist/settings-control.js +2 -1
- package/dist/settings-control.js.map +1 -1
- package/dist/settings-effect.js +29 -4
- package/dist/settings-effect.js.map +1 -1
- package/dist/settings-http-confirmation.d.ts +13 -0
- package/dist/settings-http-confirmation.js +15 -3
- package/dist/settings-http-confirmation.js.map +1 -1
- package/dist/settings-instance-patch.js +13 -1
- package/dist/settings-instance-patch.js.map +1 -1
- package/dist/settings-request-capability.d.ts +4 -1
- package/dist/settings-request-capability.js +11 -3
- package/dist/settings-request-capability.js.map +1 -1
- package/dist/settings-transaction.js.map +1 -1
- package/dist/setup-auth.d.ts +1 -0
- package/dist/setup-auth.js +2 -1
- package/dist/setup-auth.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/setup-host.d.ts +6 -1
- package/dist/setup-host.js +15 -4
- package/dist/setup-host.js.map +1 -1
- package/dist/setup-wizard.js +6 -3
- package/dist/setup-wizard.js.map +1 -1
- package/dist/spawn-gate.d.ts +5 -0
- package/dist/spawn-gate.js +28 -8
- package/dist/spawn-gate.js.map +1 -1
- package/dist/start-attempt.d.ts +1 -0
- package/dist/start-attempt.js +18 -0
- package/dist/start-attempt.js.map +1 -0
- package/dist/start-failure-notice.d.ts +4 -0
- package/dist/start-failure-notice.js +20 -0
- package/dist/start-failure-notice.js.map +1 -0
- package/dist/start-failure.d.ts +147 -0
- package/dist/start-failure.js +281 -0
- package/dist/start-failure.js.map +1 -0
- package/dist/startup-memory-deadline.d.ts +23 -0
- package/dist/startup-memory-deadline.js +55 -0
- package/dist/startup-memory-deadline.js.map +1 -0
- package/dist/statusline-watcher.js.map +1 -1
- package/dist/storm-window.js.map +1 -1
- package/dist/systemd-peer-bus.js.map +1 -1
- package/dist/target-queue-worker.js.map +1 -1
- package/dist/task-board-summary.d.ts +101 -0
- package/dist/task-board-summary.js +177 -0
- package/dist/task-board-summary.js.map +1 -0
- package/dist/task-board-tool.d.ts +25 -0
- package/dist/task-board-tool.js +155 -0
- package/dist/task-board-tool.js.map +1 -0
- package/dist/task-board-waker.d.ts +51 -0
- package/dist/task-board-waker.js +141 -0
- package/dist/task-board-waker.js.map +1 -0
- package/dist/tips.js.map +1 -1
- package/dist/tmux-control.js +1 -1
- package/dist/tmux-control.js.map +1 -1
- package/dist/tmux-exec-helper.d.ts +104 -0
- package/dist/tmux-exec-helper.js +412 -0
- package/dist/tmux-exec-helper.js.map +1 -0
- package/dist/tmux-manager.d.ts +1 -0
- package/dist/tmux-manager.js +29 -13
- package/dist/tmux-manager.js.map +1 -1
- package/dist/tmux-read.js +2 -2
- package/dist/tmux-read.js.map +1 -1
- package/dist/tool-permissions.js.map +1 -1
- package/dist/tool-progress.js.map +1 -1
- package/dist/topic-archiver.js.map +1 -1
- package/dist/topic-commands.d.ts +13 -0
- package/dist/topic-commands.js +56 -33
- package/dist/topic-commands.js.map +1 -1
- package/dist/topic-link.js.map +1 -1
- package/dist/transcript-delta-reader.js.map +1 -1
- package/dist/transcript-jsonl.js.map +1 -1
- package/dist/transcript-monitor.js +5 -0
- package/dist/transcript-monitor.js.map +1 -1
- package/dist/transcript-sources.d.ts +2 -0
- package/dist/transcript-sources.js +4 -0
- package/dist/transcript-sources.js.map +1 -1
- package/dist/tunnel/cloudflared-install.js +3 -1
- package/dist/tunnel/cloudflared-install.js.map +1 -1
- package/dist/tunnel/cloudflared.d.ts +7 -16
- package/dist/tunnel/cloudflared.js +100 -13
- package/dist/tunnel/cloudflared.js.map +1 -1
- package/dist/tunnel/lease.js +3 -2
- package/dist/tunnel/lease.js.map +1 -1
- package/dist/tunnel/manager.js.map +1 -1
- package/dist/tunnel/named-diagnostics.d.ts +13 -0
- package/dist/tunnel/named-diagnostics.js +52 -0
- package/dist/tunnel/named-diagnostics.js.map +1 -0
- package/dist/tunnel/public-fetch.js.map +1 -1
- package/dist/tunnel/purpose-lane.js.map +1 -1
- package/dist/tunnel/types.d.ts +4 -1
- package/dist/tunnel/types.js.map +1 -1
- package/dist/types.d.ts +55 -2
- package/dist/ui/app-effects.js +23 -5
- package/dist/ui/app.html +2 -0
- package/dist/ui/backend-signin.js +297 -0
- package/dist/ui/chat-effects.js +10 -3
- package/dist/ui/chat-msg-actions.js +176 -0
- package/dist/ui/chat-render.js +62 -18
- package/dist/ui/chat-store.js +218 -48
- package/dist/ui/chat-strings.js +40 -8
- package/dist/ui/chat-thread.js +119 -10
- package/dist/ui/effect-trial-offer.js +2 -31
- package/dist/ui/effects-waker.js +2 -0
- package/dist/ui/fleet-canvas-layout.js +13 -4
- package/dist/ui/fleet-canvas.js +69 -28
- package/dist/ui/fleet-office-editor.js +149 -0
- package/dist/ui/fleet-office-model.js +358 -0
- package/dist/ui/fleet-office-pack.js +199 -0
- package/dist/ui/fleet-office-skin.js +474 -0
- package/dist/ui/fleet-office-store.js +49 -0
- package/dist/ui/fleet-office-zones.js +177 -0
- package/dist/ui/fleet-office.js +434 -0
- package/dist/ui/fleet-org.js +18 -5
- package/dist/ui/fleet-route-scene.js +272 -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 +482 -0
- package/dist/ui/fleet-task-work.js +107 -0
- package/dist/ui/fleet-tasks.js +569 -0
- package/dist/ui/fleet-traffic.js +81 -18
- package/dist/ui/fleet-usage.js +36 -2
- package/dist/ui/guest-refused.html +41 -0
- package/dist/ui/icons/icon-192.png +0 -0
- package/dist/ui/icons/icon-512.png +0 -0
- package/dist/ui/image-lightbox.js +45 -1
- package/dist/ui/image-zoom.js +242 -0
- package/dist/ui/panel-chat.js +200 -56
- package/dist/ui/panel-details.js +2 -0
- package/dist/ui/panel-fleet.js +32 -71
- package/dist/ui/panel-needs.js +23 -6
- package/dist/ui/panel-settings.js +158 -32
- package/dist/ui/preview-panel.js +2 -1
- package/dist/ui/search-dialog.js +350 -0
- package/dist/ui/settings-appearance.js +170 -10
- package/dist/ui/settings-apply.js +3 -3
- package/dist/ui/settings-confirm.js +7 -2
- package/dist/ui/settings-dialogs.js +46 -21
- package/dist/ui/settings-discord.js +6 -2
- package/dist/ui/settings-guests.js +2 -1
- package/dist/ui/settings-learn.js +1 -0
- package/dist/ui/settings-model.js +51 -18
- package/dist/ui/settings-mods.js +8 -3
- package/dist/ui/settings-owner-effects.js +6 -4
- package/dist/ui/settings-strings.js +180 -6
- package/dist/ui/settings-token.js +3 -1
- package/dist/ui/settings-web-authority.js +13 -5
- package/dist/ui/settings-wizard.js +13 -7
- package/dist/ui/shared/agend-auth.js +2 -0
- package/dist/ui/shared/app-i18n.js +12 -5
- package/dist/ui/shared/app-needs.js +2 -2
- package/dist/ui/shared/app-prefs.js +15 -0
- package/dist/ui/shared/app-route.js +18 -0
- package/dist/ui/shared/app-shell.js +172 -28
- package/dist/ui/shared/app-store.js +3 -2
- package/dist/ui/shared/app-stream.js +5 -1
- package/dist/ui/shared/app-themes.js +116 -1
- package/dist/ui/shared/app.css +662 -48
- package/dist/ui/shared/app.js +9 -1
- package/dist/ui/shared/card-strings.js +4 -0
- package/dist/ui/shared/effect-app-runtime.js +7 -5
- package/dist/ui/shared/effect-chat-runtime.js +2 -7
- package/dist/ui/shared/effect-cursor.js +3 -2
- package/dist/ui/shared/effect-display-metadata.js +39 -0
- package/dist/ui/shared/effect-owner-source.js +8 -1
- package/dist/ui/shared/effect-registry.js +14 -4
- package/dist/ui/shared/effect-runtime-loader.js +3 -1
- package/dist/ui/shared/effect-stock-trust.js +5 -5
- package/dist/ui/shared/feature-flags.js +127 -0
- package/dist/ui/shared/guest-refused.js +35 -0
- package/dist/ui/shared/header-tools.js +2 -2
- package/dist/ui/shared/identity-card.js +26 -8
- package/dist/ui/shared/identity-color.js +17 -10
- package/dist/ui/shared/instance-nav.js +183 -28
- package/dist/ui/shared/owner-css.js +30 -1
- package/dist/ui/shared/panel-view.js +14 -29
- package/dist/ui/shared/text-undo.js +210 -0
- package/dist/ui/shared/tokens.css +4 -0
- package/dist/ui/shared/ui-dialog.js +5 -4
- package/dist/ui/shared/ui-icons.js +4 -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 +206 -16
- package/dist/ui/shared/view-strings.js +68 -4
- package/dist/ui/shared/web-authorize.js +50 -1
- package/dist/ui/signin.html +2 -0
- package/dist/ui/web-terminal/terminal.html +10 -10
- package/dist/ui/web-terminal/terminal.js +58 -12
- package/dist/update-check.js.map +1 -1
- package/dist/update-dispatch.js +1 -1
- package/dist/update-dispatch.js.map +1 -1
- package/dist/update-install.js.map +1 -1
- package/dist/update-launch.d.ts +10 -1
- package/dist/update-launch.js +39 -14
- package/dist/update-launch.js.map +1 -1
- package/dist/update-marker.js.map +1 -1
- package/dist/update-preflight.js +1 -1
- package/dist/update-preflight.js.map +1 -1
- package/dist/update-progress.d.ts +3 -1
- package/dist/update-progress.js +3 -1
- package/dist/update-progress.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 +89 -2
- package/dist/usage/providers.js +247 -41
- package/dist/usage/providers.js.map +1 -1
- package/dist/usage/statusline-usage.js.map +1 -1
- package/dist/usage/usage-api.d.ts +5 -1
- package/dist/usage/usage-api.js +185 -58
- package/dist/usage/usage-api.js.map +1 -1
- package/dist/usage-report.js.map +1 -1
- package/dist/usage-rollup.js +1 -1
- package/dist/usage-rollup.js.map +1 -1
- 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 +7 -0
- package/dist/view-api.js +16 -4
- package/dist/view-api.js.map +1 -1
- package/dist/wake-coordinator.js.map +1 -1
- package/dist/web-ambient.js.map +1 -1
- package/dist/web-api.d.ts +57 -10
- package/dist/web-api.js +1103 -70
- package/dist/web-api.js.map +1 -1
- package/dist/web-app-icon-worker.d.ts +1 -0
- package/dist/web-app-icon-worker.js +16 -0
- package/dist/web-app-icon-worker.js.map +1 -0
- package/dist/web-app-icon.d.ts +109 -0
- package/dist/web-app-icon.js +257 -0
- package/dist/web-app-icon.js.map +1 -0
- package/dist/web-appearance-policy.js.map +1 -1
- package/dist/web-appearance-tools.d.ts +1 -0
- package/dist/web-appearance-tools.js +7 -3
- package/dist/web-appearance-tools.js.map +1 -1
- package/dist/web-appearance-view.js.map +1 -1
- package/dist/web-appearance.d.ts +4 -10
- package/dist/web-appearance.js +7 -4
- package/dist/web-appearance.js.map +1 -1
- package/dist/web-auth.d.ts +8 -4
- package/dist/web-auth.js +33 -21
- 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.js.map +1 -1
- package/dist/web-channel-echo.js.map +1 -1
- package/dist/web-chat-history.d.ts +118 -8
- package/dist/web-chat-history.js +220 -25
- package/dist/web-chat-history.js.map +1 -1
- package/dist/web-commands.d.ts +5 -1
- package/dist/web-commands.js +1 -1
- package/dist/web-commands.js.map +1 -1
- package/dist/web-cookie-names.d.ts +5 -0
- package/dist/web-cookie-names.js +12 -0
- package/dist/web-cookie-names.js.map +1 -0
- package/dist/web-effect-bundle.d.ts +2 -2
- package/dist/web-effect-library.js.map +1 -1
- package/dist/web-effect-playback.js.map +1 -1
- package/dist/web-effect-profiles.d.ts +16 -16
- package/dist/web-effect-profiles.js +3 -2
- package/dist/web-effect-profiles.js.map +1 -1
- package/dist/web-effect-service.js.map +1 -1
- package/dist/web-effect-tools.js.map +1 -1
- package/dist/web-effect-trials.js.map +1 -1
- package/dist/web-effect-upload.js.map +1 -1
- package/dist/web-effect.d.ts +6 -6
- package/dist/web-effect.js.map +1 -1
- package/dist/web-experimental-http.d.ts +2 -0
- package/dist/web-experimental-http.js +12 -0
- package/dist/web-experimental-http.js.map +1 -0
- package/dist/web-experimental.d.ts +11 -0
- package/dist/web-experimental.js +49 -0
- package/dist/web-experimental.js.map +1 -0
- package/dist/web-guest-access.d.ts +4 -2
- package/dist/web-guest-access.js +22 -10
- package/dist/web-guest-access.js.map +1 -1
- package/dist/web-guest-rooms.js.map +1 -1
- 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-icons.d.ts +33 -1
- package/dist/web-icons.js +83 -11
- package/dist/web-icons.js.map +1 -1
- package/dist/web-identity-color.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-images.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.js.map +1 -1
- package/dist/web-mentions.js.map +1 -1
- package/dist/web-mod-agent.js.map +1 -1
- package/dist/web-mod-store.d.ts +2 -0
- package/dist/web-mod-store.js +10 -0
- package/dist/web-mod-store.js.map +1 -1
- package/dist/web-office-store.d.ts +104 -0
- package/dist/web-office-store.js +236 -0
- package/dist/web-office-store.js.map +1 -0
- 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-owner-css.js.map +1 -1
- package/dist/web-png.d.ts +33 -0
- package/dist/web-png.js +329 -0
- package/dist/web-png.js.map +1 -0
- package/dist/web-prefs.d.ts +6 -0
- package/dist/web-prefs.js +29 -3
- package/dist/web-prefs.js.map +1 -1
- package/dist/web-preview.d.ts +2 -0
- package/dist/web-preview.js +2 -0
- package/dist/web-preview.js.map +1 -1
- package/dist/web-profile.js.map +1 -1
- package/dist/web-reactions.d.ts +35 -0
- package/dist/web-reactions.js +47 -0
- package/dist/web-reactions.js.map +1 -0
- package/dist/web-request-context.d.ts +5 -0
- package/dist/web-request-context.js.map +1 -1
- package/dist/web-returning-device.d.ts +4 -2
- package/dist/web-returning-device.js +13 -6
- package/dist/web-returning-device.js.map +1 -1
- package/dist/web-schedules.js.map +1 -1
- package/dist/web-send-dedupe.d.ts +36 -0
- package/dist/web-send-dedupe.js +69 -0
- package/dist/web-send-dedupe.js.map +1 -0
- package/dist/web-session-authorization.js.map +1 -1
- package/dist/web-session.js +1 -1
- package/dist/web-session.js.map +1 -1
- package/dist/web-settings-patch.d.ts +3 -1
- package/dist/web-settings-patch.js +11 -5
- package/dist/web-settings-patch.js.map +1 -1
- package/dist/web-settings-tools.d.ts +6 -0
- package/dist/web-settings-tools.js +52 -17
- package/dist/web-settings-tools.js.map +1 -1
- package/dist/web-terminal-http.js +15 -2
- package/dist/web-terminal-http.js.map +1 -1
- package/dist/web-terminal.js +1 -1
- package/dist/web-terminal.js.map +1 -1
- package/dist/web-theme-store.d.ts +2 -2
- package/dist/web-theme-store.js +6 -3
- package/dist/web-theme-store.js.map +1 -1
- package/dist/web-theme.d.ts +29 -0
- package/dist/web-theme.js +89 -1
- package/dist/web-theme.js.map +1 -1
- package/dist/web-upload.d.ts +18 -1
- package/dist/web-upload.js +45 -7
- package/dist/web-upload.js.map +1 -1
- package/dist/web-widget.js.map +1 -1
- package/dist/webhook-emitter.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 +3 -0
- package/dist/ws-server.js.map +1 -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 +828 -0
- package/launcher/install-admission.cjs +4 -39
- package/launcher/postinstall.cjs +14 -12
- package/launcher/private-home.cjs +43 -0
- package/package.json +16 -13
- 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,350 @@
|
|
|
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 { recordEdit } from "/assets/text-undo.js";
|
|
9
|
+
import { appStore, useStore } from "/assets/app-store.js";
|
|
10
|
+
import { navigate } from "/assets/app-nav.js";
|
|
11
|
+
import { chatPath, fleetPath } from "/assets/app-route.js";
|
|
12
|
+
import { namesOf, namesStore } from "/assets/instance-nav.js";
|
|
13
|
+
import { Icon } from "/assets/ui-icons.js";
|
|
14
|
+
|
|
15
|
+
register("search", {
|
|
16
|
+
en: {
|
|
17
|
+
label: "Search", placeholder: "Search chats, messages and tasks…", close: "Close", filters: "Filters", filtersOn: "Filters ({0})",
|
|
18
|
+
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.",
|
|
19
|
+
recent: "Recent searches", clearRecent: "Clear",
|
|
20
|
+
agent: "Agent", anyAgent: "All agents", team: "Team", anyTeam: "All teams", time: "Time", anyTime: "Any time",
|
|
21
|
+
day: "24 hours", week: "7 days", month: "30 days", sources: "In", mine: "Only mine",
|
|
22
|
+
src_chat: "Chats", src_xmsg: "Agent messages", src_task: "Tasks",
|
|
23
|
+
badge_chat: "Chat", badge_xmsg: "Message", badge_task: "Task",
|
|
24
|
+
searching: "Searching…", none: "Nothing found", noneFiltered: "Nothing found with these filters",
|
|
25
|
+
building: "Still indexing — some older results may be missing.",
|
|
26
|
+
limited: "Words of 1–2 characters alone search the newest 5,000 entries only — add a longer word to search everything.",
|
|
27
|
+
unavailable: "Search is off or not available (web.search in fleet.yaml).", busy: "Search is busy — try again in a moment.",
|
|
28
|
+
signedOut: "Signed out — sign in again to search.", failed: "Search failed — try again.",
|
|
29
|
+
keys: "↑↓ move · Enter open · Esc close", results: "Results", back: "Results",
|
|
30
|
+
gone: "This message is no longer in the chat's history — this is the copy kept by search.",
|
|
31
|
+
openChat: "Open chat", openChatOf: "Open {0}'s chat", openTasks: "Open the Task Board", loadingCtx: "Loading…",
|
|
32
|
+
notFound: "No longer found — it may have been removed.", from: "{0} → {1}", groupCount: "{0} results",
|
|
33
|
+
},
|
|
34
|
+
"zh-TW": {
|
|
35
|
+
label: "搜尋", placeholder: "搜尋聊天、訊息與任務…", close: "關閉", filters: "篩選", filtersOn: "篩選({0})",
|
|
36
|
+
hint: "找回某件事當初交給誰:輸入聊天、agent 之間訊息或任務裡的一兩個詞。1–2 個字的短詞也可以。",
|
|
37
|
+
recent: "最近的搜尋", clearRecent: "清除",
|
|
38
|
+
agent: "Agent", anyAgent: "所有 agent", team: "團隊", anyTeam: "所有團隊", time: "時間", anyTime: "不限時間",
|
|
39
|
+
day: "24 小時", week: "7 天", month: "30 天", sources: "範圍", mine: "只看我的",
|
|
40
|
+
src_chat: "聊天", src_xmsg: "Agent 訊息", src_task: "任務",
|
|
41
|
+
badge_chat: "聊天", badge_xmsg: "訊息", badge_task: "任務",
|
|
42
|
+
searching: "搜尋中…", none: "找不到結果", noneFiltered: "這些篩選條件下找不到結果",
|
|
43
|
+
building: "索引仍在建立中,較舊的結果可能還沒出現。",
|
|
44
|
+
limited: "只有 1–2 個字的短詞時,只會搜尋最新的 5,000 筆;加上較長的詞即可搜尋全部。",
|
|
45
|
+
unavailable: "搜尋已關閉或無法使用(fleet.yaml 的 web.search)。", busy: "搜尋忙碌中,請稍後再試。",
|
|
46
|
+
signedOut: "已登出,請重新登入後再搜尋。", failed: "搜尋失敗,請再試一次。",
|
|
47
|
+
keys: "↑↓ 移動 · Enter 開啟 · Esc 關閉", results: "結果", back: "結果",
|
|
48
|
+
gone: "這則訊息已不在聊天紀錄中,以下是搜尋保留的副本。",
|
|
49
|
+
openChat: "開啟聊天", openChatOf: "開啟 {0} 的聊天", openTasks: "開啟 Task Board", loadingCtx: "載入中…",
|
|
50
|
+
notFound: "已找不到,可能已被移除。", from: "{0} → {1}", groupCount: "{0} 筆結果",
|
|
51
|
+
},
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
export const DEBOUNCE_MS = 200;
|
|
55
|
+
export const RECENT_KEY = "agend_search_recent";
|
|
56
|
+
export const RECENT_MAX = 8;
|
|
57
|
+
const SOURCES = ["chat", "xmsg", "task"];
|
|
58
|
+
const SINCE = { any: 0, day: 24 * 3600_000, week: 7 * 24 * 3600_000, month: 30 * 24 * 3600_000 };
|
|
59
|
+
|
|
60
|
+
export function readRecent() {
|
|
61
|
+
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 []; }
|
|
62
|
+
}
|
|
63
|
+
export function rememberSearch(q) {
|
|
64
|
+
const s = (q || "").trim();
|
|
65
|
+
if (!s) return;
|
|
66
|
+
try { localStorage.setItem(RECENT_KEY, JSON.stringify([s, ...readRecent().filter(x => x !== s)].slice(0, RECENT_MAX))); } catch { /* this page only */ }
|
|
67
|
+
}
|
|
68
|
+
function forgetRecent() { try { localStorage.removeItem(RECENT_KEY); } catch { /* nothing kept */ } }
|
|
69
|
+
|
|
70
|
+
/** The request's query string for the query and filters (null: nothing to search). */
|
|
71
|
+
export function searchUrl(q, f, now = Date.now()) {
|
|
72
|
+
const query = (q || "").trim();
|
|
73
|
+
if (!query) return null;
|
|
74
|
+
const p = new URLSearchParams({ q: query, limit: "60" });
|
|
75
|
+
if (f.instance) p.append("instance", f.instance);
|
|
76
|
+
if (f.team) p.set("team", f.team);
|
|
77
|
+
if (f.sources && f.sources.length && f.sources.length < SOURCES.length) for (const s of f.sources) p.append("source", s);
|
|
78
|
+
if (SINCE[f.since]) p.set("since", String(now - SINCE[f.since]));
|
|
79
|
+
if (f.mine) p.set("mine", "1");
|
|
80
|
+
return `/ui/search?${p.toString()}`;
|
|
81
|
+
}
|
|
82
|
+
/** Hits grouped by agent, in the order the newest of each appears; `index` is each hit's place in the flat list. */
|
|
83
|
+
export function groupHits(hits) {
|
|
84
|
+
const groups = new Map();
|
|
85
|
+
hits.forEach((hit) => {
|
|
86
|
+
if (!groups.has(hit.instance)) groups.set(hit.instance, []);
|
|
87
|
+
groups.get(hit.instance).push(hit);
|
|
88
|
+
});
|
|
89
|
+
let i = 0;
|
|
90
|
+
return [...groups].map(([instance, list]) => ({ instance, hits: list.map(hit => ({ hit, index: i++ })) }));
|
|
91
|
+
}
|
|
92
|
+
/** The flat order ↑↓ moves through: the groups', top to bottom. */
|
|
93
|
+
export const flatHits = (groups) => groups.flatMap(g => g.hits.map(x => x.hit));
|
|
94
|
+
/** A snippet as text segments, the matched ones marked (ranges from the server, clipped to the text). */
|
|
95
|
+
export function snippetSegments(text, marks) {
|
|
96
|
+
const s = String(text || ""), out = [];
|
|
97
|
+
let at = 0;
|
|
98
|
+
for (const m of Array.isArray(marks) ? marks : []) {
|
|
99
|
+
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));
|
|
100
|
+
if (a > at) out.push({ text: s.slice(at, a) });
|
|
101
|
+
if (b > a) out.push({ text: s.slice(a, b), mark: true });
|
|
102
|
+
at = b;
|
|
103
|
+
}
|
|
104
|
+
if (at < s.length) out.push({ text: s.slice(at) });
|
|
105
|
+
return out;
|
|
106
|
+
}
|
|
107
|
+
/** The query's words marked in `text` (case-insensitive, every occurrence; overlapping ones merged) — for the copy view. */
|
|
108
|
+
export function markTerms(text, q) {
|
|
109
|
+
const s = String(text || ""), lower = s.toLowerCase();
|
|
110
|
+
const terms = [...new Set(String(q || "").toLowerCase().split(/\s+/).filter(Boolean))].slice(0, 8);
|
|
111
|
+
const ranges = [];
|
|
112
|
+
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]);
|
|
113
|
+
ranges.sort((a, b) => a[0] - b[0]);
|
|
114
|
+
const merged = [];
|
|
115
|
+
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]); }
|
|
116
|
+
return snippetSegments(s, merged);
|
|
117
|
+
}
|
|
118
|
+
/** "14:05" today, "10/19 14:05" this year, "2025/10/19" before. */
|
|
119
|
+
export function when(ms, now = Date.now()) {
|
|
120
|
+
const d = new Date(ms), n = new Date(now);
|
|
121
|
+
if (Number.isNaN(d.getTime())) return "";
|
|
122
|
+
const hm = `${String(d.getHours()).padStart(2, "0")}:${String(d.getMinutes()).padStart(2, "0")}`;
|
|
123
|
+
if (d.toDateString() === n.toDateString()) return hm;
|
|
124
|
+
return d.getFullYear() === n.getFullYear() ? `${d.getMonth() + 1}/${d.getDate()} ${hm}` : `${d.getFullYear()}/${d.getMonth() + 1}/${d.getDate()}`;
|
|
125
|
+
}
|
|
126
|
+
/** A chat hit's message key in its agent's chat (#m=<boot>-<id>), or null. */
|
|
127
|
+
export function messageKeyOf(hit) {
|
|
128
|
+
const boot = hit && hit.ref && hit.ref.boot, id = hit && hit.ref && hit.ref.id;
|
|
129
|
+
return typeof boot === "string" && /^[0-9a-f]{1,32}$/.test(boot) && Number.isSafeInteger(id) && id >= 0 ? `${boot}-${id}` : null;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Open an agent's chat at a message: the route, then the address's #m= — the chat reads it as it mounts, and (already
|
|
133
|
+
* open) on the hashchange this sends.
|
|
134
|
+
*/
|
|
135
|
+
export function jumpToMessage(instance, key, w = window) {
|
|
136
|
+
const path = chatPath(instance);
|
|
137
|
+
navigate(path);
|
|
138
|
+
if (!key) return;
|
|
139
|
+
w.history.replaceState(null, "", `${path}#m=${key}`);
|
|
140
|
+
w.dispatchEvent(new Event("hashchange"));
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const errorText = (status) => (status === 404 ? t("search.unavailable") : status === 503 ? t("search.busy") : status === 401 ? t("search.signedOut") : t("search.failed"));
|
|
144
|
+
|
|
145
|
+
export function SearchDialog({ preset = {}, onClose }) {
|
|
146
|
+
const ref = useRef(null), input = useRef(null), list = useRef(null);
|
|
147
|
+
const app = useStore(appStore);
|
|
148
|
+
const names = useStore(namesStore);
|
|
149
|
+
const [q, setQ] = useState(preset.q || "");
|
|
150
|
+
const [f, setF] = useState({ instance: preset.instance || "", team: "", since: "any", sources: [...SOURCES], mine: false });
|
|
151
|
+
const [showFilters, setShowFilters] = useState(!!preset.instance);
|
|
152
|
+
const [teams, setTeams] = useState(null);
|
|
153
|
+
const [res, setRes] = useState({ status: "idle" });
|
|
154
|
+
const [active, setActive] = useState(0);
|
|
155
|
+
const [detail, setDetail] = useState(null);
|
|
156
|
+
const [recent, setRecent] = useState(readRecent);
|
|
157
|
+
const seq = useRef(0);
|
|
158
|
+
|
|
159
|
+
// The native modal: focus trapped, the page inert, Esc → cancel (closed through the parent), a click on the backdrop.
|
|
160
|
+
useLayoutEffect(() => {
|
|
161
|
+
const d = ref.current;
|
|
162
|
+
const opener = typeof document !== "undefined" ? document.activeElement : null;
|
|
163
|
+
if (d && typeof d.showModal === "function" && !d.open) d.showModal();
|
|
164
|
+
input.current && input.current.focus();
|
|
165
|
+
return () => {
|
|
166
|
+
if (d && d.open && typeof d.close === "function") d.close();
|
|
167
|
+
if (opener && typeof opener.focus === "function" && document.contains?.(opener)) opener.focus();
|
|
168
|
+
};
|
|
169
|
+
}, []);
|
|
170
|
+
useEffect(() => {
|
|
171
|
+
const d = ref.current;
|
|
172
|
+
if (!d) return undefined;
|
|
173
|
+
const onCancel = (e) => { e.preventDefault(); onClose(); };
|
|
174
|
+
const onClick = (e) => { if (e.target === d) onClose(); };
|
|
175
|
+
d.addEventListener("cancel", onCancel);
|
|
176
|
+
d.addEventListener("click", onClick);
|
|
177
|
+
return () => { d.removeEventListener("cancel", onCancel); d.removeEventListener("click", onClick); };
|
|
178
|
+
}, [onClose]);
|
|
179
|
+
|
|
180
|
+
// The query, DEBOUNCE_MS after the last change; an answer to an older one is dropped.
|
|
181
|
+
useEffect(() => {
|
|
182
|
+
const url = searchUrl(q, f);
|
|
183
|
+
const mine = ++seq.current;
|
|
184
|
+
if (!url) { setRes({ status: "idle" }); return undefined; }
|
|
185
|
+
setRes(r => ({ ...r, status: "loading" }));
|
|
186
|
+
const ac = typeof AbortController === "function" ? new AbortController() : null;
|
|
187
|
+
const timer = setTimeout(async () => {
|
|
188
|
+
try {
|
|
189
|
+
const r = await fetch(url, { headers: { Accept: "application/json" }, ...(ac ? { signal: ac.signal } : {}) });
|
|
190
|
+
if (mine !== seq.current) return;
|
|
191
|
+
if (!r.ok) { setRes({ status: "error", error: errorText(r.status) }); return; }
|
|
192
|
+
const body = await r.json();
|
|
193
|
+
if (mine !== seq.current) return;
|
|
194
|
+
setRes({ status: "ok", hits: Array.isArray(body.hits) ? body.hits : [], limited: !!body.limited, building: !!body.building });
|
|
195
|
+
setActive(0);
|
|
196
|
+
} catch (err) {
|
|
197
|
+
if (mine === seq.current && !(err && err.name === "AbortError")) setRes({ status: "error", error: t("search.failed") });
|
|
198
|
+
}
|
|
199
|
+
}, DEBOUNCE_MS);
|
|
200
|
+
return () => { clearTimeout(timer); if (ac) ac.abort(); };
|
|
201
|
+
}, [q, f]);
|
|
202
|
+
|
|
203
|
+
// Teams, once the filters are first shown.
|
|
204
|
+
useEffect(() => {
|
|
205
|
+
if (!showFilters || teams) return;
|
|
206
|
+
fetch("/ui/teams", { headers: { Accept: "application/json" } }).then(r => (r.ok ? r.json() : { teams: {} })).then(
|
|
207
|
+
(b) => setTeams(Object.keys((b && b.teams) || {}).sort()), () => setTeams([]));
|
|
208
|
+
}, [showFilters, teams]);
|
|
209
|
+
|
|
210
|
+
const byName = useMemo(() => new Map((app.instances || []).map(it => [it.name ?? it.instance_name, it])), [app.instances]);
|
|
211
|
+
const label = (name) => (byName.has(name) ? namesOf(byName.get(name), names.primary).primary : name);
|
|
212
|
+
// While a new query runs, the last answer stays (dimmed) rather than flashing empty.
|
|
213
|
+
const groups = useMemo(() => groupHits(res.hits || []), [res]);
|
|
214
|
+
const flat = useMemo(() => flatHits(groups), [groups]);
|
|
215
|
+
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);
|
|
216
|
+
|
|
217
|
+
// The active row stays in view.
|
|
218
|
+
useEffect(() => {
|
|
219
|
+
const el = list.current && list.current.querySelector(`#search-opt-${active}`);
|
|
220
|
+
if (el && typeof el.scrollIntoView === "function") el.scrollIntoView({ block: "nearest" });
|
|
221
|
+
}, [active, res]);
|
|
222
|
+
|
|
223
|
+
const open = (hit) => {
|
|
224
|
+
if (!hit) return;
|
|
225
|
+
rememberSearch(q); setRecent(readRecent());
|
|
226
|
+
const key = hit.source === "chat" ? messageKeyOf(hit) : null;
|
|
227
|
+
if (key && hit.live) { onClose(); jumpToMessage(hit.instance, key); return; }
|
|
228
|
+
setDetail({ hit, status: "loading" });
|
|
229
|
+
// Any failure — the request, or a body that is not JSON — ends as an error, never "Loading…" for good.
|
|
230
|
+
fetch(`/ui/search/context?key=${encodeURIComponent(hit.key)}`, { headers: { Accept: "application/json" } })
|
|
231
|
+
.then(async (r) => {
|
|
232
|
+
if (!r.ok) { setDetail(d => (d && d.hit === hit ? { hit, status: "error", error: r.status === 404 ? t("search.notFound") : errorText(r.status) } : d)); return; }
|
|
233
|
+
const body = await r.json();
|
|
234
|
+
setDetail(d => (d && d.hit === hit ? { hit, status: "ok", ctx: body } : d));
|
|
235
|
+
})
|
|
236
|
+
.catch(() => setDetail(d => (d && d.hit === hit ? { hit, status: "error", error: t("search.failed") } : d)));
|
|
237
|
+
};
|
|
238
|
+
const onKey = (e) => {
|
|
239
|
+
if (e.isComposing) return;
|
|
240
|
+
if ((e.ctrlKey || e.metaKey) && (e.key === "k" || e.key === "K")) { e.preventDefault(); input.current && input.current.select(); return; }
|
|
241
|
+
// ↑↓ and Enter are the query box's: in the filters a select or a chip keeps its own keys.
|
|
242
|
+
if (detail || e.target !== input.current) return;
|
|
243
|
+
if (e.key === "ArrowDown") { e.preventDefault(); setActive(i => (flat.length ? (i + 1) % flat.length : 0)); }
|
|
244
|
+
else if (e.key === "ArrowUp") { e.preventDefault(); setActive(i => (flat.length ? (i - 1 + flat.length) % flat.length : 0)); }
|
|
245
|
+
else if (e.key === "Enter") { e.preventDefault(); if (flat.length) open(flat[Math.min(active, flat.length - 1)]); else rememberSearch(q); }
|
|
246
|
+
};
|
|
247
|
+
const setFilter = (patch) => setF(x => ({ ...x, ...patch }));
|
|
248
|
+
const toggleSource = (s) => setF(x => {
|
|
249
|
+
const has = x.sources.includes(s);
|
|
250
|
+
const next = has ? x.sources.filter(y => y !== s) : [...x.sources, s];
|
|
251
|
+
return { ...x, sources: next.length ? SOURCES.filter(y => next.includes(y)) : x.sources }; // never none
|
|
252
|
+
});
|
|
253
|
+
const activeId = flat.length && !detail ? `search-opt-${Math.min(active, flat.length - 1)}` : undefined;
|
|
254
|
+
|
|
255
|
+
return html`<dialog data-trust="dialog" ref=${ref} class="dlg search-dlg" aria-label=${t("search.label")} onKeyDown=${onKey}>
|
|
256
|
+
<div class="search-box">
|
|
257
|
+
<div class="search-head">
|
|
258
|
+
<${Icon} name="search" />
|
|
259
|
+
<input ref=${input} class="search-input" type="search" value=${q} placeholder=${t("search.placeholder")} aria-label=${t("search.label")}
|
|
260
|
+
role="combobox" aria-expanded=${flat.length && !detail ? "true" : "false"} aria-controls="search-results" aria-autocomplete="list"
|
|
261
|
+
aria-activedescendant=${activeId} autocomplete="off" spellcheck=${false} enterkeyhint="search"
|
|
262
|
+
onInput=${(e) => { setQ(e.target.value); setDetail(null); }} />
|
|
263
|
+
<button type="button" class=${`chip-btn search-filters-toggle${showFilters ? " on" : ""}`} aria-expanded=${showFilters ? "true" : "false"} aria-controls="search-filters"
|
|
264
|
+
onClick=${() => setShowFilters(v => !v)}><${Icon} name="sliders" size=${14} />${activeFilters ? t("search.filtersOn", activeFilters) : t("search.filters")}</button>
|
|
265
|
+
<button type="button" class="icon-btn" aria-label=${t("search.close")} title=${t("search.close")} onClick=${onClose}><${Icon} name="close" /></button>
|
|
266
|
+
</div>
|
|
267
|
+
${showFilters ? html`<div id="search-filters" class="search-filters" role="group" aria-label=${t("search.filters")}>
|
|
268
|
+
<label class="search-field"><span>${t("search.agent")}</span>
|
|
269
|
+
<select value=${f.instance} onChange=${(e) => setFilter({ instance: e.target.value })}>
|
|
270
|
+
<option value="">${t("search.anyAgent")}</option>
|
|
271
|
+
${[...byName.keys()].sort().map(n => html`<option value=${n}>${label(n)}</option>`)}
|
|
272
|
+
${f.instance && !byName.has(f.instance) ? html`<option value=${f.instance}>${f.instance}</option>` : null}
|
|
273
|
+
</select></label>
|
|
274
|
+
${teams && teams.length ? html`<label class="search-field"><span>${t("search.team")}</span>
|
|
275
|
+
<select value=${f.team} onChange=${(e) => setFilter({ team: e.target.value })}>
|
|
276
|
+
<option value="">${t("search.anyTeam")}</option>${teams.map(n => html`<option value=${n}>${n}</option>`)}</select></label>` : null}
|
|
277
|
+
<div class="search-chips" role="group" aria-label=${t("search.time")}>
|
|
278
|
+
${["any", "day", "week", "month"].map(k => html`<button type="button" class=${`chip-btn${f.since === k ? " on" : ""}`} aria-pressed=${f.since === k ? "true" : "false"}
|
|
279
|
+
onClick=${() => setFilter({ since: k })}>${k === "any" ? t("search.anyTime") : t(`search.${k}`)}</button>`)}
|
|
280
|
+
</div>
|
|
281
|
+
<div class="search-chips" role="group" aria-label=${t("search.sources")}>
|
|
282
|
+
${SOURCES.map(s => html`<button type="button" class=${`chip-btn${f.sources.includes(s) ? " on" : ""}`} aria-pressed=${f.sources.includes(s) ? "true" : "false"}
|
|
283
|
+
onClick=${() => toggleSource(s)}>${t(`search.src_${s}`)}</button>`)}
|
|
284
|
+
<label class="search-mine"><input type="checkbox" checked=${f.mine} onChange=${(e) => setFilter({ mine: e.target.checked })} />${t("search.mine")}</label>
|
|
285
|
+
</div>
|
|
286
|
+
</div>` : null}
|
|
287
|
+
<div class="search-body" ref=${list}>
|
|
288
|
+
${detail ? html`<${Detail} detail=${detail} q=${q} label=${label} onBack=${() => { setDetail(null); input.current && input.current.focus(); }} onClose=${onClose} />`
|
|
289
|
+
: html`<${Results} q=${q} res=${res} groups=${groups} active=${active} setActive=${setActive} open=${open} label=${label} filtered=${activeFilters > 0}
|
|
290
|
+
recent=${recent} pick=${(s) => { recordEdit(input.current); setQ(s); input.current && input.current.focus(); }} clearRecent=${() => { forgetRecent(); setRecent([]); }} />`}
|
|
291
|
+
</div>
|
|
292
|
+
<div class="search-foot" aria-hidden="true">${t("search.keys")}</div>
|
|
293
|
+
</div></dialog>`;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
function Results({ q, res, groups, active, setActive, open, label, filtered, recent, pick, clearRecent }) {
|
|
297
|
+
if (!q.trim()) {
|
|
298
|
+
return html`<div class="search-empty"><p class="search-hint">${t("search.hint")}</p>
|
|
299
|
+
${recent.length ? html`<div class="search-recent"><div class="search-recent-head"><span>${t("search.recent")}</span>
|
|
300
|
+
<button type="button" class="chip-btn" onClick=${clearRecent}>${t("search.clearRecent")}</button></div>
|
|
301
|
+
<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>`;
|
|
302
|
+
}
|
|
303
|
+
if (res.status === "error") return html`<p class="search-status bad" role="alert">${res.error}</p>`;
|
|
304
|
+
const hits = res.hits || [];
|
|
305
|
+
return html`
|
|
306
|
+
${res.building ? html`<p class="search-note" role="status">${t("search.building")}</p>` : null}
|
|
307
|
+
${res.limited ? html`<p class="search-note" role="status">${t("search.limited")}</p>` : null}
|
|
308
|
+
${res.status === "loading" && !hits.length ? html`<p class="search-status" role="status">${t("search.searching")}</p>` : null}
|
|
309
|
+
${res.status === "ok" && !hits.length ? html`<p class="search-status" role="status">${filtered ? t("search.noneFiltered") : t("search.none")}</p>` : null}
|
|
310
|
+
<div id="search-results" role="listbox" aria-label=${t("search.results")} class=${`search-results${res.status === "loading" ? " stale" : ""}`}>
|
|
311
|
+
${groups.map(g => html`<div role="group" class="search-group" aria-label=${label(g.instance)}>
|
|
312
|
+
<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>
|
|
313
|
+
${g.hits.map(({ hit, index }) => html`<${Row} hit=${hit} index=${index} on=${index === active} label=${label} onHover=${() => setActive(index)} onOpen=${() => open(hit)} />`)}
|
|
314
|
+
</div>`)}
|
|
315
|
+
</div>`;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
function Row({ hit, index, on, label, onHover, onOpen }) {
|
|
319
|
+
const who = hit.source === "xmsg" ? t("search.from", label(hit.instance), label(hit.peer || "")) : hit.source === "task" ? (hit.role || "") : hit.sender || "";
|
|
320
|
+
return html`<div id=${`search-opt-${index}`} role="option" aria-selected=${on ? "true" : "false"} class=${`search-row${on ? " on" : ""}`}
|
|
321
|
+
onMouseMove=${on ? undefined : onHover} onClick=${onOpen}>
|
|
322
|
+
<div class="search-row-top"><span class=${`search-badge ${hit.source}`}>${t(`search.badge_${hit.source}`)}</span>
|
|
323
|
+
<span class="search-who">${who}${hit.source === "xmsg" && hit.role && hit.role !== "message" ? html` <span class="search-kind">${hit.role}</span>` : null}</span>
|
|
324
|
+
<time class="search-time" datetime=${Number.isFinite(hit.ts) ? new Date(hit.ts).toISOString() : undefined}>${when(hit.ts)}</time></div>
|
|
325
|
+
<div class="search-snippet">${snippetSegments(hit.snippet, hit.marks).map(s => (s.mark ? html`<mark>${s.text}</mark>` : s.text))}</div>
|
|
326
|
+
</div>`;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
function Detail({ detail, q, label, onBack, onClose }) {
|
|
330
|
+
const { hit } = detail;
|
|
331
|
+
const go = (path, key) => { onClose(); if (key !== undefined) jumpToMessage(hit.instance, key); else navigate(path); };
|
|
332
|
+
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>
|
|
333
|
+
<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>
|
|
334
|
+
<time class="search-time">${when(hit.ts)}</time></div>`;
|
|
335
|
+
if (detail.status === "loading") return html`<div class="search-detail">${head}<p class="search-status" role="status">${t("search.loadingCtx")}</p></div>`;
|
|
336
|
+
if (detail.status === "error") return html`<div class="search-detail">${head}<p class="search-status bad" role="alert">${detail.error}</p></div>`;
|
|
337
|
+
const ctx = detail.ctx || {};
|
|
338
|
+
const doc = ctx.doc || {};
|
|
339
|
+
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>
|
|
340
|
+
<div class="search-ctx-text">${markTerms(d.text, q).map(x => (x.mark ? html`<mark>${x.text}</mark>` : x.text))}</div></li>`;
|
|
341
|
+
let actions;
|
|
342
|
+
if (hit.source === "chat") actions = html`<button type="button" class="btn btn-primary" onClick=${() => go(null, messageKeyOf(hit))}>${t("search.openChat")}</button>`;
|
|
343
|
+
else if (hit.source === "xmsg") actions = html`<button type="button" class="btn" onClick=${() => go(chatPath(hit.instance))}>${t("search.openChatOf", label(hit.instance))}</button>
|
|
344
|
+
${hit.peer ? html`<button type="button" class="btn" onClick=${() => go(chatPath(hit.peer))}>${t("search.openChatOf", label(hit.peer))}</button>` : null}`;
|
|
345
|
+
else actions = html`<button type="button" class="btn btn-primary" onClick=${() => go(fleetPath("tasks"))}>${t("search.openTasks")}</button>`;
|
|
346
|
+
return html`<div class="search-detail">${head}
|
|
347
|
+
${hit.source === "chat" ? html`<p class="search-note">${t("search.gone")}</p>` : null}
|
|
348
|
+
<ol class="search-ctx">${(ctx.before || []).map(d => line(d, false))}${line(doc, true)}${(ctx.after || []).map(d => line(d, false))}</ol>
|
|
349
|
+
<div class="search-detail-actions">${actions}</div></div>`;
|
|
350
|
+
}
|
|
@@ -2,10 +2,12 @@
|
|
|
2
2
|
// versions, the pictures and fonts it may name, and this device's off switch. Settings itself is always drawn stock
|
|
3
3
|
// (owner-css.js removes the link here), so a stylesheet that hides things can always be fixed from this page.
|
|
4
4
|
import { html, useEffect, useRef, useState } from "/assets/app-html.js";
|
|
5
|
-
import { t } from "/assets/app-i18n.js";
|
|
5
|
+
import { register, t } from "/assets/app-i18n.js";
|
|
6
|
+
import { recordEdit } from "/assets/text-undo.js";
|
|
6
7
|
import { appStore, useStore } from "/assets/app-store.js";
|
|
7
8
|
import { ModsCard } from "./settings-mods.js";
|
|
8
|
-
import {
|
|
9
|
+
import { experimentalComponent, useCanvasWorker } from "/assets/feature-flags.js";
|
|
10
|
+
const EffectsCard = experimentalComponent(() => import("./settings-effects.js"), m => m.EffectsCard);
|
|
9
11
|
import { Icon } from "/assets/ui-icons.js";
|
|
10
12
|
import { toast } from "/assets/ui-toast.js";
|
|
11
13
|
import { ownerCssOffHere, ownerCssTrial, safeMode, setOwnerCssActive, setOwnerCssOffHere, startOwnerCssTrial } from "/assets/owner-css.js";
|
|
@@ -21,6 +23,15 @@ import { CardTitle } from "./settings-learn.js";
|
|
|
21
23
|
import "./settings-strings.js";
|
|
22
24
|
|
|
23
25
|
const tn = (k, ...v) => t(`settings.${k}`, ...v);
|
|
26
|
+
/** #2390: a refusal's detail in the page's language when the host named it (detail_key), else its English as sent. */
|
|
27
|
+
export function appIconDetail(body) {
|
|
28
|
+
const key = body && typeof body.detail_key === "string" ? `appIconDetail_${body.detail_key}` : null;
|
|
29
|
+
const english = (body && body.detail) || "";
|
|
30
|
+
if (!key) return english;
|
|
31
|
+
const args = Array.isArray(body.detail_args) ? body.detail_args : [];
|
|
32
|
+
const said = tn(key, ...(body.detail_key === "corrupt" ? [english] : args));
|
|
33
|
+
return said === `settings.${key}` ? english : said;
|
|
34
|
+
}
|
|
24
35
|
const short = (sha) => (sha ? sha.slice(0, 8) : "");
|
|
25
36
|
const kib = (n) => `${Math.max(1, Math.round(n / 1024))} KiB`;
|
|
26
37
|
|
|
@@ -45,11 +56,30 @@ export function AppearanceSection() {
|
|
|
45
56
|
const publicLink = typeof document !== "undefined" && document.body && document.body.dataset.publicLink === "1";
|
|
46
57
|
if (publicLink) return html`<section class="card s-appearance"><h3>${tn("cssTitle")}</h3><p class="sub">${tn("cssPublicLink")}</p></section>`;
|
|
47
58
|
const group = (key) => html`<h2 class="s-ap-group">${tn(key)}</h2>`;
|
|
48
|
-
return html`${group("groupLook")}<${ThemesCard} /><${IdentityColorCard} /><${CardSizeCard} />
|
|
59
|
+
return html`${group("groupLook")}<${ThemesCard} /><${IdentityColorCard} /><${CardSizeCard} /><${AppIconCard} />
|
|
49
60
|
${group("groupLayout")}<${SidebarWidgetCard} />
|
|
50
61
|
${group("groupCss")}<${OwnerCssCard} /><${DeviceCard} />
|
|
51
62
|
${group("groupMods")}<${ModsCard} /><${EffectsCard} />
|
|
52
|
-
${group("groupAdvanced")}<${CardLimitsCard} /><${RestoreDefaultsCard} />`;
|
|
63
|
+
${group("groupAdvanced")}<${ExperimentalEffectsCard} /><${CardLimitsCard} /><${RestoreDefaultsCard} />`;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
register("experimentalEffects", {
|
|
67
|
+
en: { title: "Experimental: Canvas Worker effects", help: "Enable uploaded Canvas Worker effects. Ambient effects are always available. Saved effects stay intact when this is off.", failed: "Could not change this setting. Try again." },
|
|
68
|
+
"zh-TW": { title: "實驗功能:Canvas Worker 特效", help: "啟用上傳的 Canvas Worker 特效。氛圍特效不受影響;關閉時保留已儲存的特效。", failed: "無法變更此設定,請重試。" },
|
|
69
|
+
});
|
|
70
|
+
function ExperimentalEffectsCard() {
|
|
71
|
+
const enabled = useCanvasWorker(), [writable, setWritable] = useState(false), [busy, setBusy] = useState(false), [failed, setFailed] = useState(false);
|
|
72
|
+
useEffect(() => { let live = true; Promise.resolve(globalThis.AgendAuth?.trialSession?.()).then(value => { if (live) setWritable(value?.localAdmin === true); }, () => {}); return () => { live = false; }; }, []);
|
|
73
|
+
if (!writable) return null;
|
|
74
|
+
const change = async value => {
|
|
75
|
+
setBusy(true); setFailed(false);
|
|
76
|
+
try {
|
|
77
|
+
const result = await confirmedWrite("/api/settings/fleet/web", { method: "PUT", body: { experimental: { canvas_worker: value } }, label: t("experimentalEffects.title") });
|
|
78
|
+
if (result.ok) appStore.set({ canvasWorker: value }); else setFailed(true);
|
|
79
|
+
} catch { setFailed(true); }
|
|
80
|
+
finally { setBusy(false); }
|
|
81
|
+
};
|
|
82
|
+
return html`<section class="card s-appearance s-experimental-effects"><label class="check"><input type="checkbox" checked=${enabled} disabled=${busy} onChange=${event => change(event.target.checked)} /><span>${t("experimentalEffects.title")}</span></label><p class="sub">${t("experimentalEffects.help")}</p>${failed ? html`<p role="alert">${t("experimentalEffects.failed")}</p>` : null}</section>`;
|
|
53
83
|
}
|
|
54
84
|
|
|
55
85
|
/** #2056 B: a sample sidebar row in the host's default colour places, so the choice is seen rather than described. */
|
|
@@ -84,7 +114,8 @@ function IdentityColorCard() {
|
|
|
84
114
|
/**
|
|
85
115
|
* #1958 decision 5: Restore defaults — the host's appearance settings back to their defaults after a confirm that says
|
|
86
116
|
* what it resets; this person's custom CSS turned off (its versions stay). Not settings, so never touched: the colours
|
|
87
|
-
* people picked per instance, installed mods, uploaded effects and the
|
|
117
|
+
* people picked per instance (and where each agent's colours go, #2196), installed mods, uploaded effects and the
|
|
118
|
+
* fleet's card limits.
|
|
88
119
|
*/
|
|
89
120
|
export async function restoreAppearanceDefaults() {
|
|
90
121
|
setThemesShown(true);
|
|
@@ -126,6 +157,131 @@ function CardSizeCard() {
|
|
|
126
157
|
</section>`;
|
|
127
158
|
}
|
|
128
159
|
|
|
160
|
+
/**
|
|
161
|
+
* #2308: the image as one square PNG the host makes the icon sizes from — centre-cropped, drawn at 512 px if smaller
|
|
162
|
+
* (marked low-resolution), as it is up to 1024 px, else 1024. The browser decodes whatever it can show (PNG, JPG, WebP);
|
|
163
|
+
* the host takes only that PNG.
|
|
164
|
+
*/
|
|
165
|
+
export async function appIconPng(file) {
|
|
166
|
+
const bmp = await createImageBitmap(file);
|
|
167
|
+
try {
|
|
168
|
+
const side = Math.min(bmp.width, bmp.height);
|
|
169
|
+
const out = Math.min(1024, Math.max(512, side));
|
|
170
|
+
const canvas = document.createElement("canvas");
|
|
171
|
+
canvas.width = canvas.height = out;
|
|
172
|
+
const g = canvas.getContext("2d");
|
|
173
|
+
g.imageSmoothingQuality = "high";
|
|
174
|
+
g.drawImage(bmp, (bmp.width - side) / 2, (bmp.height - side) / 2, side, side, 0, 0, out, out);
|
|
175
|
+
const blob = await new Promise((resolve) => canvas.toBlob(resolve, "image/png"));
|
|
176
|
+
if (!blob) throw new Error("no PNG");
|
|
177
|
+
return { blob, low: side < 512 };
|
|
178
|
+
} finally {
|
|
179
|
+
if (bmp.close) bmp.close();
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/** #2308: this page's icon links follow a change at once (the next pages carry the version from the host). */
|
|
184
|
+
export function refreshAppIconLinks(version, custom) {
|
|
185
|
+
if (typeof document === "undefined") return;
|
|
186
|
+
const v = encodeURIComponent(version);
|
|
187
|
+
const head = document.head;
|
|
188
|
+
const links = [...head.querySelectorAll("link")];
|
|
189
|
+
const byPath = (path) => links.find((l) => (l.getAttribute("href") || "").split("?")[0] === path);
|
|
190
|
+
for (const [path, rel] of [["/favicon.ico", "icon"], ["/apple-touch-icon.png", "apple-touch-icon"], ["/manifest.webmanifest", "manifest"]]) {
|
|
191
|
+
const el = byPath(path);
|
|
192
|
+
if (el && el.getAttribute("rel") === rel) el.setAttribute("href", `${path}?v=${v}`);
|
|
193
|
+
}
|
|
194
|
+
const svg = byPath("/favicon.svg");
|
|
195
|
+
if (custom && svg) svg.remove();
|
|
196
|
+
else if (!custom && !svg) {
|
|
197
|
+
const link = document.createElement("link");
|
|
198
|
+
for (const [k, val] of [["rel", "icon"], ["href", `/favicon.svg?v=${v}`], ["type", "image/svg+xml"], ["sizes", "any"]]) link.setAttribute(k, val);
|
|
199
|
+
head.appendChild(link);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* #2308: the app icon — what a browser tab and a home screen show. One picture for every page (sign-in and the public
|
|
205
|
+
* link too); an admin changes it, everyone else sees it.
|
|
206
|
+
*/
|
|
207
|
+
function AppIconCard() {
|
|
208
|
+
const [state, setState] = useState(null);
|
|
209
|
+
const [canWrite, setCanWrite] = useState(false);
|
|
210
|
+
const [picked, setPicked] = useState(null); // { blob, url, low }
|
|
211
|
+
const [busy, setBusy] = useState(false);
|
|
212
|
+
const [error, setError] = useState("");
|
|
213
|
+
const file = useRef(null);
|
|
214
|
+
const load = async () => {
|
|
215
|
+
const r = await call("/ui/app-icon");
|
|
216
|
+
setState(r.ok && r.body && typeof r.body.version === "string" ? r.body : { failed: true });
|
|
217
|
+
};
|
|
218
|
+
useEffect(() => {
|
|
219
|
+
load();
|
|
220
|
+
const auth = typeof window !== "undefined" ? window.AgendAuth : null;
|
|
221
|
+
if (auth && auth.canWrite) Promise.resolve(auth.canWrite()).then((v) => setCanWrite(v === true), () => {});
|
|
222
|
+
}, []);
|
|
223
|
+
useEffect(() => () => { if (picked) URL.revokeObjectURL(picked.url); }, [picked]);
|
|
224
|
+
const title = html`<${CardTitle} title=${tn("appIconTitle")} topic="app-icon" />`;
|
|
225
|
+
if (!state) return html`<section class="card s-appearance s-app-icon">${title}<p class="sub">…</p></section>`;
|
|
226
|
+
if (state.failed) return html`<section class="card s-appearance s-app-icon">${title}<p class="err">${tn("appIconLoadFailed")}</p></section>`;
|
|
227
|
+
|
|
228
|
+
const choose = async () => {
|
|
229
|
+
const f = file.current && file.current.files && file.current.files[0];
|
|
230
|
+
if (file.current) file.current.value = "";
|
|
231
|
+
if (!f) return;
|
|
232
|
+
setError("");
|
|
233
|
+
try {
|
|
234
|
+
const made = await appIconPng(f);
|
|
235
|
+
setPicked({ ...made, url: URL.createObjectURL(made.blob) });
|
|
236
|
+
} catch {
|
|
237
|
+
setError(tn("appIconNotAnImage"));
|
|
238
|
+
}
|
|
239
|
+
};
|
|
240
|
+
const answered = (r, done) => {
|
|
241
|
+
setBusy(false);
|
|
242
|
+
if (!r.ok) {
|
|
243
|
+
const known = ["app_icon_too_large", "app_icon_not_png", "app_icon_unreadable", "app_icon_admin_only", "app_icon_needs_a_signed_in_person", "app_icon_busy", "app_icon_superseded", "app_icon_not_reset", "app_icon_not_kept", "app_icon_failed"];
|
|
244
|
+
setError(known.includes(r.body && r.body.error) ? tn(`appIconError_${r.body.error}`, appIconDetail(r.body)) : tn("appIconError_other", (r.body && r.body.error) || String(r.status)));
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
setState(r.body);
|
|
248
|
+
setPicked(null);
|
|
249
|
+
refreshAppIconLinks(r.body.version, r.body.custom);
|
|
250
|
+
toast(done);
|
|
251
|
+
};
|
|
252
|
+
const use = async () => {
|
|
253
|
+
setBusy(true); setError("");
|
|
254
|
+
answered(await call("/ui/app-icon", { method: "PUT", headers: { "Content-Type": "image/png" }, body: picked.blob }), tn("appIconSaved"));
|
|
255
|
+
};
|
|
256
|
+
const reset = async () => {
|
|
257
|
+
setBusy(true); setError("");
|
|
258
|
+
answered(await call("/ui/app-icon", { method: "DELETE" }), tn("appIconResetDone"));
|
|
259
|
+
};
|
|
260
|
+
const v = encodeURIComponent(state.version);
|
|
261
|
+
return html`<section class="card s-appearance s-app-icon">${title}
|
|
262
|
+
<p class="sub">${tn("appIconLead")}</p>
|
|
263
|
+
<div class="s-app-icon-row">
|
|
264
|
+
<figure class="s-app-icon-now"><img class="s-app-icon-big" src=${`/apple-touch-icon.png?v=${v}`} width="96" height="96" alt=${tn("appIconCurrent")} />
|
|
265
|
+
<img class="s-app-icon-small" src=${`/icon-192.png?v=${v}`} width="32" height="32" alt="" />
|
|
266
|
+
<figcaption>${state.custom ? tn("appIconCustom") : tn("appIconDefault")}</figcaption></figure>
|
|
267
|
+
${picked ? html`<figure class="s-app-icon-new"><img class="s-app-icon-big" src=${picked.url} width="96" height="96" alt=${tn("appIconPreview")} />
|
|
268
|
+
<figcaption>${tn("appIconPreview")}</figcaption></figure>` : null}
|
|
269
|
+
</div>
|
|
270
|
+
${picked && picked.low ? html`<p class="sub s-app-icon-low">${tn("appIconLow")}</p>` : null}
|
|
271
|
+
${error ? html`<p class="err" role="alert">${error}</p>` : null}
|
|
272
|
+
${canWrite ? html`<div class="s-css-actions">
|
|
273
|
+
<input ref=${file} type="file" class="sr-only" id="s-app-icon-file" accept="image/png,image/jpeg,image/webp" onChange=${choose} />
|
|
274
|
+
${picked ? html`
|
|
275
|
+
<button type="button" class="btn btn-primary" disabled=${busy} onClick=${use}><${Icon} name="check" size=${16} />${tn("appIconUse")}</button>
|
|
276
|
+
<button type="button" class="btn" disabled=${busy} onClick=${() => setPicked(null)}>${tn("appIconCancel")}</button>`
|
|
277
|
+
: html`<button type="button" class="btn" disabled=${busy} onClick=${() => file.current && file.current.click()}>${tn("appIconChoose")}</button>`}
|
|
278
|
+
${state.custom && !picked ? html`<button type="button" class="btn" disabled=${busy} onClick=${reset}>${tn("appIconReset")}</button>` : null}
|
|
279
|
+
</div>` : html`<p class="sub">${tn("appIconReadOnly")}</p>`}
|
|
280
|
+
<p class="sub">${tn("appIconWho")}</p>
|
|
281
|
+
<p class="sub">${tn("appIconIos")}</p>
|
|
282
|
+
</section>`;
|
|
283
|
+
}
|
|
284
|
+
|
|
129
285
|
const LIMIT_FIELDS = [["portrait", "max_width"], ["portrait", "max_height"], ["portrait", "max_kib"], ["banner", "max_width"], ["banner", "max_height"], ["banner", "max_kib"],
|
|
130
286
|
["instance_quota", "files"], ["instance_quota", "mib"]];
|
|
131
287
|
/**
|
|
@@ -221,6 +377,7 @@ function SidebarWidgetCard() {
|
|
|
221
377
|
const app = useStore(appStore);
|
|
222
378
|
const saved = sidebarWidgetRaw();
|
|
223
379
|
const [text, setText] = useState(() => (saved ? JSON.stringify(saved, null, 2) : ""));
|
|
380
|
+
const area = useRef(null);
|
|
224
381
|
const [error, setError] = useState("");
|
|
225
382
|
let parsed = null, problem = "";
|
|
226
383
|
if (text.trim()) {
|
|
@@ -233,17 +390,17 @@ function SidebarWidgetCard() {
|
|
|
233
390
|
setSidebarWidget(parsed);
|
|
234
391
|
toast(tn("widgetSaved"));
|
|
235
392
|
};
|
|
236
|
-
const remove = () => { setSidebarWidget(null); setText(""); setError(""); toast(tn("widgetRemoved")); };
|
|
393
|
+
const remove = () => { setSidebarWidget(null); recordEdit(area.current); setText(""); setError(""); toast(tn("widgetRemoved")); };
|
|
237
394
|
return html`<section class="card s-appearance s-synced s-widget"><${CardTitle} title=${tn("widgetTitle")} topic="widgets" />
|
|
238
395
|
<p class="sub">${tn("widgetLead")}</p>
|
|
239
396
|
<label class="field"><span class="sr-only">${tn("widgetTitle")}</span>
|
|
240
|
-
<textarea class="s-widget-text" rows="10" spellcheck=${false} autocapitalize="off" autocomplete="off" value=${text}
|
|
397
|
+
<textarea ref=${area} class="s-widget-text" rows="10" spellcheck=${false} autocapitalize="off" autocomplete="off" value=${text}
|
|
241
398
|
onInput=${(e) => { setText(e.target.value); setError(""); }}></textarea></label>
|
|
242
399
|
${error || problem ? html`<p class="err" role="alert">${error || problem}</p>` : null}
|
|
243
400
|
${preview ? html`<div class="s-widget-preview"><${AgentWidget} slot="sidebar" widget=${preview} binds=${fleetBinds(app.instances, app.exec, app.awaiting)} /></div>` : null}
|
|
244
401
|
<div class="s-widget-actions">
|
|
245
402
|
<button type="button" class="btn btn-primary" disabled=${!parsed || !!problem} onClick=${save}><${Icon} name="check" size=${16} />${tn("widgetSave")}</button>
|
|
246
|
-
${!text.trim() ? html`<button type="button" class="btn" onClick=${() => setText(JSON.stringify(SIDEBAR_WIDGET_EXAMPLE, null, 2))}>${tn("widgetExample")}</button>` : null}
|
|
403
|
+
${!text.trim() ? html`<button type="button" class="btn" onClick=${() => { recordEdit(area.current); setText(JSON.stringify(SIDEBAR_WIDGET_EXAMPLE, null, 2)); }}>${tn("widgetExample")}</button>` : null}
|
|
247
404
|
${saved ? html`<button type="button" class="btn" onClick=${remove}>${tn("widgetRemove")}</button>` : null}
|
|
248
405
|
</div>
|
|
249
406
|
</section>`;
|
|
@@ -252,6 +409,7 @@ function SidebarWidgetCard() {
|
|
|
252
409
|
function OwnerCssCard() {
|
|
253
410
|
const [state, setState] = useState(null);
|
|
254
411
|
const [text, setText] = useState("");
|
|
412
|
+
const area = useRef(null);
|
|
255
413
|
const [busy, setBusy] = useState(false);
|
|
256
414
|
const [error, setError] = useState("");
|
|
257
415
|
const trial = useStore(ownerCssTrial);
|
|
@@ -260,6 +418,8 @@ function OwnerCssCard() {
|
|
|
260
418
|
const r = await call("/ui/owner-css/state");
|
|
261
419
|
if (!r.ok || !r.body) { setState({ failed: true }); return; }
|
|
262
420
|
setState(r.body);
|
|
421
|
+
// #2342: a restored version replaces the editor's text as a step Ctrl+Z goes back from.
|
|
422
|
+
if (opts.undoable) recordEdit(area.current);
|
|
263
423
|
if (opts.text !== false) setText(r.body.text || "");
|
|
264
424
|
};
|
|
265
425
|
useEffect(() => { load(); }, []);
|
|
@@ -283,7 +443,7 @@ function OwnerCssCard() {
|
|
|
283
443
|
setBusy(false);
|
|
284
444
|
if (!r.ok) { setError(refusal(r.body)); return; }
|
|
285
445
|
setOwnerCssActive(r.body.active);
|
|
286
|
-
load();
|
|
446
|
+
load({ undoable: true });
|
|
287
447
|
};
|
|
288
448
|
const addAsset = async () => {
|
|
289
449
|
const f = file.current && file.current.files && file.current.files[0];
|
|
@@ -314,7 +474,7 @@ function OwnerCssCard() {
|
|
|
314
474
|
<p class="sub">${tn("cssLead")}</p>
|
|
315
475
|
<p class="s-css-status" role="status">${inForce}</p>
|
|
316
476
|
<label class="field"><span class="sr-only">${tn("cssTitle")}</span>
|
|
317
|
-
<textarea class="s-css-text" rows="14" spellcheck=${false} autocapitalize="off" autocomplete="off" value=${text} disabled=${busy}
|
|
477
|
+
<textarea ref=${area} class="s-css-text" rows="14" spellcheck=${false} autocapitalize="off" autocomplete="off" value=${text} disabled=${busy}
|
|
318
478
|
onInput=${(e) => setText(e.target.value)}></textarea></label>
|
|
319
479
|
${error ? html`<p class="err" role="alert">${error}</p>` : null}
|
|
320
480
|
<div class="s-css-actions">
|